diff --git a/tests/test_solve/test_solve.py b/tests/test_solve/test_solve.py index 35370386..e203a504 100644 --- a/tests/test_solve/test_solve.py +++ b/tests/test_solve/test_solve.py @@ -177,3 +177,57 @@ def test_simple_trade(): assert model.solution["NetTrade"].values[0][2][0] == 15 assert np.round(model._m.objective.value) == 28200.0 + + +def test_simple_re_target(): + """ + This model has 2 generators, solar and coal, with identical parameters except for solar having + double the capex and is tagged as renewable using the param include_in_joint_renewable_target. + + A 20% renewable target is set. + """ + model = Model( + id="test-feasibility", + renewable_production_target=0.2, + time_definition=dict(id="years-only", years=range(2020, 2031)), + regions=[dict(id="single-region")], + commodities=[ + dict(id="electricity", demand_annual=25, include_in_joint_renewable_target=True) + ], + impacts=[], + technologies=[ + dict( + id="coal-gen", + operating_life=2, + capex=400, + operating_modes=[ + dict( + id="generation", + opex_variable=5, + output_activity_ratio={"electricity": 1}, + ) + ], + ), + dict( + id="solar-gen", + operating_life=2, + capex=800, + include_in_joint_renewable_target=True, + operating_modes=[ + dict( + id="generation", + opex_variable=5, + output_activity_ratio={"electricity": 1}, + ) + ], + ), + ], + ) + + model._build() + + model._m.solve() + + assert model._m.termination_condition == "optimal" + assert np.round(model._m.objective.value) == 54671.0 + assert model._m.solution["NewCapacity"][0][1][0] == 5 # i.e. solar new capacity diff --git a/tz/osemosys/model/constraints/__init__.py b/tz/osemosys/model/constraints/__init__.py index 1d237069..c0a78e11 100644 --- a/tz/osemosys/model/constraints/__init__.py +++ b/tz/osemosys/model/constraints/__init__.py @@ -43,7 +43,8 @@ def add_constraints(ds: xr.Dataset, m: Model, lex: Dict[str, LinearExpression]) m = add_emissions_constraints(ds, m, lex) m = add_annual_activity_constraints(ds, m, lex) m = add_new_capacity_constraints(ds, m, lex) - m = add_re_targets_constraints(ds, m, lex) + if ds["REMinProductionTarget"].notnull().any(): + m = add_re_targets_constraints(ds, m, lex) m = add_reserve_margin_constraints(ds, m, lex) m = add_storage_constraints(ds, m, lex) m = add_total_activity_constraints(ds, m, lex) diff --git a/tz/osemosys/model/constraints/re_targets.py b/tz/osemosys/model/constraints/re_targets.py index e8fb2379..0bb1edbd 100644 --- a/tz/osemosys/model/constraints/re_targets.py +++ b/tz/osemosys/model/constraints/re_targets.py @@ -54,6 +54,12 @@ def add_re_targets_constraints(ds: xr.Dataset, m: Model, lex: Dict[str, LinearEx ``` """ - # TODO + con = ( + lex["ProductionAnnualRE"] + >= lex["ProductionAnnual"].assign_coords({"FUEL": ds["RETagFuel"] == 1}) + * ds["REMinProductionTarget"] + ) + mask = ds["RETagFuel"] == 1 + m.add_constraints(con, name="RE1_RenewableProduction_MinConstraint", mask=mask) return m diff --git a/tz/osemosys/model/linear_expressions/__init__.py b/tz/osemosys/model/linear_expressions/__init__.py index 26fc2781..4c527303 100644 --- a/tz/osemosys/model/linear_expressions/__init__.py +++ b/tz/osemosys/model/linear_expressions/__init__.py @@ -9,6 +9,7 @@ from tz.osemosys.model.linear_expressions.emissions import add_lex_emissions from tz.osemosys.model.linear_expressions.financials import add_lex_financials from tz.osemosys.model.linear_expressions.production import add_lex_quantities +from tz.osemosys.model.linear_expressions.re_production import add_lex_re_production from tz.osemosys.model.linear_expressions.reserve_margin import add_lex_reserve_margin from tz.osemosys.model.linear_expressions.storage import add_lex_storage from tz.osemosys.model.linear_expressions.trade import add_lex_trade @@ -29,5 +30,7 @@ def add_linear_expressions(ds: xr.Dataset, m: Model) -> Dict[str, LinearExpressi add_lex_financials(ds, m, lex) add_lex_quantities(ds, m, lex) add_lex_reserve_margin(ds, m, lex) + if ds["REMinProductionTarget"].notnull().any(): + add_lex_re_production(ds, m, lex) return lex diff --git a/tz/osemosys/model/linear_expressions/re_production.py b/tz/osemosys/model/linear_expressions/re_production.py new file mode 100644 index 00000000..51790b82 --- /dev/null +++ b/tz/osemosys/model/linear_expressions/re_production.py @@ -0,0 +1,29 @@ +from typing import Dict + +import xarray as xr +from linopy import LinearExpression, Model + + +def add_lex_re_production(ds: xr.Dataset, m: Model, lex: Dict[str, LinearExpression]): + + RateOfProductionByTechnologyByModeRE = m["RateOfActivity"] * ds["OutputActivityRatio"].where( + ds["OutputActivityRatio"].notnull() & (ds["RETagTechnology"] == 1) + ) + RateOfProductionByTechnologyRE = RateOfProductionByTechnologyByModeRE.where( + ds["OutputActivityRatio"].sum("MODE_OF_OPERATION") != 0 + ).sum(dims="MODE_OF_OPERATION") + RateOfProductionRE = RateOfProductionByTechnologyRE.sum(dims="TECHNOLOGY") + ProductionByTechnologyRE = RateOfProductionByTechnologyRE * ds["YearSplit"] + ProductionRE = RateOfProductionRE * ds["YearSplit"] + ProductionAnnualRE = ProductionRE.sum(dims="TIMESLICE") + + lex.update( + { + "RateOfProductionByTechnologyByModeRE": RateOfProductionByTechnologyByModeRE, + "RateOfProductionByTechnologyRE": RateOfProductionByTechnologyRE, + "RateOfProductionRE": RateOfProductionRE, + "ProductionRE": ProductionRE, + "ProductionByTechnologyRE": ProductionByTechnologyRE, + "ProductionAnnualRE": ProductionAnnualRE, + } + )