Skip to content

Commit

Permalink
[ENG-1675] Add validation to check residual_capacity < capacity_gross…
Browse files Browse the repository at this point in the history
…_max (#144)

* Add validation to check residual_capacity < capacity_gross_max

* Move technology validation to model class so it's done post-composition
  • Loading branch information
edwardxtg authored Sep 12, 2024
1 parent d69dd51 commit fe38777
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 101 deletions.
56 changes: 56 additions & 0 deletions tests/test_construction/test_runspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,62 @@
),
],
),
residual_capacity_higher_than_capacity_gross_max=dict(
time_definition=dict(id="years-only", years=range(2020, 2051)),
regions=[dict(id="GB")],
commodities=[dict(id="COAL")],
impacts=[dict(id="CO2e")],
technologies=[
dict(
id="coal_powerplant",
operating_life=10,
capex=15,
opex_fixed=1.5,
residual_capacity=10,
capacity_gross_max=5,
operating_modes=[
dict(
id="mode_1",
input_activity_ratio={"COAL": 1},
emission_activity_ratio={"CO2e": 1},
)
],
),
dict(
id="min_coal",
operating_life=10,
operating_modes=[dict(id="mode_1", output_activity_ratio={"COAL": 1})],
),
],
),
activity_total_min_higher_than_activity_total_max=dict(
time_definition=dict(id="years-only", years=range(2020, 2051)),
regions=[dict(id="GB")],
commodities=[dict(id="COAL")],
impacts=[dict(id="CO2e")],
technologies=[
dict(
id="coal_powerplant",
operating_life=10,
capex=15,
opex_fixed=1.5,
activity_total_min=10,
activity_total_max=5,
operating_modes=[
dict(
id="mode_1",
input_activity_ratio={"COAL": 1},
emission_activity_ratio={"CO2e": 1},
)
],
),
dict(
id="min_coal",
operating_life=10,
operating_modes=[dict(id="mode_1", output_activity_ratio={"COAL": 1})],
),
],
),
)


Expand Down
5 changes: 5 additions & 0 deletions tz/osemosys/schemas/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
discount_rate_as_decimals,
reserve_margin_fully_defined,
)
from tz.osemosys.schemas.validation.technology_validation import validate_min_lt_max
from tz.osemosys.utils import merge, recursive_keys

# filter this pandas-3 dep warning for now
Expand Down Expand Up @@ -368,6 +369,10 @@ def composition_validation(self):
self = discount_rate_as_decimals(self)
if self.storage:
self = check_tech_linked_to_storage(self)

# Technology validation post composition (broadcasting)
validate_min_lt_max(self.technologies)

return self

@model_validator(mode="before")
Expand Down
41 changes: 0 additions & 41 deletions tz/osemosys/schemas/technology.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from tz.osemosys.defaults import defaults
from tz.osemosys.schemas.base import OSeMOSYSBase, OSeMOSYSData, cast_osemosysdata_value
from tz.osemosys.schemas.compat.technology import OtooleTechnology
from tz.osemosys.schemas.validation.validation_utils import check_min_vals_lower_max


class OperatingMode(OSeMOSYSBase):
Expand Down Expand Up @@ -356,43 +355,3 @@ def cast_values(cls, values: Any) -> Any:
values[field] = cast_osemosysdata_value(field_val, info)

return values

@model_validator(mode="after")
def validate_min_lt_max(self):
# # Broken for now
# if self.capacity_gross_min is not None and self.capacity_gross_max is not None:
# if not check_min_vals_lower_max(
# self.capacity_gross_min,
# self.capacity_gross_max,
# ["REGION", "YEAR", "VALUE"],
# ):
# raise ValueError(
# "Minimum gross capacity is not less than maximum gross capacity.")

if self.capacity_additional_min is not None and self.capacity_additional_max is not None:
if not check_min_vals_lower_max(
self.capacity_additional_min,
self.capacity_additional_max,
["REGION", "YEAR", "VALUE"],
):
raise ValueError("Minimum gross capacity is not less than maximum gross capacity.")

if self.activity_annual_min is not None and self.activity_annual_max is not None:
if not check_min_vals_lower_max(
self.activity_annual_min,
self.activity_annual_max,
["REGION", "YEAR", "VALUE"],
):
raise ValueError(
"Minimum annual activity is not less than maximum annual activity."
)

