def test_transfer(pywr_solver, size): """Test a simple transfer model. """ model = Model() Scenario(model, name='test', size=size) supply1 = Input(model, name='supply-1', max_flow=5.0) wtw1 = Link(model, name='wtw-1') demand1 = Output(model, name='demand-1', max_flow=10.0, cost=-10.0) supply1.connect(wtw1) wtw1.connect(demand1) supply2 = Input(model, name='supply-2', max_flow=15.0) wtw2 = Link(model, name='wtw-2') demand2 = Output(model, name='demand-2', max_flow=10.0, cost=-10.0) supply2.connect(wtw2) wtw2.connect(demand2) transfer21 = Link(model, name='transfer-12', max_flow=2.0, cost=1.0) wtw2.connect(transfer21) transfer21.connect(wtw1) model.setup() model.step() assert_allclose(supply1.flow, [5.0] * size) assert_allclose(demand1.flow, [7.0] * size) assert_allclose(supply2.flow, [12.0] * size) assert_allclose(demand2.flow, [10.0] * size) assert_allclose(transfer21.flow, [2.0] * size)
def _make_weather_nodes(self, model, weather, cost): if not isinstance(self.area, Parameter): raise ValueError( 'Weather nodes can only be created if an area Parameter is given.' ) rainfall = weather['rainfall'].astype(np.float64) rainfall_param = MonthlyProfileParameter(model, rainfall) evaporation = weather['evaporation'].astype(np.float64) evaporation_param = MonthlyProfileParameter(model, evaporation) # Assume rainfall/evap is mm/day # Need to convert: # Mm2 -> m2 # mm/day -> m/day # m3/day -> Mm3/day # TODO allow this to be configured const = ConstantParameter(model, 1e6 * 1e-3 * 1e-6) # Create the flow parameters multiplying area by rate of rainfall/evap rainfall_flow_param = AggregatedParameter( model, [rainfall_param, const, self.area], agg_func='product') evaporation_flow_param = AggregatedParameter( model, [evaporation_param, const, self.area], agg_func='product') # Create the nodes to provide the flows rainfall_node = Input(model, '{}.rainfall'.format(self.name), parent=self) rainfall_node.max_flow = rainfall_flow_param rainfall_node.cost = cost evporation_node = Output(model, '{}.evaporation'.format(self.name), parent=self) evporation_node.max_flow = evaporation_flow_param evporation_node.cost = cost rainfall_node.connect(self) self.connect(evporation_node) self.rainfall_node = rainfall_node self.evaporation_node = evporation_node # Finally record these flows self.rainfall_recorder = NumpyArrayNodeRecorder( model, rainfall_node, name=f'__{rainfall_node.name}__:rainfall') self.evaporation_recorder = NumpyArrayNodeRecorder( model, evporation_node, name=f'__{evporation_node.name}__:evaporation')
def test_delay_param_load(): """Test that the `.load` method of `FlowDelayParameter` works correctly""" model = Model() model.timestepper.start = "2015/01/01" model.timestepper.end = "2015/01/31" catchment = Catchment(model, name="input", flow=1) output = Output(model, name="output") catchment.connect(output) data = {"name": "delay", "node": "input", "days": 2} param = FlowDelayParameter.load(model, data) assert param.days == 2 data = {"name": "delay2", "node": "input", "timesteps": 2} param2 = FlowDelayParameter.load(model, data) assert param2.timesteps == 2 expected = np.concatenate([np.zeros(2), np.ones(29)]).reshape(31, 1) AssertionRecorder(model, param, name="rec1", expected_data=expected) AssertionRecorder(model, param2, name="rec2", expected_data=expected) model.setup() model.run()
def test_virtual_storage_duplicate_route(): """ Test the VirtualStorage node """ model = pywr.core.Model() inpt = Input(model, "Input", max_flow=20) lnk = Link(model, "Link") inpt.connect(lnk) otpt = Output(model, "Output", max_flow=10, cost=-10.0) lnk.connect(otpt) vs = pywr.core.VirtualStorage(model, "Licence", [lnk, otpt], factors=[0.5, 1.0], initial_volume=10.0, max_volume=10.0) model.setup() assert_allclose(vs.volume, [10], atol=1e-7) model.step() assert_allclose(otpt.flow, [10 / 1.5], atol=1e-7) assert_allclose(vs.volume, [0], atol=1e-7) model.step() assert_allclose(otpt.flow, [0], atol=1e-7) assert_allclose(vs.volume, [0], atol=1e-7)
def test_initial_volume(self): """Test RollingVirtualStorage node behaviour when initial volume is less than max volume""" model = pywr.core.Model(timestep="1D") inpt = Input(model, "Input", max_flow=0.0) lnk = Link(model, "Link") inpt.connect(lnk) otpt = Output(model, "Output", max_flow=10, cost=-10.0) lnk.connect(otpt) vs = pywr.core.RollingVirtualStorage(model, "Licence", [lnk], timesteps=3, initial_volume=70.0, max_volume=100.0) model.setup() assert_allclose(vs.volume, [70.0], atol=1e-7) model.step() assert_allclose(otpt.flow, [0.0], atol=1e-7) assert_allclose(vs.volume, [85.0], atol=1e-7) model.step() assert_allclose(otpt.flow, [0.0], atol=1e-7) assert_allclose(vs.volume, [100.0], atol=1e-7) model.step() assert_allclose(otpt.flow, [0.0], atol=1e-7) assert_allclose(vs.volume, [100.0], atol=1e-7)
def test_virtual_storage(solver): """ Test the VirtualStorage node """ model = pywr.core.Model(solver=solver) inpt = Input(model, "Input", max_flow=20) lnk = Link(model, "Link") inpt.connect(lnk) otpt = Output(model, "Output", max_flow=10, cost=-10.0) lnk.connect(otpt) vs = pywr.core.VirtualStorage(model, "Licence", [lnk], initial_volume=10.0, max_volume=10.0) model.setup() assert_allclose(vs.volume, [10], atol=1e-7) model.step() assert_allclose(otpt.flow, [10], atol=1e-7) assert_allclose(vs.volume, [0], atol=1e-7) model.step() assert_allclose(otpt.flow, [0], atol=1e-7) assert_allclose(vs.volume, [0], atol=1e-7)
def test_transfer2(pywr_solver, size): """Test a simple transfer model. """ model = Model() Scenario(model, name='test', size=size) demands = [9.47, 7.65, 9.04, 9.56, 9.44] supply = [4.74, 6.59, 12.04, 8.81, 11.75] for i, (s, d) in enumerate(zip(supply, demands)): s = Input(model, name=f'supply-{i}', max_flow=s) lnk = Link(model, name=f'wtw-{i}') d = Output(model, name=f'demand-{i}', max_flow=d, cost=-10.0) s.connect(lnk) lnk.connect(d) transfer04 = Link(model, name='transfer-04', max_flow=15.36, cost=1.0) model.nodes['wtw-0'].connect(transfer04) transfer04.connect(model.nodes['wtw-4']) model.setup() model.step() expected_supply = [4.74, 6.59, 9.04, 8.81, 9.44] for i, expected in enumerate(expected_supply): assert_allclose(model.nodes[f'supply-{i}'].flow, [expected] * size) assert_allclose(transfer04.flow, [0.0] * size, atol=1e-8)
def test_delay_node(key, delay, initial_flow): """Test that the `DelayNode` and the `FlowDelayParameter` internal to it correctly delay node for a range of inputs and across scenarios""" model = Model() model.timestepper.start = "2015/01/01" model.timestepper.end = "2015/01/31" scen = Scenario(model, name="scenario", size=2) flow_vals = np.arange(1, 63).reshape((31, 2), order="F") flow = ArrayIndexedScenarioParameter(model, scen, flow_vals) catchment = Catchment(model, name="input", flow=flow) kwargs = {key: delay} if initial_flow: kwargs["initial_flow"] = initial_flow delaynode = DelayNode(model, name="delaynode", **kwargs) output = Output(model, name="output") catchment.connect(delaynode) delaynode.connect(output) rec = NumpyArrayNodeRecorder(model, output) model.run() if initial_flow: expected = np.concatenate( [np.full((delay, 2), initial_flow), flow_vals[:-delay, :]]) else: expected = np.concatenate( [np.zeros((delay, 2)), flow_vals[:-delay, :]]) assert_array_almost_equal(rec.data, expected)
def __init__(self, model, name, *args, **kwargs): demand = kwargs.pop('demand', ConstantParameter(model, 0)) supply = kwargs.pop('supply', ConstantParameter(model, 0)) headroom = kwargs.pop('headroom', ConstantParameter(model, 0)) super().__init__(model, name, *args, **kwargs) self.supply_node = Input(model, name=f'{name}-supply', parent=self) self.demand_node = Output(model, name=f'{name}-demand', parent=self) self.supply_node.max_flow = MaxParameter(model, supply) demand_headroom = AggregatedParameter(model, parameters=[demand, headroom], agg_func='sum') self.demand_node.max_flow = MaxParameter(model, demand_headroom) self.demand_node.cost = -100 self.supply_node.connect(self) self.connect(self.demand_node) # Track deficits deficit_param = DeficitParameter(model, self.demand_node, name=f'{name}-deficit_param') NumpyArrayParameterRecorder(model, deficit_param, name=f'__{name}-demand__:deficit', temporal_agg_func='sum')
def test_empty_storage_min_flow(): model = Model() storage = Storage(model, "storage", initial_volume=100, max_volume=100, num_inputs=1, num_outputs=0) otpt = Output(model, "output", min_flow=75) storage.connect(otpt) model.check() model.step() with pytest.raises(RuntimeError): model.step()
def _make_evaporation_node(self, model, evaporation, cost): if not isinstance(self.area, Parameter): log.warning( 'Evaporation nodes can only be created if an area Parameter is given.' ) return if evaporation is None: try: evaporation_param = load_parameter( model, f'__{self.name}__:evaporation') except KeyError: log.warning( f"Please speficy an evaporation or a weather on node {self.name}" ) return elif isinstance(evaporation, pd.DataFrame) or isinstance( evaporation, pd.Series): evaporation = evaporation.astype(np.float64) evaporation_param = MonthlyProfileParameter(model, evaporation) else: evaporation_param = evaporation evaporation_flow_param = AggregatedParameter( model, [evaporation_param, self.const, self.area], agg_func='product') evporation_node = Output(model, '{}.evaporation'.format(self.name), parent=self) evporation_node.max_flow = evaporation_flow_param evporation_node.cost = cost self.connect(evporation_node) self.evaporation_node = evporation_node self.evaporation_recorder = NumpyArrayNodeRecorder( model, evporation_node, name=f'__{evporation_node.name}__:evaporation')
def create_model(): # create a model model = Model(start="2016-01-01", end="2019-12-31", timestep=7) # create three nodes (an input, a link, and an output) A = Input(model, name="A", max_flow=10.0) B = Link(model, name="B", cost=10.0) C = Output(model, name="C", max_flow=5.0, cost=-20.0) # connect the nodes together A.connect(B) B.connect(C) return model
def test_run(self, from_json): """Test RollingVirtualStorage node behaviour.""" if from_json: model = load_model('rolling_virtual_storage.json') vs = model.nodes['Licence'] otpt = model.nodes['Output'] else: model = pywr.core.Model() inpt = Input(model, "Input", max_flow=20) lnk = Link(model, "Link") inpt.connect(lnk) otpt = Output(model, "Output", max_flow=15, cost=-10.0) lnk.connect(otpt) vs = pywr.core.RollingVirtualStorage(model, "Licence", [lnk], days=3, initial_volume=30.0, max_volume=30.0) model.setup() assert_allclose(vs.volume, [30], atol=1e-7) model.step() assert_allclose(otpt.flow, [15], atol=1e-7) assert_allclose(vs.volume, [15], atol=1e-7) model.step() assert_allclose(otpt.flow, [15], atol=1e-7) assert_allclose(vs.volume, [0], atol=1e-7) model.step() assert_allclose(otpt.flow, [0], atol=1e-7) # End of third day the flow from the first day is return to the licence assert_allclose(vs.volume, [15], atol=1e-7) model.step() assert_allclose(otpt.flow, [15], atol=1e-7) assert_allclose(vs.volume, [15], atol=1e-7)
def test_delay_failure(key, delay): """Test the FlowDelayParameter returns a ValueError when the input value of the `days` attribute is not divisible exactly by the model timestep delta and when the `timesteps` attribute is less than 1 """ model = Model() model.timestepper.start = "2015/01/01" model.timestepper.end = "2015/01/31" model.timestepper.delta = 3 catchment = Catchment(model, name="input", flow=1) output = Output(model, name="output") catchment.connect(output) FlowDelayParameter(model, catchment, **{key: delay}) with pytest.raises(ValueError): model.setup()
def test_run_weekly(self): """Test RollingVirtualStorage node behaviour with a weekly timestep.""" model = pywr.core.Model(timestep='7D') inpt = Input(model, "Input", max_flow=20) lnk = Link(model, "Link") inpt.connect(lnk) otpt = Output(model, "Output", max_flow=10, cost=-10.0) lnk.connect(otpt) vs = pywr.core.RollingVirtualStorage(model, "Licence", [lnk], timesteps=3, initial_volume=100.0, max_volume=100.0) model.setup() assert_allclose(vs.volume, [100.0], atol=1e-7) model.step() assert_allclose(otpt.flow, [10.0], atol=1e-7) assert_allclose(vs.volume, [30.0], atol=1e-7) model.step() assert_allclose(otpt.flow, [30.0 / 7], atol=1e-7) assert_allclose(vs.volume, [0], atol=1e-7) model.step() assert_allclose(otpt.flow, [0], atol=1e-7) # End of third day the flow from the first day is return to the licence assert_allclose(vs.volume, [70.0], atol=1e-7) model.step() assert_allclose(otpt.flow, [10.0], atol=1e-7) assert_allclose(vs.volume, [30.0], atol=1e-7)
def test_max_vol_param_zero_initial_vol(self): """Test RollingVirtualStorage node behaviour when initial volume is zero""" model = pywr.core.Model(timestep="1D") inpt = Input(model, "Input", max_flow=0.0) lnk = Link(model, "Link") inpt.connect(lnk) otpt = Output(model, "Output", max_flow=10, cost=-10.0) lnk.connect(otpt) mx_vol = ConstantParameter(model, 100) pywr.core.RollingVirtualStorage( model, "Licence", [lnk], timesteps=3, initial_volume=0.0, initial_volume_pc=0.0, max_volume=mx_vol, ) with pytest.raises(ValueError): model.setup()
def test_reservoir_circle(): """ Issue #140. A model with a circular route, from a reservoir Input back around to it's own Output. Demand ^ | Reservoir <- Pumping | ^ v | Compensation | | | v | Catchment -> River 1 -> River 2 ----> MRFA -> Waste | ^ |---> MRFB ----| """ model = Model() catchment = Input(model, "catchment", max_flow=500, min_flow=500) reservoir = Storage(model, "reservoir", max_volume=10000, initial_volume=5000) demand = Output(model, "demand", max_flow=50, cost=-100) pumping_station = Link(model, "pumping station", max_flow=100, cost=-10) river1 = Link(model, "river1") river2 = Link(model, "river2") compensation = Link(model, "compensation", cost=600) mrfA = Link(model, "mrfA", cost=-500, max_flow=50) mrfB = Link(model, "mrfB") waste = Output(model, "waste") catchment.connect(river1) river1.connect(river2) river2.connect(mrfA) river2.connect(mrfB) mrfA.connect(waste) mrfB.connect(waste) river2.connect(pumping_station) pumping_station.connect(reservoir) reservoir.connect(compensation) compensation.connect(river1) reservoir.connect(demand) model.check() model.setup() # not limited by mrf, pump capacity is constraint model.step() assert_allclose(catchment.flow, 500) assert_allclose(waste.flow, 400) assert_allclose(compensation.flow, 0) assert_allclose(pumping_station.flow, 100) assert_allclose(demand.flow, 50) # limited by mrf catchment.min_flow = catchment.max_flow = 100 model.step() assert_allclose(waste.flow, 50) assert_allclose(compensation.flow, 0) assert_allclose(pumping_station.flow, 50) assert_allclose(demand.flow, 50) # reservoir can support mrf, but doesn't need to compensation.cost = 200 model.step() assert_allclose(waste.flow, 50) assert_allclose(compensation.flow, 0) assert_allclose(pumping_station.flow, 50) assert_allclose(demand.flow, 50) # reservoir supporting mrf catchment.min_flow = catchment.max_flow = 0 model.step() assert_allclose(waste.flow, 50) assert_allclose(compensation.flow, 50) assert_allclose(pumping_station.flow, 0) assert_allclose(demand.flow, 50)