def __init__(self, *args, **kwargs): """ Keywords: control_curve - A Parameter object that can return the control curve position, as a percentage of fill, for the given timestep. """ control_curve = pop_kwarg_parameter(kwargs, 'control_curve', None) above_curve_cost = kwargs.pop('above_curve_cost', None) cost = kwargs.pop('cost', 0.0) if above_curve_cost is not None: if control_curve is None: # Make a default control curve at 100% capacity control_curve = ConstantParameter(1.0) elif not isinstance(control_curve, Parameter): # Assume parameter is some kind of constant and coerce to ConstantParameter control_curve = ConstantParameter(control_curve) if not isinstance(cost, Parameter): # In the case where an above_curve_cost is given and cost is not a Parameter # a default cost Parameter is created. kwargs['cost'] = ControlCurveParameter( self, control_curve, [above_curve_cost, cost]) else: raise ValueError( 'If an above_curve_cost is given cost must not be a Parameter.' ) else: # reinstate the given cost parameter to pass to the parent constructors kwargs['cost'] = cost super(Reservoir, self).__init__(*args, **kwargs)
def test_control_curve(solver): """ Use a simple model of a Reservoir to test that a control curve behaves as expected. The control curve should alter the cost of the Reservoir when it is above or below a particular threshold. (flow = 8.0) (max_flow = 10.0) Catchment -> River -> DemandCentre | ^ (max_flow = 2.0) v | (max_flow = 2.0) Reservoir """ in_flow = 8 model = pywr.core.Model(solver=solver) catchment = river.Catchment(model, name="Catchment", flow=in_flow) lnk = river.River(model, name="River") catchment.connect(lnk) demand = pywr.core.Output(model, name="Demand", cost=-10.0, max_flow=10) lnk.connect(demand) from pywr.parameters import ConstantParameter control_curve = ConstantParameter(0.8) reservoir = river.Reservoir(model, name="Reservoir", max_volume=10, cost=-20, above_curve_cost=0.0, control_curve=control_curve, initial_volume=10) reservoir.inputs[0].max_flow = 2.0 reservoir.outputs[0].max_flow = 2.0 lnk.connect(reservoir) reservoir.connect(demand) model.step() # Reservoir is currently above control curve. 2 should be taken from the # reservoir assert (reservoir.volume == 8) assert (demand.flow == 10) # Reservoir is still at (therefore above) control curve. So 2 is still taken model.step() assert (reservoir.volume == 6) assert (demand.flow == 10) # Reservoir now below curve. Better to retain volume and divert some of the # inflow model.step() assert (reservoir.volume == 8) assert (demand.flow == 6) # Set the above_curve_cost function to keep filling from pywr.parameters.control_curves import ControlCurveParameter # We know what we're doing with the control_curve Parameter so unset its parent before overriding # the cost parameter. reservoir.cost = ControlCurveParameter(reservoir, control_curve, [-20.0, -20.0]) model.step() assert (reservoir.volume == 10) assert (demand.flow == 6)
def test_with_nonstorage(self, model): """Test usage on non-`Storage` node.""" # Now test if the parameter is used on a non storage node m = model s = m.nodes["Storage"] l = Link(m, "Link") # Connect the link node to the network to create a valid model o = m.nodes["Output"] s.connect(l) l.connect(o) cc = ConstantParameter(model, 0.8) l.cost = ControlCurveParameter(model, s, cc, [10.0, 0.0]) @assert_rec(m, l.cost) def expected_func(timestep, scenario_index): v = s.initial_volume if v >= 80.0: expected = 10.0 else: expected = 0.0 return expected for initial_volume in (90, 70): s.initial_volume = initial_volume m.run()
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_with_values(self, model): """Test with `values` keyword argument""" m = model s = m.nodes['Storage'] # Return 10.0 when above 0.0 when below s.cost = ControlCurveParameter(m, s, [0.8, 0.6], [1.0, 0.7, 0.4]) self._assert_results(m, s)
def test_with_parameters(self, model): """ Test with `parameters` keyword argument. """ m = model s = m.nodes['Storage'] # Two different control curves cc = [ConstantParameter(model, 0.8), ConstantParameter(model, 0.6)] # Three different parameters to return params = [ ConstantParameter(model, 1.0), ConstantParameter(model, 0.7), ConstantParameter(model, 0.4) ] s.cost = ControlCurveParameter(model, s, cc, parameters=params) self._assert_results(m, s)
def test_with_nonstorage(self, model): """ Test usage on non-`Storage` node. """ # Now test if the parameter is used on a non storage node m = model m.scenarios.setup() s = Storage(m, 'Storage', max_volume=100.0) l = Link(m, 'Link') cc = ConstantParameter(0.8) l.cost = ControlCurveParameter(s, cc, [10.0, 0.0]) s.setup(m) # Init memory view on storage (bypasses usual `Model.setup`) print(s.volume) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) assert_allclose(l.get_cost(m.timestepper.current, si), 0.0) # When storage volume changes, the cost of the link changes. s.initial_volume = 90.0 m.reset() print(s.volume) assert_allclose(l.get_cost(m.timestepper.current, si), 10.0)
def test_with_nonstorage(self, model): """ Test usage on non-`Storage` node. """ # Now test if the parameter is used on a non storage node m = model s = m.nodes['Storage'] l = Link(m, 'Link') cc = ConstantParameter(model, 0.8) l.cost = ControlCurveParameter(model, s, cc, [10.0, 0.0]) @assert_rec(m, l.cost) def expected_func(timestep, scenario_index): v = s.initial_volume if v >= 80.0: expected = 10.0 else: expected = 0.0 return expected for initial_volume in (90, 70): s.initial_volume = initial_volume m.run()
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