def _assert_results(self, model, s, p, scale=1.0): # Test correct aggregation is performed s.setup(model) # Init memory view on storage (bypasses usual `Model.setup`) s.initial_volume = 90.0 model.reset() # Set initial volume on storage ts = next(model.timestepper) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for mth in range(1, 13): ts = Timestep(datetime.datetime(2016, mth, 1), 366, 1.0) np.testing.assert_allclose(p.value(ts, si), 1.0*scale) s.initial_volume = 70.0 model.reset() # Set initial volume on storage ts = next(model.timestepper) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for mth in range(1, 13): ts = Timestep(datetime.datetime(2016, mth, 1), 366, 1.0) np.testing.assert_allclose(p.value(ts, si), 0.7 * (mth - 1)*scale) s.initial_volume = 30.0 model.reset() # Set initial volume on storage ts = next(model.timestepper) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for mth in range(1, 13): ts = Timestep(datetime.datetime(2016, mth, 1), 366, 1.0) np.testing.assert_allclose(p.value(ts, si), 0.3*scale)
def test_scaled_profile_nested_load(model): """ Test `ScaledProfileParameter` loading with `AggregatedParameter` """ s = Storage(model, 'Storage', max_volume=100.0) l = Link(model, 'Link') data = { 'type': 'scaledprofile', 'scale': 50.0, 'profile': { 'type': 'aggregated', 'agg_func': 'product', 'parameters': [{ 'type': 'monthlyprofile', 'values': [0.5] * 12 }, { 'type': 'monthlyprofilecontrolcurve', 'control_curves': [0.8, 0.6], 'values': [[1.0] * 12, [0.7] * np.arange(12), [0.3] * 12], 'storage_node': 'Storage' }] } } l.max_flow = p = load_parameter(model, data) p.setup(model) # Test correct aggregation is performed model.scenarios.setup() s.setup( model) # Init memory view on storage (bypasses usual `Model.setup`) s.initial_volume = 90.0 model.reset() # Set initial volume on storage si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for mth in range(1, 13): ts = Timestep(datetime.datetime(2016, mth, 1), 366, 1.0) np.testing.assert_allclose(p.value(ts, si), 50.0 * 0.5 * 1.0) s.initial_volume = 70.0 model.reset() # Set initial volume on storage si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for mth in range(1, 13): ts = Timestep(datetime.datetime(2016, mth, 1), 366, 1.0) np.testing.assert_allclose(p.value(ts, si), 50.0 * 0.5 * 0.7 * (mth - 1)) s.initial_volume = 30.0 model.reset() # Set initial volume on storage si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for mth in range(1, 13): ts = Timestep(datetime.datetime(2016, mth, 1), 366, 1.0) np.testing.assert_allclose(p.value(ts, si), 50.0 * 0.5 * 0.3)
def test_single_cc_load(self, model): """ Test load from dict with 'control_curve' key This is different to the above test by using singular 'control_curve' key in the dict """ m = model m.scenarios.setup() s = Storage(m, 'Storage', max_volume=100.0) data = { "type": "controlcurve", "storage_node": "Storage", "control_curve": 0.8, } s.cost = p = load_parameter(model, data) assert isinstance(p, ControlCurveParameter) s.setup(m) # Init memory view on storage (bypasses usual `Model.setup`) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) s.initial_volume = 90.0 m.reset() assert_allclose(s.get_cost(m.timestepper.current, si), 0) s.initial_volume = 70.0 m.reset() assert_allclose(s.get_cost(m.timestepper.current, si), 1)
def test_with_nonstorage_load(self, model): """ Test load from dict with 'storage_node' key. """ m = model m.scenarios.setup() s = Storage(m, 'Storage', max_volume=100.0) l = Link(m, 'Link') data = { "type": "controlcurve", "control_curve": 0.8, "values": [10.0, 0.0], "storage_node": "Storage" } l.cost = p = load_parameter(model, data) assert isinstance(p, ControlCurveParameter) s.setup(m) # Init memory view on storage (bypasses usual `Model.setup`) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) print(s.volume) 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() assert_allclose(l.get_cost(m.timestepper.current, si), 10.0)
def test_control_curve_interpolated(model): m = model m.scenarios.setup() si = ScenarioIndex(0, np.array([0], dtype=np.int32)) s = Storage(m, 'Storage', max_volume=100.0) cc = ConstantParameter(0.8) values = [20.0, 5.0, 0.0] s.cost = ControlCurveInterpolatedParameter(s, cc, values) s.setup(m) for v in (0.0, 10.0, 50.0, 80.0, 90.0, 100.0): s.initial_volume = v s.reset() assert_allclose(s.get_cost(m.timestepper.current, si), np.interp(v/100.0, [0.0, 0.8, 1.0], values[::-1])) # special case when control curve is 100% cc.update(np.array([1.0,])) s.initial_volume == 100.0 s.reset() assert_allclose(s.get_cost(m.timestepper.current, si), values[1]) # special case when control curve is 0% cc.update(np.array([0.0,])) s.initial_volume == 0.0 s.reset() assert_allclose(s.get_cost(m.timestepper.current, si), values[0])
def test_variable(self, model): """ Test that variable updating works. """ p1 = AnnualHarmonicSeriesParameter(model, 0.5, [0.25], [np.pi / 4], is_variable=True) assert p1.double_size == 3 assert p1.integer_size == 0 new_var = np.array([0.6, 0.1, np.pi / 2]) p1.set_double_variables(new_var) np.testing.assert_allclose(p1.get_double_variables(), new_var) with pytest.raises(NotImplementedError): p1.set_integer_variables(np.arange(3, dtype=np.int32)) with pytest.raises(NotImplementedError): p1.get_integer_variables() si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for ts in model.timestepper: doy = (ts.datetime.dayofyear - 1) / 365 np.testing.assert_allclose( p1.value(ts, si), 0.6 + 0.1 * np.cos(doy * 2 * np.pi + np.pi / 2))
def test_target_json(self): """ Test loading a HydropowerTargetParameter from JSON. """ model = load_model("hydropower_target_example.json") si = ScenarioIndex(0, np.array([0], dtype=np.int32)) # 30 time-steps are run such that the head gets so flow to hit the max_flow # constraint. The first few time-steps are also bound by the min_flow constraint. for i in range(30): model.step() rec = model.recorders["turbine1_energy"] param = model.parameters["turbine1_discharge"] turbine1 = model.nodes["turbine1"] assert turbine1.flow[0] > 0 if np.allclose(turbine1.flow[0], 500.0): # If flow is bounded by min_flow then more HP is produced. assert rec.data[i, 0] > param.target.get_value(si) elif np.allclose(turbine1.flow[0], 1000.0): # If flow is bounded by max_flow then less HP is produced. assert rec.data[i, 0] < param.target.get_value(si) else: # If flow is within the bounds target is met exactly. assert_allclose(rec.data[i, 0], param.target.get_value(si))
def test_threshold_parameter(model): class DummyRecorder(Recorder): def __init__(self, *args, **kwargs): super(DummyRecorder, self).__init__(*args, **kwargs) self.data = np.array([[0.0]], dtype=np.float64) rec = DummyRecorder(model) timestep = Timestep(datetime.datetime(2016, 1, 2), 1, 1) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) threshold = 10.0 values = [50.0, 60.0] expected = [ ("LT", (1, 0, 0)), ("GT", (0, 0, 1)), ("EQ", (0, 1, 0)), ("LE", (1, 1, 0)), ("GE", (0, 1, 1)), ] for predicate, (value_lt, value_eq, value_gt) in expected: param = RecorderThresholdParameter(rec, threshold, values, predicate) rec.data[...] = threshold - 5 # data is below threshold assert_allclose(param.value(timestep, si), values[value_lt]) assert (param.index(timestep, si) == value_lt) rec.data[...] = threshold # data is at threshold assert_allclose(param.value(timestep, si), values[value_eq]) assert (param.index(timestep, si) == value_eq) rec.data[...] = threshold + 5 # data is above threshold assert_allclose(param.value(timestep, si), values[value_gt]) assert (param.index(timestep, si) == value_gt)
def test_json_load(self, solver): model = load_model("demand_saving.json", solver=solver) storage = model.nodes["supply1"] demand = model.nodes["demand1"] assert (isinstance(demand.max_flow, MonthlyProfileControlCurveParameter)) model.setup() profile = np.array([1.0, 1.0, 1.0, 1.0, 1.2, 1.2, 1.2, 1.2, 1.0, 1.0, 1.0, 1.0]) * 10.0 saving = np.array([ [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], [0.8, 0.8, 0.8, 0.8, 0.7, 0.7, 0.7, 0.7, 0.8, 0.8, 0.8, 0.8] ]) scenario_index = ScenarioIndex(0, np.array([], dtype=np.int32)) for i in range(12): model.step() # First two timesteps should result in storage above 50% control curve # Therefore no demand saving if i < 2: expected_max_flow = profile[i] * saving[0, i] else: expected_max_flow = profile[i] * saving[1, i] value = demand.max_flow.value(model.timestepper.current, scenario_index) assert_allclose(value, expected_max_flow)
def test_parameter_array_indexed_scenario_monthly_factors(simple_linear_model): """ Test ArrayIndexedParameterScenarioMonthlyFactors """ model = simple_linear_model # Baseline timeseries data values = np.arange(len(model.timestepper), dtype=np.float64) # Add two scenarios scA = Scenario(model, 'Scenario A', size=2) scB = Scenario(model, 'Scenario B', size=5) # Random factors for each Scenario B value per month factors = np.random.rand(scB.size, 12) p = ArrayIndexedScenarioMonthlyFactorsParameter(model, scB, values, factors) model.setup() # Iterate in time for v, ts in zip(values, model.timestepper): imth = ts.datetime.month - 1 # Now ensure the appropriate value is returned for the Scenario B indices. for i, (a, b) in enumerate( itertools.product(range(scA.size), range(scB.size))): f = factors[b, imth] si = ScenarioIndex(i, np.array([a, b], dtype=np.int32)) np.testing.assert_allclose(p.value(ts, si), v * f)
def test_parameter_threshold_parameter(self, simple_linear_model, threshold): """ Test ParameterThresholdParameter """ m = simple_linear_model m.nodes['Input'].max_flow = 10.0 m.nodes['Output'].cost = -10.0 data = { "type": "parameterthreshold", "parameter": { "type": "constant", "value": 3.0 }, "threshold": threshold, "predicate": "<" } p1 = load_parameter(m, data) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) m.setup() m.step() # value < 5 assert p1.index(m.timestepper.current, si) == 1 p1.param.update(np.array([ 8.0, ])) m.setup() m.step() # flow < 5 assert p1.index(m.timestepper.current, si) == 0
def test_parameter_df_upsampling_multiple_columns(model): """ Test that the `DataFrameParameter` works with multiple columns that map to a `Scenario` """ scA = Scenario(model, 'A', size=20) scB = Scenario(model, 'B', size=2) # scenario indices (not used for this test) # Use a 7 day timestep for this test and run 2015 model.timestepper.delta = datetime.timedelta(7) model.timestepper.start = pd.to_datetime('2015-01-01') model.timestepper.end = pd.to_datetime('2015-12-31') # Daily time-step index = pd.date_range('2015-01-01', periods=365, freq='D') df = pd.DataFrame(np.random.rand(365, 20), index=index) p = DataFrameParameter(model, df, scenario=scA) p.setup() A = df.resample('7D', axis=0).mean() for v, ts in zip(A.values, model.timestepper): np.testing.assert_allclose([ p.value(ts, ScenarioIndex(i, np.array([i], dtype=np.int32))) for i in range(20) ], v) p = DataFrameParameter(model, df, scenario=scB) with pytest.raises(ValueError): p.setup()
def test_double_harmonic(self, model): p1 = AnnualHarmonicSeriesParameter(model, 0.5, [0.25, 0.3], [np.pi/4, np.pi/3]) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for ts in model.timestepper: doy = (ts.datetime.dayofyear - 1) /365 expected = 0.5 + 0.25*np.cos(doy*2*np.pi + np.pi / 4) + 0.3*np.cos(doy*4*np.pi + np.pi/3) np.testing.assert_allclose(p1.value(ts, si), expected)
def test_daily_license(): '''Test daily licence''' si = ScenarioIndex(0, np.array([0], dtype=np.int32)) lic = TimestepLicense(None, 42.0) assert(isinstance(lic, License)) assert(lic.value(Timestep(datetime(2015, 1, 1), 0, 0), si) == 42.0) # daily licences don't have resource state assert(lic.resource_state(Timestep(datetime(2015, 1, 1), 0, 0)) is None)
def test_single_harmonic(self, model): p1 = AnnualHarmonicSeriesParameter(0.5, [0.25], [np.pi / 4]) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for ts in model.timestepper: doy = (ts.datetime.dayofyear - 1) / 365 np.testing.assert_allclose( p1.value(ts, si), 0.5 + 0.25 * np.cos(doy * 2 * np.pi + np.pi / 4))
def test_daily_license(simple_linear_model): '''Test daily licence''' m = simple_linear_model si = ScenarioIndex(0, np.array([0], dtype=np.int32)) lic = TimestepLicense(m, None, 42.0) assert (isinstance(lic, License)) assert (lic.value(Timestep(pandas.Period('2015-1-1'), 0, 1), si) == 42.0) # daily licences don't have resource state assert (lic.resource_state(Timestep(pandas.Period('2015-1-1'), 0, 1)) is None)
def test_daily_profile_control_curve(model): """ Test `DailyProfileControlCurveParameter` """ s = Storage(model, 'Storage', max_volume=100.0) l = Link(model, 'Link') data = { 'type': 'dailyprofilecontrolcurve', 'control_curves': [0.8, 0.6], 'values': [[1.0]*366, [0.7]*np.arange(366), [0.3]*366], 'storage_node': 'Storage' } l.max_flow = p = load_parameter(model, data) p.setup(model) # Test correct aggregation is performed model.scenarios.setup() s.setup(model) # Init memory view on storage (bypasses usual `Model.setup`) s.initial_volume = 90.0 model.reset() # Set initial volume on storage si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for mth in range(1, 13): ts = Timestep(datetime.datetime(2016, mth, 1), 366, 1.0) np.testing.assert_allclose(p.value(ts, si), 1.0) s.initial_volume = 70.0 model.reset() # Set initial volume on storage si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for mth in range(1, 13): ts = Timestep(datetime.datetime(2016, mth, 1), 366, 1.0) doy = ts.datetime.dayofyear np.testing.assert_allclose(p.value(ts, si), 0.7*(doy - 1)) s.initial_volume = 30.0 model.reset() # Set initial volume on storage si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for mth in range(1, 13): ts = Timestep(datetime.datetime(2016, mth, 1), 366, 1.0) np.testing.assert_allclose(p.value(ts, si), 0.3)
def test_parameter_df_upsampling(model): """ Test that the `DataFrameParameter` can upsample data from a `pandas.DataFrame` and return that correctly """ # scenario indices (not used for this test) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) # Use a 7 day timestep for this test and run 2015 model.timestepper.delta = datetime.timedelta(7) model.timestepper.start = pd.to_datetime('2015-01-01') model.timestepper.end = pd.to_datetime('2015-12-31') # Daily time-step index = pd.date_range('2015-01-01', periods=365, freq='D') series = pd.Series(np.arange(365), index=index) p = DataFrameParameter(model, series) p.setup() A = series.resample('7D').mean() for v, ts in zip(A, model.timestepper): np.testing.assert_allclose(p.value(ts, si), v) model.reset() # Daily time-step that requires aligning index = pd.date_range('2014-12-31', periods=366, freq='D') series = pd.Series(np.arange(366), index=index) p = DataFrameParameter(model, series) p.setup() # offset the resample appropriately for the test A = series[1:].resample('7D').mean() for v, ts in zip(A, model.timestepper): np.testing.assert_allclose(p.value(ts, si), v) model.reset() # Daily time-step that is not covering the require range index = pd.date_range('2015-02-01', periods=365, freq='D') series = pd.Series(np.arange(365), index=index) p = DataFrameParameter(model, series) with pytest.raises(ValueError): p.setup() model.reset() # Daily time-step that is not covering the require range index = pd.date_range('2014-11-01', periods=365, freq='D') series = pd.Series(np.arange(365), index=index) p = DataFrameParameter(model, series) with pytest.raises(ValueError): p.setup()
def test_init(self, model): scenario = Scenario(model, 'A', 10) values = np.random.rand(10, 12) p = ScenarioMonthlyProfileParameter(scenario, values) p.setup(model) # Iterate in time for ts in model.timestepper: imth = ts.datetime.month - 1 for i in range(scenario.size): si = ScenarioIndex(i, np.array([i], dtype=np.int32)) np.testing.assert_allclose(p.value(ts, si), values[i, imth])
def test_parameter_monthly_profile(model): """ Test MonthlyProfileParameter """ values = np.arange(12, dtype=np.float64) p = MonthlyProfileParameter(values) p.setup(model) # Iterate in time for ts in model.timestepper: imth = ts.datetime.month - 1 si = ScenarioIndex(0, np.array([0], dtype=np.int32)) np.testing.assert_allclose(p.value(ts, si), values[imth])
def test_constant_from_shared_df(): """ Test that a shared dataframe can be used to provide data to ConstantParameter (single values). """ model = load_model('simple_df_shared.json') assert isinstance(model.nodes['demand1'].max_flow, ConstantParameter) assert isinstance(model.nodes['demand1'].cost, ConstantParameter) ts = model.timestepper.next() si = ScenarioIndex(0, np.array([0], dtype=np.int32)) np.testing.assert_allclose(model.nodes['demand1'].max_flow.value(ts, si), 10.0) np.testing.assert_allclose(model.nodes['demand1'].cost.value(ts, si), -10.0)
def test_load(self, model): data = { "type": "annualharmonicseries", "mean": 0.5, "amplitudes": [0.25], "phases": [np.pi/4] } p1 = load_parameter(model, data) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for ts in model.timestepper: doy = (ts.datetime.dayofyear - 1) / 365 np.testing.assert_allclose(p1.value(ts, si), 0.5 + 0.25 * np.cos(doy * 2 * np.pi + np.pi / 4))
def test_parameter_array_indexed(model): """ Test ArrayIndexedParameter """ A = np.arange(len(model.timestepper), dtype=np.float64) p = ArrayIndexedParameter(A) p.setup(model) # scenario indices (not used for this test) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) for v, ts in zip(A, model.timestepper): np.testing.assert_allclose(p.value(ts, si), v) # Now check that IndexError is raised if an out of bounds Timestep is given. ts = Timestep(datetime.datetime(2016, 1, 1), 366, 1.0) with pytest.raises(IndexError): p.value(ts, si)
def test_parameter_constant_scenario(simple_linear_model): """ Test ConstantScenarioParameter """ model = simple_linear_model # Add two scenarios scA = Scenario(model, 'Scenario A', size=2) scB = Scenario(model, 'Scenario B', size=5) p = ConstantScenarioParameter(model, scB, np.arange(scB.size, dtype=np.float64)) model.setup() ts = model.timestepper.current # Now ensure the appropriate value is returned for the Scenario B indices. for i, (a, b) in enumerate(itertools.product(range(scA.size), range(scB.size))): si = ScenarioIndex(i, np.array([a, b], dtype=np.int32)) np.testing.assert_allclose(p.value(ts, si), float(b))
def test_parameter_daily_profile(simple_linear_model): """ Test DailyProfileParameter """ model = simple_linear_model values = np.arange(366, dtype=np.float64) p = DailyProfileParameter(model, values) model.setup() # Iterate in time for ts in model.timestepper: month = ts.datetime.month day = ts.datetime.day iday = int((datetime.datetime(2016, month, day) - datetime.datetime(2016, 1, 1)).days) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) np.testing.assert_allclose(p.value(ts, si), values[iday])
def test_basic_use(self, simple_linear_model): """ Test the basic use of `ConstantParameter` using the Python API """ model = simple_linear_model # Add two scenarios scA = Scenario(model, 'Scenario A', size=2) scB = Scenario(model, 'Scenario B', size=5) p = ConstantParameter(model, np.pi, name='pi', comment='Mmmmm Pi!') assert not p.is_variable assert p.double_size == 1 assert p.integer_size == 0 model.setup() ts = model.timestepper.current # Now ensure the appropriate value is returned for all scenarios for i, (a, b) in enumerate(itertools.product(range(scA.size), range(scB.size))): si = ScenarioIndex(i, np.array([a, b], dtype=np.int32)) np.testing.assert_allclose(p.value(ts, si), np.pi)
def _assert_results(m, s): """ Correct results for the following tests """ m.scenarios.setup() s.setup(m) # Init memory view on storage (bypasses usual `Model.setup`) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) s.initial_volume = 90.0 m.reset() ts = next(m.timestepper) assert_allclose(s.get_cost(m.timestepper.current, si), 1.0) s.initial_volume = 70.0 m.reset() ts = next(m.timestepper) assert_allclose(s.get_cost(m.timestepper.current, si), 0.7) s.initial_volume = 40.0 m.reset() ts = next(m.timestepper) assert_allclose(s.get_cost(m.timestepper.current, si), 0.4)
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_control_curve_interpolated_json(solver): # this is a little hack-y, as the parameters don't provide access to their # data once they've been initalised model = load_model("reservoir_with_cc.json", solver=solver) reservoir1 = model.nodes["reservoir1"] model.setup() ts = next(model.timestepper) si = ScenarioIndex(0, np.array([0], dtype=np.int32)) path = os.path.join(os.path.dirname(__file__), "models", "control_curve.csv") control_curve = pd.read_csv(path)["Control Curve"].values max_volume = reservoir1.max_volume values = [-8, -6, -4] for n in range(0, 10): # calculate expected cost manually and compare to parameter output volume = reservoir1._volume[si.global_id] volume_factor = reservoir1._current_pc[si.global_id] cc = control_curve[model.timestepper.current.index] expected_cost = np.interp(volume_factor, [0.0, cc, 1.0], values[::-1]) cost = reservoir1.get_cost(model.timestepper.current, si) assert_allclose(expected_cost, cost) model.step()
def test_simple_model_with_exponential_license(simple_linear_model): m = simple_linear_model si = ScenarioIndex(0, np.array([0], dtype=np.int32)) annual_total = 365 # Expoential licence with max_value of e should give a hard constraint of 1.0 when on track lic = AnnualExponentialLicense(m, m.nodes["Input"], annual_total, np.e) # Apply licence to the model m.nodes["Input"].max_flow = lic m.nodes["Output"].max_flow = 10.0 m.nodes["Output"].cost = -10.0 m.setup() m.step() # Licence is a hard constraint of 1.0 # timestepper.current is now end of the first day assert_allclose(m.nodes["Output"].flow, 1.0) # Check the constraint for the next timestep. assert_allclose(lic.value(m.timestepper._next, si), 1.0) # Now constrain the demand so that licence is not fully used m.nodes["Output"].max_flow = 0.5 m.step() assert_allclose(m.nodes["Output"].flow, 0.5) # Check the constraint for the next timestep. The available amount should now be larger # due to the reduced use remaining = (annual_total - 1.5) assert_allclose(lic.value(m.timestepper._next, si), np.exp(-remaining / (365 - 2) + 1)) # Unconstrain the demand m.nodes["Output"].max_flow = 10.0 m.step() assert_allclose(m.nodes["Output"].flow, np.exp(-remaining / (365 - 2) + 1)) # Licence should now be on track for an expected value of 1.0 remaining -= np.exp(-remaining / (365 - 2) + 1) assert_allclose(lic.value(m.timestepper._next, si), np.exp(-remaining / (365 - 3) + 1))