From 46efbee8f490622bec5620150dabb332a2eef05f Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Tue, 11 Mar 2025 08:50:26 +0000 Subject: [PATCH 1/4] Exclude capacity constraint if parameters are not specified --- docs/inputs/technodata.rst | 2 +- src/muse/constraints.py | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/inputs/technodata.rst b/docs/inputs/technodata.rst index 8bb51c386..eca549317 100644 --- a/docs/inputs/technodata.rst +++ b/docs/inputs/technodata.rst @@ -81,7 +81,7 @@ var_par, var_exp ProductionRef is the production of a reference capacity (CapRef) for the cost estimate decided by the modeller before filling the input data files. -Growith constraints +Growith constraints (optional) MaxCapacityAddition represents the maximum addition of installed capacity per technology, per year in a period, per region. diff --git a/src/muse/constraints.py b/src/muse/constraints.py index ff297175f..72194e44b 100644 --- a/src/muse/constraints.py +++ b/src/muse/constraints.py @@ -275,7 +275,7 @@ def max_capacity_expansion( search_space: xr.DataArray, technologies: xr.Dataset, **kwargs, -) -> Constraint: +) -> Constraint | None: r"""Max-capacity addition, max-capacity growth, and capacity limits constraints. Limits by how much the capacity of each technology owned by an agent can grow in @@ -311,6 +311,17 @@ def max_capacity_expansion( \Gamma_t^{r, i} \geq 0 """ + # If all three parameters are missing, don't apply the constraint + if not any( + param in technologies + for param in ( + "max_capacity_addition", + "total_capacity_limit", + "max_capacity_growth", + ) + ): + return None + # case with technology and region in asset dimension if capacity.region.dims != (): names = [u for u in capacity.asset.coords if capacity[u].dims == ("asset",)] @@ -344,14 +355,14 @@ def max_capacity_expansion( # Max capacity addition constraint time_frame = int(capacity.year[1] - capacity.year[0]) - add_cap = techs.max_capacity_addition * time_frame + add_cap = techs.get("max_capacity_addition", np.inf) * time_frame # Total capacity limit constraint - limit = techs.total_capacity_limit + limit = techs.get("total_capacity_limit", np.inf) total_cap = (limit - forecasted).clip(min=0) # Max capacity growth constraint - max_growth = techs.max_capacity_growth + max_growth = techs.get("max_capacity_growth", np.inf) growth_cap = initial * (max_growth + 1) ** time_frame - forecasted # Relax growth constraint if no existing capacity From 32e47d8d747071df620e8193a11ab406e27dd726 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Tue, 11 Mar 2025 09:53:42 +0000 Subject: [PATCH 2/4] Better documentation --- docs/inputs/technodata.rst | 2 ++ src/muse/constraints.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/docs/inputs/technodata.rst b/docs/inputs/technodata.rst index eca549317..768bacc5f 100644 --- a/docs/inputs/technodata.rst +++ b/docs/inputs/technodata.rst @@ -107,6 +107,8 @@ Growith constraints (optional) Growth constraints are applied for each single agent in a multi-agent simulation. When only one agent is present, the growth constraints apply individually to the "New" and "Retrofit" agent, when present. + If any of the three parameters are not provided in the technodata file, that particular constraint is not applied. + TechnicalLife represents the number of years that a technology operates before it is decommissioned. diff --git a/src/muse/constraints.py b/src/muse/constraints.py index 72194e44b..460d95916 100644 --- a/src/muse/constraints.py +++ b/src/muse/constraints.py @@ -310,6 +310,10 @@ def max_capacity_expansion( .. math:: \Gamma_t^{r, i} \geq 0 + + :math:`L_t^r(y)`, :math:`G_t^r(y)` and :math:`W_t^r(y)` default to np.inf if + not provided (i.e. no constraint). + If all three parameters are not provided, no constraint is applied (returns None). """ # If all three parameters are missing, don't apply the constraint if not any( From 16b86bae8bbc34d8baa5c3806c036fba7f25da61 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Wed, 12 Mar 2025 19:28:09 +0000 Subject: [PATCH 3/4] Add test --- tests/test_constraints.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 571f56351..82c5df0c3 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -481,6 +481,24 @@ def test_max_capacity_expansion(max_capacity_expansion): ).all() +def test_max_capacity_expansion_no_limits( + market_demand, capacity, search_space, technologies +): + from muse.constraints import max_capacity_expansion + + # Without growth limits, the constraint should return None + techs = technologies.drop_vars( + ["max_capacity_addition", "max_capacity_growth", "total_capacity_limit"] + ) + result = max_capacity_expansion( + market_demand, + capacity, + search_space, + techs, + ) + assert result is None + + def test_max_production(max_production): dims = {"replacement", "asset", "commodity", "timeslice"} assert set(max_production.capacity.dims) == dims From 1377384d3676e1bf388d80cc66abad393d71a0c0 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Wed, 12 Mar 2025 19:36:00 +0000 Subject: [PATCH 4/4] Error message for inf growth constraints --- src/muse/constraints.py | 9 +++++++++ tests/test_constraints.py | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/muse/constraints.py b/src/muse/constraints.py index 460d95916..64c5591c5 100644 --- a/src/muse/constraints.py +++ b/src/muse/constraints.py @@ -375,6 +375,15 @@ def max_capacity_expansion( # Take the most restrictive constraint b = np.minimum(np.minimum(add_cap, total_cap), growth_cap) + # np.inf values are not allowed in the final constraint - raise error + # Will happen if user provides "inf" for all three parameters for any technology + if np.isinf(b).any(): + inf_replacements = b.replacement[np.isinf(b)].values + raise ValueError( + "Capacity growth constraint cannot be infinite. " + f"Check growth constraint parameters for technologies: {inf_replacements}" + ) + if b.region.dims == (): capa = 1 elif "dst_region" in b.dims: diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 82c5df0c3..67b4f45b2 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -3,7 +3,7 @@ import numpy as np import pandas as pd import xarray as xr -from pytest import approx, fixture +from pytest import approx, fixture, raises from muse.timeslices import drop_timeslice from muse.utilities import interpolate_capacity, reduce_assets @@ -499,6 +499,19 @@ def test_max_capacity_expansion_no_limits( assert result is None +def test_max_capacity_expansion_infinite_limits( + market_demand, capacity, search_space, technologies +): + from muse.constraints import max_capacity_expansion + + # If all limits are infinite, a ValueError should be raised + technologies["max_capacity_addition"] = np.inf + technologies["max_capacity_growth"] = np.inf + technologies["total_capacity_limit"] = np.inf + with raises(ValueError): + max_capacity_expansion(market_demand, capacity, search_space, technologies) + + def test_max_production(max_production): dims = {"replacement", "asset", "commodity", "timeslice"} assert set(max_production.capacity.dims) == dims