if self.activity_total_min is not None and self.activity_total_max is not None:
if not check_min_vals_lower_max(
self.activity_total_min,
self.activity_total_max,
["REGION", "VALUE"],
):
raise ValueError("Minimum total activity is not less than maximum total activity.")

return self
120 changes: 60 additions & 60 deletions tz/osemosys/schemas/validation/technology_validation.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,69 @@
from tz.osemosys.schemas.validation.validation_utils import check_min_vals_lower_max


def min_activity_lower_than_max(values):
"""
Check minimum activity constraints are lower than maximum activity constraints
"""
id = values.get("id")
activity_annual_max = values.get("activity_annual_max")
activity_annual_min = values.get("activity_annual_min")
activity_total_max = values.get("activity_total_max")
activity_total_min = values.get("activity_total_min")
def validate_min_lt_max(technologies):

if activity_annual_min is not None and activity_annual_max is not None:
check_min_vals_lower_max(
activity_annual_min,
activity_annual_max,
["REGION", "YEAR", "VALUE"],
(
f"Technology {id} values in activity_annual_min should be lower than "
"or equal to the corresponding values in activity_annual_max"
),
)
if activity_total_min is not None and activity_total_max is not None:
check_min_vals_lower_max(
activity_total_min,
activity_total_max,
["REGION", "VALUE"],
(
f"Technology {id} values in activity_total_min should be lower than "
"or equal to the corresponding values in activity_total_max"
),
)
for technology in technologies:

return values
if technology.capacity_gross_min is not None and technology.capacity_gross_max is not None:
if not check_min_vals_lower_max(
technology.capacity_gross_min,
technology.capacity_gross_max,
["REGION", "YEAR", "VALUE"],
):
raise ValueError(
f"Minimum gross capacity (capacity_gross_min) is not less than maximum gross "
f"capacity (capacity_gross_max) for technology '{technology.id}'."
)

if (
technology.capacity_additional_min is not None
and technology.capacity_additional_max is not None
):
if not check_min_vals_lower_max(
technology.capacity_additional_min,
technology.capacity_additional_max,
["REGION", "YEAR", "VALUE"],
):
raise ValueError(
f"Minimum additional capacity (capacity_additional_min) is not less than "
f"maximum additional capacity (capacity_additional_max) for technology "
f"'{technology.id}'."
)

def min_capacity_lower_than_max(values):
"""
Check minimum capacity constraints are lower than maximum capacity constraints
"""
id = values.get("id")
capacity_gross_max = values.get("capacity_gross_max")
capacity_gross_min = values.get("capacity_gross_min")
capacity_additional_max = values.get("capacity_additional_max")
capacity_additional_min = values.get("capacity_additional_min")
if (
technology.activity_annual_min is not None
and technology.activity_annual_max is not None
):
if not check_min_vals_lower_max(
technology.activity_annual_min,
technology.activity_annual_max,
["REGION", "YEAR", "VALUE"],
):
raise ValueError(
f"Minimum annual activity (activity_annual_min) is not less than maximum annual"
f" activity (activity_annual_max) for technology '{technology.id}'."
)

if capacity_additional_min is not None and capacity_additional_max is not None:
check_min_vals_lower_max(
capacity_additional_min,
capacity_additional_max,
["REGION", "YEAR", "VALUE"],
(
f"Technology {id} values in capacity_additional_min should be lower than "
"or equal to the corresponding values in capacity_additional_max"
),
)
if capacity_gross_min is not None and capacity_gross_max is not None:
check_min_vals_lower_max(
capacity_gross_min,
capacity_gross_max,
["REGION", "YEAR", "VALUE"],
(
f"Technology {id} values in capacity_gross_min should be lower than "
"or equal to the corresponding values in capacity_gross_max"
),
)
if technology.activity_total_min is not None and technology.activity_total_max is not None:
if not check_min_vals_lower_max(
technology.activity_total_min,
technology.activity_total_max,
["REGION", "VALUE"],
):
raise ValueError(
f"Minimum total activity (activity_total_min) is not less than maximum total "
f"activity (activity_total_max) for technology '{technology.id}'."
)

return values
if technology.residual_capacity is not None and technology.capacity_gross_max is not None:

if not check_min_vals_lower_max(
technology.residual_capacity,
technology.capacity_gross_max,
["REGION", "YEAR", "VALUE"],
):
raise ValueError(
f"Residual capacity is greater than the allowed total installed capacity "
f"defined in capacity_gross_max for technology '{technology.id}'."
)

0 comments on commit fe38777

Please sign in to comment.