def test_dynamic_factors_load(model): model.timestepper.end = Timestamp("2016-01-03") A = Input(model, "A", max_flow=10.0) B = Input(model, "B", max_flow=10.0) Z = Output(model, "Z", cost=-10, max_flow=10.0) A.connect(Z) B.connect(Z) DailyProfileParameter(model, np.append(np.array([3, 4]), np.ones(364)), name="factor1") data = {"name": "agg", "factors": [1, "factor1"], "nodes": ["A", "B"]} AggregatedNode.load(data, model) model.step() assert_allclose(A.flow, 2.5) assert_allclose(B.flow, 7.5) model.step() assert_allclose(A.flow, 2) assert_allclose(B.flow, 8)
def test_aggregated_node_two_factors_time_varying(model): """Nodes constrained by a time-varying ratio between flows (2 nodes)""" model.timestepper.end = Timestamp("2016-01-03") A = Input(model, "A") B = Input(model, "B", max_flow=40.0) Z = Output(model, "Z", max_flow=100, cost=-10) agg = AggregatedNode(model, "agg", [A, B]) agg.factors = [0.5, 0.5] assert_allclose(agg.factors, [0.5, 0.5]) A.connect(Z) B.connect(Z) model.setup() model.step() assert_allclose(agg.flow, 80.0) assert_allclose(A.flow, 40.0) assert_allclose(B.flow, 40.0) agg.factors = [1.0, 2.0] model.step() assert_allclose(agg.flow, 60.0) assert_allclose(A.flow, 20.0) assert_allclose(B.flow, 40.0)
def test_parameter_array_indexed_scenario_monthly_factors_json(model): model.path = os.path.join(TEST_DIR, "models") scA = Scenario(model, 'Scenario A', size=2) scB = Scenario(model, 'Scenario B', size=3) p1 = ArrayIndexedScenarioMonthlyFactorsParameter.load(model, { "scenario": "Scenario A", "values": list(range(32)), "factors": [list(range(1, 13)),list(range(13, 25))], }) p2 = ArrayIndexedScenarioMonthlyFactorsParameter.load(model, { "scenario": "Scenario B", "values": { "url": "timeseries1.csv", "index_col": "Timestamp", "column": "Data", }, "factors": { "url": "monthly_profiles.csv", "index_col": "scenario", }, }) node1 = Input(model, "node1", max_flow=p1) node2 = Input(model, "node2", max_flow=p2) nodeN = Output(model, "nodeN", max_flow=None, cost=-1) node1.connect(nodeN) node2.connect(nodeN) model.timestepper.start = "2015-01-01" model.timestepper.end = "2015-01-31" model.run()
def test_dynamic_factors(model): model.timestepper.end = Timestamp("2016-01-03") A = Input(model, "A", max_flow=10.0) B = Input(model, "B", max_flow=10.0) C = Input(model, "C", max_flow=10.0) Z = Output(model, "Z", cost=-10) agg = AggregatedNode(model, "agg", [A, B, C]) agg.max_flow = 10.0 factor1 = DailyProfileParameter( model, np.append(np.array([0.8, 0.3]), np.ones(364))) factor2 = DailyProfileParameter( model, np.append(np.array([0.1, 0.3]), np.ones(364))) factor3 = DailyProfileParameter( model, np.append(np.array([0.1, 0.4]), np.ones(364))) agg.factors = [factor1, factor2, factor3] A.connect(Z) B.connect(Z) C.connect(Z) model.step() assert_allclose(A.flow, 8) assert_allclose(B.flow, 1) assert_allclose(C.flow, 1) model.step() assert_allclose(A.flow, 3) assert_allclose(B.flow, 3) assert_allclose(C.flow, 4)
def test_multipiecewise_constraint(model, flow): """Test using an aggregated node in combination with a MultiSplitLink. This test is the same as the `test_piecewise_constraint` but using the MultiSplitLink API for brevity. """ A = Input(model, "A", min_flow=flow, max_flow=flow) X = MultiSplitLink( model, name="X", nsteps=2, costs=[-500.0, 0], max_flows=[40.0, None], factors=[3, 1], extra_slots=1, slot_names=["river", "abstraction"], ) C = Output(model, "C") D = Output(model, "D", max_flow=50, cost=-100) A.connect(X) X.connect(C, from_slot="river") X.connect(D, from_slot="abstraction") model.step() assert_allclose(D.flow, min((flow - 40) * 0.25, 50.0))
def simple_storage_model(request, solver): """ Make a simple model with a single Input, Storage and Output. Input -> Storage -> Output """ model = pywr.core.Model(start=pandas.to_datetime('2016-01-01'), end=pandas.to_datetime('2016-01-05'), timestep=datetime.timedelta(1), solver=solver) inpt = Input(model, name="Input", max_flow=5.0, cost=-1) res = Storage(model, name="Storage", num_outputs=1, num_inputs=1, max_volume=20, initial_volume=10) otpt = Output(model, name="Output", max_flow=8, cost=-999) inpt.connect(res) res.connect(otpt) return model
def test_scenario_storage(): """Test the behaviour of Storage nodes with multiple scenarios The model defined has two inflow scenarios: 5 and 10. It is expected that the volume in the storage node should increase at different rates in the two scenarios. """ model = Model() i = Input(model, 'input', max_flow=999) s = Storage(model, 'storage', num_inputs=1, num_outputs=1, max_volume=1000, initial_volume=500) o = Output(model, 'output', max_flow=999) scenario_input = Scenario(model, 'Inflow', size=2) i.min_flow = ConstantScenarioParameter(model, scenario_input, [5.0, 10.0]) i.connect(s) s.connect(o) s_rec = NumpyArrayStorageRecorder(model, s) model.run() assert_allclose(i.flow, [5, 10]) assert_allclose(s_rec.data[0], [505, 510]) assert_allclose(s_rec.data[1], [510, 520])
def test_aggregated_storage_control_curve(three_storage_model): """Test using a control curve based on an aggregate storage, rather than a single storage. """ model = three_storage_model # create a new supply node inpt = Input(model, "Input 3", cost=-1000) inpt.connect(model.nodes["Output 0"]) inpt.connect(model.nodes["Output 1"]) inpt.connect(model.nodes["Output 2"]) # limit the flow of the new node using a control curve on the aggregate storage curves = [0.5] # 50% values = [0, 5] inpt.max_flow = ControlCurveParameter(model.nodes["Total Storage"], curves, values) # initial storage is > 50% so flow == 0 model.step() np.testing.assert_allclose(inpt.flow, 0.0) # set initial storage to < 50% storages = [node for node in model.nodes if isinstance(node, Storage)] for node, value in zip(storages, [0.6, 0.1, 0.1]): if isinstance(node, Storage): node.initial_volume = node.max_volume * value # now below the control curve, so flow is allowed model.reset() model.step() np.testing.assert_allclose(inpt.flow, 5.0)
def test_mean_flow_recorder(solver): model = Model(solver=solver) model.timestepper.start = pandas.to_datetime("2016-01-01") model.timestepper.end = pandas.to_datetime("2016-01-04") inpt = Input(model, "input") otpt = Output(model, "output") inpt.connect(otpt) rec_flow = NumpyArrayNodeRecorder(model, inpt) rec_mean = MeanFlowRecorder(model, node=inpt, timesteps=3) scenario = Scenario(model, "dummy", size=2) inpt.max_flow = inpt.min_flow = FunctionParameter(model, inpt, lambda model, t, si: 2 + t.index) model.run() expected = [ 2.0, (2.0 + 3.0) / 2, (2.0 + 3.0 + 4.0) / 3, (3.0 + 4.0 + 5.0) / 3, # zeroth day forgotten ] for value, expected_value in zip(rec_mean.data[:, 0], expected): assert_allclose(value, expected_value)
def test_aggregated_node_max_flow(model): """Nodes constrained by the max_flow of their AggregatedNode""" A = Input(model, "A", max_flow=20.0, cost=1) B = Input(model, "B", max_flow=20.0, cost=2) Z = Output(model, "Z", max_flow=100, cost=-10) A.connect(Z) B.connect(Z) agg = AggregatedNode(model, "agg", [A, B]) agg.max_flow = 30.0 model.run() assert_allclose(agg.flow, 30.0) assert_allclose(A.flow, 20.0) assert_allclose(B.flow, 10.0)
def test_aggregated_node_min_flow_parameter(model): """Nodes constrained by the min_flow of their AggregatedNode""" A = Input(model, "A", max_flow=20.0, cost=1) B = Input(model, "B", max_flow=20.0, cost=100) Z = Output(model, "Z", max_flow=100, cost=0) A.connect(Z) B.connect(Z) agg = AggregatedNode(model, "agg", [A, B]) agg.min_flow = ConstantParameter(model, 15.0) model.run() assert_allclose(agg.flow, 15.0) assert_allclose(A.flow, 15.0) assert_allclose(B.flow, 0.0)
def test_aggregated_node_max_flow_same_route(model): """Unusual case where the aggregated nodes are in the same route""" A = Input(model, "A", max_flow=20.0, cost=1) B = Input(model, "B", max_flow=20.0, cost=2) C = Input(model, "C", max_flow=50.0, cost=0) Z = Output(model, "Z", max_flow=100, cost=-10) A.connect(B) B.connect(Z) C.connect(Z) agg = AggregatedNode(model, "agg", [A, B]) agg.max_flow = 30.0 model.run() assert_allclose(agg.flow, 30.0) assert_allclose(A.flow + B.flow, 30.0)
def test_aggregated_node_two_factors(model): """Nodes constrained by a fixed ratio between flows (2 nodes)""" A = Input(model, "A") B = Input(model, "B", max_flow=40.0) Z = Output(model, "Z", max_flow=100, cost=-10) agg = AggregatedNode(model, "agg", [A, B]) agg.factors = [0.5, 0.5] assert_allclose(agg.factors, [0.5, 0.5]) A.connect(Z) B.connect(Z) model.run() assert_allclose(agg.flow, 80.0) assert_allclose(A.flow, 40.0) assert_allclose(B.flow, 40.0)
def test_aggregated_node_max_flow_with_weights(model, flow_weights, expected_agg_flow, expected_A_flow, expected_B_flow): """Nodes constrained by the weighted max_flow of their AggregatedNode""" A = Input(model, "A", max_flow=20.0, cost=1) B = Input(model, "B", max_flow=20.0, cost=8) Z = Output(model, "Z", max_flow=100, cost=-10) A.connect(Z) B.connect(Z) agg = AggregatedNode(model, "agg", [A, B]) agg.flow_weights = flow_weights agg.max_flow = 30.0 model.run() assert_allclose(agg.flow, expected_agg_flow) assert_allclose(A.flow, expected_A_flow) assert_allclose(B.flow, expected_B_flow)
def test_piecewise_constraint(model, flow): """Test using an aggregated node constraint in combination with a piecewise link in order to create a minimum flow constraint of the form y = mx + c, where y is the MRF, x is the upstream flow and m and c are constants. Flows are tested at 100, 200 and 300 to ensure the aggregated ratio works when there is too much to route entirely through to node 'D'. :: / -->-- X0 -->-- \ A -->-- Xo -->-- X1 -->-- Xi -->-- C \\ -->-- X2 -->-- / | Bo -->-- Bi --> D """ A = Input(model, "A", min_flow=flow, max_flow=flow) X = PiecewiseLink(model, name="X", nsteps=3, costs=[-500.0, 0, 0], max_flows=[40.0, None, None]) C = Output(model, "C") A.connect(X) X.connect(C) # create a new input inside the piecewise link which only has access # to flow travelling via the last sublink (X2) Bo = Output(model, "Bo", domain=X.sub_domain) Bi = Input(model, "Bi") D = Output(model, "D", max_flow=50, cost=-100) Bo.connect(Bi) Bi.connect(D) X.sublinks[-1].connect(Bo) agg = AggregatedNode(model, "agg", X.sublinks[1:]) agg.factors = [3.0, 1.0] model.step() assert_allclose(D.flow, min((flow - 40) * 0.25, 50.0))
def test_aggregated_node_two_factors(model): """Nodes constrained by a fixed ratio between flows (2 nodes)""" A = Input(model, "A") B = Input(model, "B", max_flow=40.0) Z = Output(model, "Z", max_flow=100, cost=-10) agg = AggregatedNode(model, "agg", [A, B]) agg.factors = [0.5, 0.5] for f in agg.factors: assert isinstance(f, ConstantParameter) assert_allclose(f.get_double_variables(), 0.5) A.connect(Z) B.connect(Z) model.run() assert_allclose(agg.flow, 80.0) assert_allclose(A.flow, 40.0) assert_allclose(B.flow, 40.0)
def test_aggregated_node_three_factors(model): """Nodes constrained by a fixed ratio between flows (3 nodes)""" A = Input(model, "A") B = Input(model, "B", max_flow=10.0) C = Input(model, "C") Z = Output(model, "Z", max_flow=100, cost=-10) agg = AggregatedNode(model, "agg", [A, B, C]) agg.factors = [0.5, 1.0, 2.0] assert_allclose(agg.factors, [0.5, 1.0, 2.0]) A.connect(Z) B.connect(Z) C.connect(Z) model.run() assert_allclose(agg.flow, 35.0) assert_allclose(A.flow, 5.0) assert_allclose(B.flow, 10.0) assert_allclose(C.flow, 20.0)
def test_mean_flow_recorder_days(solver): model = Model(solver=solver) model.timestepper.delta = 7 inpt = Input(model, "input") otpt = Output(model, "output") inpt.connect(otpt) rec_mean = MeanFlowRecorder(model, node=inpt, days=31) model.run() assert(rec_mean.timesteps == 4)
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=1.0) C = Output(model, name="C", max_flow=5.0, cost=-2.0) # connect nodes A.connect(B) B.connect(C) return model
def test_aggregated_node_three_factors(model): """Nodes constrained by a fixed ratio between flows (3 nodes)""" A = Input(model, "A") B = Input(model, "B", max_flow=10.0) C = Input(model, "C") Z = Output(model, "Z", max_flow=100, cost=-10) agg = AggregatedNode(model, "agg", [A, B, C]) agg.factors = [0.5, 1.0, 2.0] for f, v in zip(agg.factors, [0.5, 1.0, 2.0]): assert isinstance(f, ConstantParameter) assert_allclose(f.get_double_variables(), v) A.connect(Z) B.connect(Z) C.connect(Z) model.run() assert_allclose(agg.flow, 35.0) assert_allclose(A.flow, 5.0) assert_allclose(B.flow, 10.0) assert_allclose(C.flow, 20.0)
def simple_linear_model(request, solver): """ Make a simple model with a single Input and Output. Input -> Link -> Output """ model = Model(solver=solver) inpt = Input(model, name="Input") lnk = Link(model, name="Link", cost=1.0) inpt.connect(lnk) otpt = Output(model, name="Output") lnk.connect(otpt) return model
def three_storage_model(request): """ Make a simple model with three input, storage and output nodes. Also adds an `AggregatedStorage` and `AggregatedNode`. Input 0 -> Storage 0 -> Output 0 Input 1 -> Storage 1 -> Output 1 Input 2 -> Storage 2 -> Output 2 """ model = pywr.core.Model( start=pandas.to_datetime('2016-01-01'), end=pandas.to_datetime('2016-01-05'), timestep=datetime.timedelta(1), ) all_res = [] all_otpt = [] for num in range(3): inpt = Input(model, name="Input {}".format(num), max_flow=5.0 * num, cost=-1) res = Storage(model, name="Storage {}".format(num), num_outputs=1, num_inputs=1, max_volume=20, initial_volume=10 + num) otpt = Output(model, name="Output {}".format(num), max_flow=8 + num, cost=-999) inpt.connect(res) res.connect(otpt) all_res.append(res) all_otpt.append(otpt) AggregatedStorage(model, name='Total Storage', storage_nodes=all_res) AggregatedNode(model, name='Total Output', nodes=all_otpt) return model
def test_reset_timestepper_recorder(solver): model = Model( solver=solver, start=pandas.to_datetime('2016-01-01'), end=pandas.to_datetime('2016-01-01') ) inpt = Input(model, "input", max_flow=10) otpt = Output(model, "output", max_flow=50, cost=-10) inpt.connect(otpt) rec = NumpyArrayNodeRecorder(model, otpt) model.run() model.timestepper.end = pandas.to_datetime("2016-01-02") model.run()
def test_daily_profile_leap_day(model): """Test behaviour of daily profile parameter for leap years """ inpt = Input(model, "input") otpt = Output(model, "otpt", max_flow=None, cost=-999) inpt.connect(otpt) inpt.max_flow = DailyProfileParameter(model, np.arange(0, 366, dtype=np.float64)) # non-leap year model.timestepper.start = pd.to_datetime("2015-01-01") model.timestepper.end = pd.to_datetime("2015-12-31") model.run() assert_allclose(inpt.flow, 365) # NOT 364 # leap year model.timestepper.start = pd.to_datetime("2016-01-01") model.timestepper.end = pd.to_datetime("2016-12-31") model.run() assert_allclose(inpt.flow, 365)
def create_model(harmonic=True): # import flow timeseries for catchments flow = pd.read_csv(os.path.join('data', 'thames_stochastic_flow.gz')) flow['Date'] = flow['Date'].apply(pd.to_datetime) flow.set_index('Date', inplace=True) # resample input to weekly average flow = flow.resample('7D', how='mean') model = InspyredOptimisationModel( solver='glpk', start=flow.index[0], end=flow.index[365*10], # roughly 10 years timestep=datetime.timedelta(7), # weekly time-step ) flow_parameter = ArrayIndexedParameter(model, flow['flow'].values) catchment1 = Input(model, 'catchment1', min_flow=flow_parameter, max_flow=flow_parameter) catchment2 = Input(model, 'catchment2', min_flow=flow_parameter, max_flow=flow_parameter) reservoir1 = Storage(model, 'reservoir1', min_volume=3000, max_volume=20000, initial_volume=16000) reservoir2 = Storage(model, 'reservoir2', min_volume=3000, max_volume=20000, initial_volume=16000) if harmonic: control_curve = AnnualHarmonicSeriesParameter(model, 0.5, [0.5], [0.0], mean_upper_bounds=1.0, amplitude_upper_bounds=1.0) else: control_curve = MonthlyProfileParameter(model, np.array([0.0]*12), lower_bounds=0.0, upper_bounds=1.0) control_curve.is_variable = True controller = ControlCurveParameter(model, reservoir1, control_curve, [0.0, 10.0]) transfer = Link(model, 'transfer', max_flow=controller, cost=-500) demand1 = Output(model, 'demand1', max_flow=45.0, cost=-101) demand2 = Output(model, 'demand2', max_flow=20.0, cost=-100) river1 = Link(model, 'river1') river2 = Link(model, 'river2') # compensation flows from reservoirs compensation1 = Link(model, 'compensation1', max_flow=5.0, cost=-9999) compensation2 = Link(model, 'compensation2', max_flow=5.0, cost=-9998) terminator = Output(model, 'terminator', cost=1.0) catchment1.connect(reservoir1) catchment2.connect(reservoir2) reservoir1.connect(demand1) reservoir2.connect(demand2) reservoir2.connect(transfer) transfer.connect(reservoir1) reservoir1.connect(river1) reservoir2.connect(river2) river1.connect(terminator) river2.connect(terminator) reservoir1.connect(compensation1) reservoir2.connect(compensation2) compensation1.connect(terminator) compensation2.connect(terminator) r1 = TotalDeficitNodeRecorder(model, demand1) r2 = TotalDeficitNodeRecorder(model, demand2) r3 = AggregatedRecorder(model, [r1, r2], agg_func="mean") r3.is_objective = 'minimise' r4 = TotalFlowNodeRecorder(model, transfer) r4.is_objective = 'minimise' return model
def test_keating_aquifer(solver): model = Model( solver=solver, start=pandas.to_datetime('2016-01-01'), end=pandas.to_datetime('2016-01-01'), ) aqfer = KeatingAquifer( model, 'keating', num_streams, num_additional_inputs, stream_flow_levels, transmissivity, coefficient, levels, area=area, storativity=storativity, ) catchment = Input(model, 'catchment', max_flow=0) stream = Output(model, 'stream', max_flow=np.inf, cost=0) abstraction = Output(model, 'abstraction', max_flow=15, cost=-999) catchment.connect(aqfer) aqfer.connect(stream, from_slot=0) aqfer.connect(abstraction, from_slot=1) rec_level = NumpyArrayLevelRecorder(model, aqfer) rec_volume = NumpyArrayStorageRecorder(model, aqfer) rec_stream = NumpyArrayNodeRecorder(model, stream) rec_abstraction = NumpyArrayNodeRecorder(model, abstraction) model.check() assert(len(aqfer.inputs) == (num_streams + num_additional_inputs)) for initial_level in (50, 100, 110, 150): # set the inital aquifer level and therefor the initial volume aqfer.initial_level = initial_level initial_volume = aqfer.initial_volume assert(initial_volume == (area * storativity[0] * initial_level * 0.001)) # run the model (for one timestep only) model.run() # manually calculate keating streamflow and check model flows are OK Qp = 2 * transmissivity[0] * max(initial_level - stream_flow_levels[0][0], 0) * coefficient Qe = 2 * transmissivity[1] * max(initial_level - stream_flow_levels[0][1], 0) * coefficient delta_storage = initial_volume - rec_volume.data[0, 0] abs_flow = rec_abstraction.data[0, 0] stream_flow = rec_stream.data[0, 0] assert(delta_storage == (stream_flow + abs_flow)) assert(stream_flow == (Qp+Qe)) A_VERY_LARGE_NUMBER = 9999999999999 model.timestepper.end = pandas.to_datetime('2016-01-02') # fill the aquifer completely # there is no spill for the storage so it should find no feasible solution with pytest.raises(RuntimeError): catchment.max_flow = A_VERY_LARGE_NUMBER catchment.min_flow = A_VERY_LARGE_NUMBER model.run() # drain the aquifer completely catchment.min_flow = 0 catchment.max_flow = 0 abstraction.max_flow = A_VERY_LARGE_NUMBER model.run() assert(rec_volume.data[1, 0] == 0) abs_flow = rec_abstraction.data[1, 0] stream_flow = rec_stream.data[1, 0] assert(stream_flow == 0) assert(abs_flow == 0)
for node_id, node_trait in node_lookup_id.items(): types = node_trait['type'] name = node_trait['name'] if types in storage_types: num_outputs = node_trait['connect_in'] num_inputs = node_trait['connect_out'] storage[node_id] = Storage(model, name=name, num_outputs=num_outputs, num_inputs=num_inputs) elif types in output_types: non_storage_outputs[node_id] = Output(model, name=name) elif types in misc_types: non_storage_junctions[node_id] = Link(model, name=name) elif types in input_types: non_storage_inputs[node_id] = Input(model, name=name) else: raise Exception("Oops, missed a type!") non_storage = { **non_storage_inputs, **non_storage_outputs, **non_storage_junctions } # Generate dict of pywr network components pywr_components = { "inputs": non_storage_inputs, "outputs": non_storage_outputs, "junctions": non_storage_junctions, "storage": storage,
def simple_model(model): inpt = Input(model, "input") otpt = Output(model, "output", max_flow=20, cost=-1000) inpt.connect(otpt) return model