diff --git a/docs/source/database.rst b/docs/source/database.rst index 3f6e7c317..4f4072717 100644 --- a/docs/source/database.rst +++ b/docs/source/database.rst @@ -110,18 +110,26 @@ recommend that you populate input tables in the following order: Group Region and Technology Constraints --------------------------------------- -Some constraint tables support summation over groups of regions or technologies. Note that each row in these tables will still only create one constraint, but that constraint will be a summation over the defined group. For example, the ``limit_capacity`` table will limit the total summed capacity of all technologies in the technology group (if used) and over all the regions in the region group (if used). Consider behaviour carefully. For example, the ``limit_annual_capacity_factor`` table will constrain the total summed capacity factor of the group, which would allow for varying capacity factors of processes within that group as long as the limit is met in aggregate. +Some constraint tables support summation over groups of regions or technologies. +Note that each row in these tables will still only create one constraint, but +that constraint will be a summation over the defined group. For example, the +``limit_capacity`` table will limit the total summed capacity of all technologies +in the technology group (if used) and over all the regions in the region group (if used). +Consider behaviour carefully. For example, the ``limit_annual_capacity_factor`` table will +constrain the total summed capacity factor of the group, which would allow for varying +capacity factors of processes within that group as long as the limit is met in aggregate. **Group Regions:** For the supported tables, the ``region`` column can be populated with either a single region (e.g., ``"east"``), a subset of regions delineated with a ``+`` (e.g., ``"east+west"``), or ``"global"`` to indicate summation over all model regions. .. important:: - When grouped or global region constraints are used with exchange technologies, - exchange flows are only counted if the relevant exchange-technology keys are + When ``+`` delineated region summations are used with exchange technologies, + exchange flows are only counted if the relevant exchange-technology region pairs are explicitly included in the region string used by the constraint row. - Exchange-technology keys are directional and use a hyphen to separate the - two regions (e.g., ``east-west`` and ``west-east`` are distinct keys). + Exchange-technology regions are directional and use a hyphen to separate the + two regions (e.g., ``east-west`` and ``west-east`` are distinct keys). ``"global"`` + *does* include all exchange flows by default. For example, to constrain activity across the ``east`` and ``west`` regions including all exchange flows between them, the region string should be: @@ -132,7 +140,12 @@ For the supported tables, the ``region`` column can be populated with either a s If exchange keys are omitted (e.g., using only ``east+west``), flows through exchange technologies between those regions will *not* be included in the - constrained summation. + constraint summation. + + The only exception to this rule is reserve margin constraints, + :code:`planning_reserve_margin` and :code:`operating_reserve_margin`, + which automatically include all exchange flows into and out of the region + or region group due to their specific logic requiring this behaviour. Supported tables: @@ -146,18 +159,17 @@ Supported tables: * limit_capacity_share * limit_new_capacity_share * limit_emission -* limit_growth_capacity -* limit_growth_new_capacity -* limit_growth_new_capacity_delta -* limit_degrowth_capacity -* limit_degrowth_new_capacity -* limit_degrowth_new_capacity_delta +* planning_reserve_margin +* operating_reserve_margin **Technology Groups:** -For the supported tables, the following columns accept either technologies or technology groups, over which the constraint is summed. Technology groups are defined in the ``tech_group`` and ``tech_group_member`` tables. +For the supported tables, the following columns accept either technologies or +technology groups, over which the constraint is summed. Technology groups are +defined in the ``tech_group`` and ``tech_group_member`` tables. + +Technology group columns: -* tech * tech_or_group * sub_group * super_group @@ -173,18 +185,14 @@ Supported tables: * limit_activity_share * limit_capacity_share * limit_new_capacity_share -* limit_growth_capacity -* limit_growth_new_capacity -* limit_growth_new_capacity_delta -* limit_degrowth_capacity -* limit_degrowth_new_capacity -* limit_degrowth_new_capacity_delta +* planning_reserve_margin +* operating_reserve_margin For help getting started, consider using the ``temoa tutorial`` command to generate a template project or inspect the example SQL file at ``temoa/tutorial_assets/utopia.sql``. To begin building your own database file, use -``temoa/db_schema/temoa_schema_v4.sql``, which is a database file with the requisite +``temoa/db_schema/temoa_schema_v4_1.sql``, which is a database file with the requisite structure but no data added. We recommend leaving the database structure intact, and simply adding data to the schema file, or constructing an empty database from the schema file and then using a script or database editor to import data. diff --git a/docs/source/database_schema.mmd b/docs/source/database_schema.mmd index 84f580141..421a3ba6f 100644 --- a/docs/source/database_schema.mmd +++ b/docs/source/database_schema.mmd @@ -1,12 +1,29 @@ erDiagram -capacity_credit { - INTEGER period PK +planning_reserve_credit { TEXT region PK TEXT tech PK - INTEGER vintage PK REAL credit TEXT notes } +operating_reserve_derate { + TEXT region PK + TEXT season PK + TEXT tech PK + REAL factor + TEXT notes +} +planning_reserve_margin { + TEXT region PK + TEXT tech_or_group PK + REAL margin + TEXT notes +} +operating_reserve_margin { + TEXT region PK + TEXT tech_or_group PK + REAL margin + TEXT notes +} technology { TEXT tech PK INTEGER annual @@ -16,7 +33,6 @@ technology { INTEGER exchange TEXT flag INTEGER flex - INTEGER reserve INTEGER retire INTEGER seas_stor TEXT sector @@ -539,11 +555,6 @@ output_storage_level { REAL level TEXT sector } -planning_reserve_margin { - TEXT region PK - REAL margin - TEXT notes -} ramp_down_hourly { TEXT region PK TEXT tech PK @@ -556,22 +567,6 @@ ramp_up_hourly { TEXT notes REAL rate } -reserve_capacity_derate { - INTEGER period PK - TEXT region PK - TEXT season PK - TEXT tech PK - INTEGER vintage PK - REAL factor - TEXT notes -} -rps_requirement { - TEXT notes - INTEGER period - TEXT region - REAL requirement - TEXT tech_group -} tech_group { TEXT group_name PK TEXT notes @@ -621,8 +616,11 @@ time_segment_fraction { TEXT notes REAL segment_fraction } -technology one or zero--0+ capacity_credit : has -time_period one or zero--0+ capacity_credit : has +region one or zero--0+ planning_reserve_credit : has +technology one or zero--0+ planning_reserve_credit : has +region one or zero--0+ operating_reserve_derate : has +technology one or zero--0+ operating_reserve_derate : has +time_season one or zero--0+ operating_reserve_derate : has technology_type 1--0+ technology : has time_period_type one or zero--0+ time_period : has technology one or zero--0+ capacity_factor_process : has @@ -698,15 +696,9 @@ operator 1--0+ limit_capacity : has time_period one or zero--0+ limit_capacity : has time_period one or zero--0+ limit_capacity_share : has operator 1--0+ limit_capacity_share : has -operator 1--0+ limit_degrowth_capacity : has -operator 1--0+ limit_degrowth_new_capacity : has -operator 1--0+ limit_degrowth_new_capacity_delta : has commodity one or zero--0+ limit_emission : has operator 1--0+ limit_emission : has time_period one or zero--0+ limit_emission : has -operator 1--0+ limit_growth_capacity : has -operator 1--0+ limit_growth_new_capacity : has -operator 1--0+ limit_growth_new_capacity_delta : has time_period one or zero--0+ limit_new_capacity : has operator 1--0+ limit_new_capacity : has operator 1--0+ limit_new_capacity_share : has @@ -797,15 +789,8 @@ sector_label one or zero--0+ output_storage_level : has time_period one or zero--0+ output_storage_level : has season_label one or zero--0+ output_storage_level : has time_of_day one or zero--0+ output_storage_level : has -region one or zero--1 planning_reserve_margin : has technology one or zero--0+ ramp_down_hourly : has technology one or zero--0+ ramp_up_hourly : has -technology one or zero--0+ reserve_capacity_derate : has -time_period one or zero--0+ reserve_capacity_derate : has -season_label one or zero--0+ reserve_capacity_derate : has -time_period 1--0+ rps_requirement : has -tech_group 1--0+ rps_requirement : has -region 1--0+ rps_requirement : has tech_group one or zero--0+ tech_group_member : has technology one or zero--0+ tech_group_member : has time_period one or zero--0+ time_season : has diff --git a/docs/source/mathematical_formulation.rst b/docs/source/mathematical_formulation.rst index 8bbd7a21e..0dc22926a 100644 --- a/docs/source/mathematical_formulation.rst +++ b/docs/source/mathematical_formulation.rst @@ -338,19 +338,8 @@ efficiency values. If not specified for a given process, it defaults to 1, meaning the base :code:`efficiency` value applies uniformly. Note that there is no period index: the time-varying efficiency applies to all periods. - .. _capacity_factor_tech: -capacity_credit -~~~~~~~~~~~~~~~ - -:math:`{CC}_{r \in R, p \in P, t \in T, v \in V}` - -The capacity credit represents the fraction of total installed capacity of -a process that can be relied upon during the time slice in which peak -electricity demand occurs. This parameter is used in the 'static' version of -the :math:`reserve_margin` constraint. - capacity_factor_tech ~~~~~~~~~~~~~~~~~~~~ @@ -822,13 +811,51 @@ level to vary. planning_reserve_margin ~~~~~~~~~~~~~~~~~~~~~~~ -:math:`{PRM}_{r \in R}` +:math:`{PRM}_{r \in R, t \in T}` + +The required excess of credited installed capacity above demand, expressed +as a fraction of demand, keyed by a region-or-group :math:`r` and a +technology-or-group :math:`t`. For example, a value of 0.2 requires that +credited capacity be at least 120% of demand. Demand is estimated from production +by time slice. Any exchange region (e.g. ``r1-r2``) where exactly one endpoint +connects to the region or group is automatically included in the +reserve calculation; this auto-inclusion is unique to the reserve margin constraints. + + +planning_reserve_credit +~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{PRC}_{r \in R, t \in T}` + +The fraction of a technology's installed capacity that can be reliably counted +toward the reserve margin. A firm, fully dispatchable process (e.g. a gas turbine) +typically receives a credit near 1, while a weather-dependent process (e.g. wind +or solar) receives a lower value. + + +operating_reserve_margin +~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{ORM}_{r \in R, t \in T}` + +The dynamic counterpart to :code:`planning_reserve_margin`, indexed the same +way by region-or-group and technology-or-group. Rather than crediting +installed capacity, it requires that available (derated) generation in each +time slice exceed the region-group's proxy demand by this margin. +Any exchange region (e.g. ``r1-r2``) where exactly one endpoint +connects to the region or group is automatically included in the +reserve calculation; this auto-inclusion is unique to the reserve margin constraints. + + +operating_reserve_derate +~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{ORD}_{r \in R, s \in S, t \in T}` -The :code:`planning_reserve_margin` parameter specifies the capacity reserve margin -in the electric sector by region. The capacity reserve margin represents the -installed generating capacity — expressed as a share of peak load — that must be -available in reserve to meet contingencies. Temoa estimates peak demand from electricity -production by time slice. +The fraction of a technology's available output that can be depended upon in a +given season. A value less than 1 reflects the fact that not all of a process's +capacity is reliably available — for example, due to scheduled maintenance or +seasonal resource constraints. Defaults to 1. ramp_down_hourly @@ -851,18 +878,6 @@ by which a technology can ramp output up per hour. This is used in the :code:`ramp_up_constraint`. -reserve_capacity_derate -~~~~~~~~~~~~~~~~~~~~~~~ - -:math:`{RCD}_{r \in R, s \in S, t \in T^{res}, v \in V}` - -The :code:`reserve_capacity_derate` parameter allows the modeler to derate -the capacity of a reserve technology in specific seasons — for example, to -account for seasonal availability. Values default to 1 (no derate). This -parameter is used in the 'dynamic' version of the :code:`reserve_margin` -constraint. - - .. _segment_fraction: segment_fraction @@ -1313,9 +1328,11 @@ various physical and operational real-world phenomena. .. autofunction:: temoa.components.operations.ramp_down_constraint -.. autofunction:: temoa.components.reserves.reserve_margin_static +.. autofunction:: temoa.components.reserves.planning_reserve_margin_constraint + +.. autofunction:: temoa.components.reserves.operating_reserve_margin_constraint -.. autofunction:: temoa.components.reserves.reserve_margin_dynamic +.. autofunction:: temoa.components.reserves.reserve_margin_proxy_demand .. autofunction:: temoa.components.emissions.linked_emissions_tech_constraint diff --git a/docs/source/param_desc_and_tables.rst b/docs/source/param_desc_and_tables.rst index aef95320e..57cdea17b 100644 --- a/docs/source/param_desc_and_tables.rst +++ b/docs/source/param_desc_and_tables.rst @@ -24,12 +24,13 @@ characteristics**. :widths: 15, 20, 25, 40 ":math:`\text{C2A}_{r,t}`", ":code:`capacity_to_activity`", ":code:`capacity_to_activity`", "converts from capacity to activity units" - ":math:`\text{CC}_{r,p,t,v}`", ":code:`capacity_credit`", ":code:`capacity_credit`", "process-specific capacity credit used in the static reserve margin constraint" + ":math:`\text{PRC}_{r,t}`", ":code:`planning_reserve_credit`", ":code:`planning_reserve_credit`", "fraction of installed capacity that can be relied upon (default 0)" ":math:`\text{CFT}_{r,s,d,t}`", ":code:`capacity_factor_tech`", ":code:`capacity_factor_tech`", "technology-specific capacity factor" ":math:`\text{CFP}_{r,s,d,t,v}`", ":code:`capacity_factor_process`", ":code:`capacity_factor_process`", "process-specific capacity factor; allows capacity factor to change with technology vintage" ":math:`\text{ECAP}_{r,t,v}`", ":code:`existing_capacity`", ":code:`existing_capacity`", "installed capacity that exists prior to first model time period" - ":math:`\text{PRM}_{r}`", ":code:`planning_reserve_margin`", ":code:`planning_reserve_margin`", "planning reserve margin used to ensure sufficient generating capacity" - ":math:`\text{RCD}_{r,s,t,v}`", ":code:`reserve_capacity_derate`", ":code:`reserve_capacity_derate`", "capacity derate factor for dynamic reserve margin constraint" + ":math:`\text{PRM}_{r_g,t_g}`", ":code:`planning_reserve_margin`", ":code:`planning_reserve_margin`", "required excess of credited capacity above demand in each time slice, as a fraction of demand" + ":math:`\text{ORM}_{r_g,t_g}`", ":code:`operating_reserve_margin`", ":code:`operating_reserve_margin`", "required excess of available (derated) output above demand in each time slice, as a fraction of that demand" + ":math:`\text{ORD}_{r,s,t}`", ":code:`operating_reserve_derate`", ":code:`operating_reserve_derate`", "fraction of available output that can be relied upon in a given season (default 1)" ":math:`\text{RUH}_{r,t}`", ":code:`ramp_up_hourly`", ":code:`ramp_up_hourly`", "hourly rate at which generation techs can ramp output up" ":math:`\text{RDH}_{r,t}`", ":code:`ramp_down_hourly`", ":code:`ramp_down_hourly`", "hourly rate at which generation techs can ramp output down" @@ -125,7 +126,6 @@ Parameters in the table below relate to the specification of **policy**. :header: "Parameter", "Database Table", "Model Element", "Notes" :widths: 15, 20, 25, 40 - "", ":code:`rps_requirement`", ":code:`renewable_portfolio_standard`", "**[Deprecated]** RPS requirements; use :code:`limit_activity_share` instead" ":math:`\text{LIT}_{r,t,e,t'}`", ":code:`linked_tech`", ":code:`linked_techs`", "dummy techs used to convert CO2 emissions to physical commodity" Parameters in the table below relate to the specification of **construction and diff --git a/docs/source/set_desc_and_tables.rst b/docs/source/set_desc_and_tables.rst index 29818d487..150eb2596 100644 --- a/docs/source/set_desc_and_tables.rst +++ b/docs/source/set_desc_and_tables.rst @@ -51,14 +51,13 @@ number of **technology subsets**. ":math:`\text{T}^b`", ":code:`technology, flag = pb`", ":code:`tech_baseload`", "baseload electric generators, which have constant output across intraday time segments (:math:`{T}^b \subset T`)" ":math:`\text{T}^s`", ":code:`technology, flag = ps`", ":code:`tech_storage`", "all storage technologies (:math:`{T}^s \subset T`)" ":math:`\text{T}^a`", ":code:`technology, annual = 1`", ":code:`tech_annual`", "technologies that produce constant annual output (:math:`{T}^a \subset T`)" - ":math:`\text{T}^{res}`", ":code:`technology, reserve = 1`", ":code:`tech_reserve`", "electric generators contributing to the reserve margin requirement (:math:`{T}^{res} \subset T`)" - ":math:`\text{T}^c`", ":code:`technology, curtail = 1`", ":code:`tech_curtailment`", "technologies with curtailable output and no upstream cost (:math:`{T}^c \subset (T - T^{res})`)" + ":math:`\text{T}^c`", ":code:`technology, curtail = 1`", ":code:`tech_curtailment`", "technologies with curtailable output and no upstream cost (:math:`{T}^c \subset T`)" ":math:`\text{T}^f`", ":code:`technology, flex = 1`", ":code:`tech_flex`", "technologies producing excess commodity flows (:math:`{T}^f \subset T`)" ":math:`\text{T}^x`", ":code:`technology, exchange = 1`", ":code:`tech_exchange`", "technologies used for interregional commodity flows (:math:`{T}^x \subset T`)" ":math:`\text{T}^{ur}`", ":code:`ramp_up_hourly`", ":code:`tech_upramping`", "electric generators with a ramp up hourly rate limit; derived from :code:`ramp_up_hourly` table (:math:`{T}^{ur} \subset T`)" ":math:`\text{T}^{dr}`", ":code:`ramp_down_hourly`", ":code:`tech_downramping`", "electric generators with a ramp down hourly rate limit; derived from :code:`ramp_down_hourly` table (:math:`{T}^{dr} \subset T`)" ":math:`\text{T}^{ret}`", ":code:`technology, retire = 1`", ":code:`tech_retirement`", "technologies allowed to retire before end of life (:math:`{T}^{ret} \subset (T - T^{u})`)" - ":math:`\text{T}^u`", ":code:`technology, unlim_cap = 1`", ":code:`tech_uncap`", "technologies that have no bound on capacity (:math:`{T}^u \subset (T - T^{res})`)" + ":math:`\text{T}^u`", ":code:`technology, unlim_cap = 1`", ":code:`tech_uncap`", "technologies that have no bound on capacity (:math:`{T}^u \subset T`)" ":math:`\text{T}^{ss}`", ":code:`technology, flag = 'ps' AND seas_stor = 1`", ":code:`tech_seasonal_storage`", "seasonal storage technologies; requires both storage flag and seas_stor column (:math:`{T}^{ss} \subset T^s`)" "", ":code:`tech_group`", ":code:`tech_group_names`", "named groups for use in group constraints" "", ":code:`tech_group_member`", ":code:`tech_group_members`", "each technology belonging to each group" diff --git a/temoa/__about__.py b/temoa/__about__.py index 28c809a05..954c4971d 100644 --- a/temoa/__about__.py +++ b/temoa/__about__.py @@ -21,4 +21,7 @@ # db is tested for match on major and >= on minor DB_MAJOR_VERSION = 4 -MIN_DB_MINOR_VERSION = 0 +MIN_DB_MINOR_VERSION = 1 + +# Also needs updating in database.rst +DB_SCHEMA = 'temoa_schema_v4_1.sql' diff --git a/temoa/_internal/temoa_sequencer.py b/temoa/_internal/temoa_sequencer.py index bd44044e3..d49d15ab6 100644 --- a/temoa/_internal/temoa_sequencer.py +++ b/temoa/_internal/temoa_sequencer.py @@ -144,6 +144,8 @@ def build_model(self) -> TemoaModel: data_portal, silent=self.config.silent, extensions=self.config.extensions, + keep_lp_file=self.config.save_lp_file, + lp_path=self.config.output_path, ) logger.info('Model build process complete.') diff --git a/temoa/cli.py b/temoa/cli.py index 9feee14d4..90cf91f54 100644 --- a/temoa/cli.py +++ b/temoa/cli.py @@ -11,7 +11,7 @@ from rich.logging import RichHandler from rich.text import Text -from temoa.__about__ import __version__ +from temoa.__about__ import DB_SCHEMA, __version__ from temoa._internal.temoa_sequencer import TemoaSequencer from temoa.core.config import TemoaConfig from temoa.core.modes import TemoaMode @@ -137,7 +137,7 @@ def _cite_callback(value: bool) -> None: def get_default_schema() -> Path: """Get the default path to the v4 schema file, handling both installed and development cases.""" try: - schema_path = resources.files('temoa.db_schema') / 'temoa_schema_v4.sql' + schema_path = resources.files('temoa.db_schema') / DB_SCHEMA if not schema_path.is_file(): raise FileNotFoundError( @@ -149,7 +149,7 @@ def get_default_schema() -> Path: # The fallback for development needs to reflect the current repository structure # assuming `cli.py` is in `temoa/` and `db_schema/` is a sibling of `cli.py` within # `temoa/`. - fallback_path = Path(__file__).parent / 'db_schema' / 'temoa_schema_v4.sql' + fallback_path = Path(__file__).parent / 'db_schema' / DB_SCHEMA if fallback_path.is_file(): logger.warning( 'Using fallback schema path: %s. ' @@ -573,7 +573,7 @@ def _copy_tutorial_resources(target_config: Path, target_database: Path) -> None config_resource = base / 'config_sample.toml' sql_resource = base / 'utopia.sql' mc_settings_resource = base / 'mc_settings.csv' - schema_resource = resources.files('temoa.db_schema') / 'temoa_schema_v4.sql' + schema_resource = resources.files('temoa.db_schema') / DB_SCHEMA # Copy configuration file with config_resource.open('rb') as source: @@ -612,7 +612,7 @@ def _copy_tutorial_resources(target_config: Path, target_database: Path) -> None fallback_config = Path(__file__).parent / 'tutorial_assets' / 'config_sample.toml' fallback_sql = Path(__file__).parent / 'tutorial_assets' / 'utopia.sql' fallback_mc = Path(__file__).parent / 'tutorial_assets' / 'mc_settings.csv' - fallback_schema = Path(__file__).parent / 'db_schema' / 'temoa_schema_v4.sql' + fallback_schema = Path(__file__).parent / 'db_schema' / DB_SCHEMA if not fallback_config.exists(): raise FileNotFoundError( diff --git a/temoa/components/capacity.py b/temoa/components/capacity.py index 78b681346..1492e3ef3 100644 --- a/temoa/components/capacity.py +++ b/temoa/components/capacity.py @@ -657,34 +657,34 @@ def create_capacity_and_retirement_sets(model: TemoaModel) -> None: def gather_group_active_processes( - model: TemoaModel, r: Region, p: Period, t: Technology + model: TemoaModel, r_g: Region, p: Period, t_g: Technology ) -> set[tuple[Region, Technology]]: """ A utility to get the valid active processes for a group-region, tech-group pair in period p. Caches the result if its a new call so we don't repeat these O(n2) lookups. """ - if (r, p, t) not in model.group_active_processes: - model.group_active_processes[r, p, t] = { - (_r, _t) - for _r in geography.gather_group_regions(model, r) - for _t in technology.gather_group_techs(model, t) - if (_r, p, _t) in model.process_vintages + if (r_g, p, t_g) not in model.group_active_processes: + model.group_active_processes[r_g, p, t_g] = { + (r, t) + for r in geography.gather_group_regions(model, r_g) + for t in technology.gather_group_techs(model, t_g) + if (r, p, t) in model.process_vintages } - return model.group_active_processes[r, p, t] + return model.group_active_processes[r_g, p, t_g] def gather_group_built_processes( - model: TemoaModel, r: Region, t: Technology, v: Vintage + model: TemoaModel, r_g: Region, t_g: Technology, v: Vintage ) -> set[tuple[Region, Technology]]: """ A utility to get the valid built processes for a group-region, tech-group, vintage index. Caches the result if its a new call so we don't repeat these O(n2) lookups. """ - if (r, t, v) not in model.group_built_processes: - model.group_built_processes[r, t, v] = { - (_r, _t) - for _r in geography.gather_group_regions(model, r) - for _t in technology.gather_group_techs(model, t) - if (_r, _t, v) in model.process_periods + if (r_g, t_g, v) not in model.group_built_processes: + model.group_built_processes[r_g, t_g, v] = { + (r, t) + for r in geography.gather_group_regions(model, r_g) + for t in technology.gather_group_techs(model, t_g) + if (r, t, v) in model.process_periods } - return model.group_built_processes[r, t, v] + return model.group_built_processes[r_g, t_g, v] diff --git a/temoa/components/geography.py b/temoa/components/geography.py index 45e8f788e..892f76974 100644 --- a/temoa/components/geography.py +++ b/temoa/components/geography.py @@ -19,8 +19,6 @@ from pyomo.environ import value if TYPE_CHECKING: - from collections.abc import Iterable - from temoa.core.model import TemoaModel from temoa.types import ExprLike, Period, Region, Technology, Vintage @@ -33,15 +31,13 @@ # ============================================================================ -def gather_group_regions(model: TemoaModel, region: Region) -> Iterable[Region]: - regions: list[Region] +def gather_group_regions(model: TemoaModel, region: Region) -> set[Region]: if region == 'global': - regions = list(model.regions) + return set(model.regional_indices) elif '+' in region: - regions = [cast('Region', r) for r in region.split('+')] + return {cast('Region', r) for r in region.split('+')} else: - regions = [region] - return regions + return {region} # ============================================================================ diff --git a/temoa/components/limits.py b/temoa/components/limits.py index 35eacb054..3ce201012 100644 --- a/temoa/components/limits.py +++ b/temoa/components/limits.py @@ -160,42 +160,6 @@ def limit_annual_capacity_factor_indices( # ============================================================================ -# @deprecated('Deprecated. Use limit_activityGroupShare instead') # doesn't play well with pyomo -def renewable_portfolio_standard_constraint( - model: TemoaModel, r: Region, p: Period, g: str -) -> ExprLike: - r""" - Allows users to specify the share of electricity generation in a region - coming from RPS-eligible technologies. - """ - # devnote: this formulation leans on the reserve set, which is not necessarily - # the super set we want. We can also generalise this to all groups and so - # it has been deprecated in favour of the limit_activityGroupShare constraint. - - inp = quicksum( - model.v_flow_out[r, p, s, d, S_i, t, v, S_o] - for t in model.tech_group_members[g] - for (_t, v) in model.process_reserve_periods.get((r, p), []) - if _t == t - for s in model.time_season - for d in model.time_of_day - for S_i in model.process_inputs[r, p, t, v] - for S_o in model.process_outputs_by_input[r, p, t, v, S_i] - ) - - total_inp = quicksum( - model.v_flow_out[r, p, s, d, S_i, t, v, S_o] - for (t, v) in model.process_reserve_periods[r, p] - for s in model.time_season - for d in model.time_of_day - for S_i in model.process_inputs[r, p, t, v] - for S_o in model.process_outputs_by_input[r, p, t, v, S_i] - ) - - expr = inp >= (value(model.renewable_portfolio_standard[r, p, g]) * total_inp) - return expr - - def limit_resource_constraint(model: TemoaModel, r: Region, t: Technology, op: str) -> ExprLike: r""" diff --git a/temoa/components/operations.py b/temoa/components/operations.py index 3d6ab7dfc..0df5879eb 100644 --- a/temoa/components/operations.py +++ b/temoa/components/operations.py @@ -175,7 +175,6 @@ def create_operational_vintage_sets(model: TemoaModel) -> None: for r, p, t in model.process_vintages: for v in model.process_vintages[r, p, t]: key_rpt = (r, p, t) - key_rp = (r, p) if t in model.tech_curtailment: model.curtailment_vintages.setdefault(key_rpt, set()).add(v) if t in model.tech_baseload: @@ -186,8 +185,6 @@ def create_operational_vintage_sets(model: TemoaModel) -> None: model.ramp_up_vintages.setdefault(key_rpt, set()).add(v) if t in model.tech_downramping: model.ramp_down_vintages.setdefault(key_rpt, set()).add(v) - if t in model.tech_reserve: - model.process_reserve_periods.setdefault(key_rp, set()).add((t, v)) # A dictionary of whether a storage tech is seasonal, just to speed things up for t in model.tech_storage: diff --git a/temoa/components/reserves.py b/temoa/components/reserves.py index 9b519d89a..3fa12a6ae 100644 --- a/temoa/components/reserves.py +++ b/temoa/components/reserves.py @@ -2,10 +2,15 @@ """ Defines the reserve margin components of the Temoa model. -This module is responsible for ensuring the energy system maintains a sufficient -planning reserve margin to ensure reliability. It supports both a 'static' -(based on installed capacity) and a 'dynamic' (based on available generation -in a time slice) formulation for calculating available reserves. +This module ensures the energy system maintains sufficient reserves for +reliability. It supports both a 'planning' (based on installed capacity and +a capacity credit) and an 'operating' (based on available, derated generation +in a time slice) formulation. + +Both formulations are indexed by an arbitrary region-group and tech-group. +Reserve margins are the only constraints in Temoa that, given a region group, +automatically pull in exchange processes connecting a region inside the group +to a region outside it -- see `initialize_reserve_margins`. """ from __future__ import annotations @@ -13,14 +18,24 @@ from logging import getLogger from typing import TYPE_CHECKING -from pyomo.environ import Constraint, Expression, value +from pyomo.environ import Constraint, quicksum, value + +from temoa.components import geography +from temoa.components.capacity import gather_group_active_processes from .utils import get_available_output, get_variable_efficiency if TYPE_CHECKING: from temoa.core.model import TemoaModel from temoa.types import ExprLike - from temoa.types.core_types import Period, Region, Season, TimeOfDay + from temoa.types.core_types import ( + Period, + Region, + Season, + Technology, + TimeOfDay, + Vintage, + ) logger = getLogger(__name__) @@ -29,348 +44,448 @@ # ============================================================================ -def reserve_margin_indices(model: TemoaModel) -> set[tuple[Region, Period, Season, TimeOfDay]]: +def operating_reserve_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology]]: return { - (r, p, s, d) - for r in model.planning_reserve_margin.sparse_keys() + (r_g, p, s, d, t_g) + for r_g, t_g in model.operating_reserve_margin.sparse_keys() for p in model.time_optimize - if (r, p) in model.process_reserve_periods + if model.operating_reserve_processes.get((r_g, p, t_g), set()) for s in model.time_season for d in model.time_of_day } -# ============================================================================ -# HELPER FUNCTIONS FOR CONSTRAINT LOGIC -# ============================================================================ - +def planning_reserve_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology]]: + return { + (r_g, p, s, d, t_g) + for r_g, t_g in model.planning_reserve_margin.sparse_keys() + for p in model.time_optimize + if model.planning_reserve_processes.get((r_g, p, t_g), set()) + for s in model.time_season + for d in model.time_of_day + } -def _available_activity_dynamic( - model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay -) -> Expression: - # Everything but storage and exchange techs - # Derated available generation - available = sum( - get_available_output(model, r, p, s, d, t, v) - * value(model.reserve_capacity_derate[r, s, t, v]) - for (t, v) in model.process_reserve_periods[r, p] - if t not in model.tech_uncap and t not in model.tech_storage - ) +# ============================================================================ +# INITIALIZATION FUNCTIONS +# ============================================================================ - # Storage - # Derated net output flow - available += sum( - model.v_flow_out[r, p, s, d, i, t, v, o] * value(model.reserve_capacity_derate[r, s, t, v]) - for (t, v) in model.process_reserve_periods[r, p] - if t in model.tech_storage - for i in model.process_inputs[r, p, t, v] - for o in model.process_outputs_by_input[r, p, t, v, i] - ) - available -= sum( - model.v_flow_in[r, p, s, d, i, t, v, o] * value(model.reserve_capacity_derate[r, s, t, v]) - for (t, v) in model.process_reserve_periods[r, p] - if t in model.tech_storage - for i in model.process_inputs[r, p, t, v] - for o in model.process_outputs_by_input[r, p, t, v, i] - ) - # The above code does not consider exchange techs, e.g. electricity - # transmission between two distinct regions. - # We take exchange takes into account below. - # Note that a single exchange tech linking regions Ri and Rj is twice - # defined: once for region "Ri-Rj" and once for region "Rj-Ri". +def initialize_reserve_margins(model: TemoaModel) -> None: + """Build dictionaries of processes contributing to each reserve margin and log any issues. - # First, determine the amount of firm capacity each exchange tech - # contributes. - for r1r2 in model.regional_indices: - if '-' not in r1r2: - continue - if ( - r1r2, - p, - ) not in model.process_reserve_periods: # ensure r1r2 is a valid reserve provider in p - continue - r1, r2 = r1r2.split('-') + For each (region-group, tech-group) key, resolves the group's base regions + via `geography.gather_group_regions`, then appends any exchange region-pair + (`r1-r2`) with exactly one endpoint inside the group, so exchange processes + crossing the group boundary are counted as contributors. This is the only + place in Temoa where a group constraint auto-includes connected exchange + regions; the corresponding import/export sign handling is applied later in + `reserve_margin_proxy_demand` and the two constraint rules below. + """ - output = sum( - get_available_output(model, r1r2, p, s, d, t, v) - * value(model.reserve_capacity_derate[r1r2, s, t, v]) - for (t, v) in model.process_reserve_periods[r1r2, p] - ) + for r_g, t_g in model.operating_reserve_margin.sparse_keys(): + _r_g = r_g + regions = geography.gather_group_regions(model, r_g) + # Append any connected exchange region pairs + for r1r2 in model.regional_indices: + if r1r2 in regions or '-' not in r1r2: + continue + r1, r2 = r1r2.split('-') + if (r1 in regions) != (r2 in regions): + _r_g += '+' + r1r2 + # Get all contributing valid processes in each period + for p in model.time_optimize: + valid_rtv = { + (r, t, v) + for r, t in gather_group_active_processes(model, _r_g, p, t_g) + for v in model.process_vintages.get((r, p, t), set()) + } + if valid_rtv: + model.operating_reserve_processes[(r_g, p, t_g)] = valid_rtv + else: + logger.info( + 'Operating reserve margin %s has no contributors in period %s', + ((r_g, t_g), p), + ) + + if not any( + model.operating_reserve_processes.get((r_g, p, t_g), set()) for p in model.time_optimize + ): + logger.warning( + 'Operating reserve margin has no contributors in any period: %s', + ((r_g, t_g), value(model.operating_reserve_margin[r_g, t_g])), + ) - # Only consider exchange technologies connecting to this region - if r2 == r: - # Add the firm capacity commitment TO this region - # (this region was guaranteed an import of power) - available += output - elif r1 == r: - # Subtract the firm capacity commitment FROM this region - # (this region guaranteed an export of power) - available -= output + for r_g, t_g in model.planning_reserve_margin.sparse_keys(): + _r_g = r_g + regions = geography.gather_group_regions(model, r_g) + # Append any connected exchange region pairs + for r1r2 in model.regional_indices: + if r1r2 in regions or '-' not in r1r2: + continue + r1, r2 = r1r2.split('-') + if (r1 in regions) != (r2 in regions): + _r_g += '+' + r1r2 + # Get all contributing valid processes in each period + for p in model.time_optimize: + valid_rtv = { + (r, t, v) + for r, t in gather_group_active_processes(model, _r_g, p, t_g) + for v in model.process_vintages.get((r, p, t), set()) + } + if valid_rtv: + model.planning_reserve_processes[(r_g, p, t_g)] = valid_rtv + else: + logger.info( + 'Planning reserve margin %s has no contributors in period %s', + (r_g, t_g), + p, + ) + + if not any( + model.planning_reserve_processes.get((r_g, p, t_g), set()) for p in model.time_optimize + ): + logger.warning( + 'Planning reserve margin has no contributors in any period: %s', + ((r_g, t_g), value(model.planning_reserve_margin[r_g, t_g])), + ) - return available +# ============================================================================ +# HELPER FUNCTIONS FOR CONSTRAINT LOGIC +# ============================================================================ -def _available_activity_static( - model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay -) -> Expression: - available = sum( - value(model.capacity_credit[r, p, t, v]) - * model.v_capacity[r, p, t, v] - * value(model.capacity_to_activity[r, t]) - * value(model.segment_fraction[s, d]) - for (t, v) in model.process_reserve_periods[r, p] - if t not in model.tech_uncap - ) +def _into_region(model: TemoaModel, r: Region, t: Technology, regions: set[Region]) -> int: + """ + Returns +1 if not an exchange tech or is an exchange tech importing into the region group. + """ + if t not in model.tech_exchange: + return 1 + r1, r2 = r.split('-') + if r2 in regions and r1 not in regions: + return 1 + return 0 - # The above code does not consider exchange techs, e.g. electricity - # transmission between two distinct regions. - # We take exchange takes into account below. - # Note that a single exchange tech linking regions Ri and Rj is twice - # defined: once for region "Ri-Rj" and once for region "Rj-Ri". - # First, determine the amount of firm capacity each exchange tech - # contributes. - for r1r2 in model.regional_indices: - if '-' not in r1r2: - continue - if ( - r1r2, - p, - ) not in model.process_reserve_periods: # ensure r1r2 is a valid reserve provider in p - continue - r1, r2 = r1r2.split('-') +def _out_of_region(model: TemoaModel, r: Region, t: Technology, regions: set[Region]) -> int: + """ + Returns +1 if and only if is an exchange tech exporting out of the region group. + """ + if t not in model.tech_exchange: + return 0 + r1, r2 = r.split('-') + if r1 in regions and r2 not in regions: + return 1 + return 0 - # Only consider exchange technologies connecting to this region - if r2 == r: - # Add the firm capacity commitment TO this region - # (this region was guaranteed an import of power) - available += sum( - value(model.capacity_credit[r1r2, p, t, v]) - * model.v_capacity[r1r2, p, t, v] - * value(model.capacity_to_activity[r1r2, t]) - * value(model.segment_fraction[s, d]) - for (t, v) in model.process_reserve_periods[r1r2, p] - ) - elif r1 == r: - # Subtract the firm capacity commitment FROM this region - # (this region guaranteed an export of power) - available -= sum( - value(model.capacity_credit[r1r2, p, t, v]) - * model.v_capacity[r1r2, p, t, v] - * value(model.capacity_to_activity[r1r2, t]) - * value(model.segment_fraction[s, d]) - for (t, v) in model.process_reserve_periods[r1r2, p] - ) - return available +def reserve_margin_proxy_demand( + model: TemoaModel, + processes: set[tuple[Region, Technology, Vintage]], + r_g: Region, + p: Period, + s: Season, + d: TimeOfDay, +) -> ExprLike: + r"""In Temoa, demand for a particular commodity (e.g., electricity) may be endogenous to + decisions in the model. So, we estimate demand as the net production of processes + in the reserve group, :math:`\Theta^{res}_{r_g,p,t_g}`. This provides the RHS demand for + both reserve constraints. + + The region group :math:`r_g` indicates the regions in which demand is met, and the + tech group :math:`t_g` indicates which technologies supply the demanded commodity. The + technology group should include all technologies that produce, store, or import/export + the commodity of interest but **not** the technologies that demand it downstream. + + .. note:: + In Temoa, we are not reasonably able to disaggregate the **available** output of a + process for individual commodities when that process outputs multiple commodities. As a + result, a reserve margin constraint cannot be applied to a single commodity where supplying + processes output multiple different commodities (e.g., if a co-generation plant outputs both + heat and electricity, the heat will be included in the equations). This can be avoided by + adding an intermediate "dummy" process that throughputs only the output of interest and then + adding this dummy process to the tech group instead of the original process, but this + requires careful cloning of other technoeconomic parameters so the reserve contributions + remain the same. + .. math:: + :label: reserve_margin_proxy_demand + + \begin{aligned} + D^{proxy}_{r_g,p,s,d} =& + \sum_{\substack{(r,t,v) \in \Theta^{res} \setminus T^a \\ \uparrow_{r,r_g},\, I, O}} + \mathbf{FO}_{r, p, s, d, i, t, v, o} + && \text{(non-annual production and imports)} \\ + &+ \sum_{\substack{(r,t,v) \in \Theta^{res} \cap T^a \\ \uparrow_{r,r_g},\, I, O}} + \begin{cases} DSD_{r,s,d,o} & o \in C^d \\ SEG_{s,d} & \text{otherwise} + \end{cases} \cdot \mathbf{FOA}_{r, p, i, t, v, o} + && \text{(annual production and imports)} \\ + &- \sum_{(r,t,v) \in \Theta^{res} \cap T^s,\, I, O} + \mathbf{FI}_{r, p, s, d, i, t, v, o} + && \text{(storage inputs)} \\ + &- \sum_{\substack{(r,t,v) \in \Theta^{res} \cap T^x \setminus T^a \\ + \downarrow_{r,r_g},\, I, O}} + \mathbf{FO}_{r, p, s, d, i, t, v, o} / EFF_{r,p,s,d,i,t,v,o} + && \text{(non-annual exports)} \\ + &- \sum_{\substack{(r,t,v) \in \Theta^{res} \cap T^x \cap T^a \\ + \downarrow_{r,r_g},\, I, O}} + \begin{cases} DSD_{r,s,d,o} & o \in C^d \\ SEG_{s,d} & \text{otherwise} + \end{cases} \cdot \mathbf{FOA}_{r, p, i, t, v, o} / EFF_{r,p,i,t,v,o} + && \text{(annual exports)} + \end{aligned} + + where :math:`\uparrow_{r,r_g}` selects non-exchange processes and exchange imports + (:math:`r_2 \in r_g,\ r_1 \notin r_g`), and :math:`\downarrow_{r,r_g}` selects exchange + exports (:math:`r_1 \in r_g,\ r_2 \notin r_g`). :math:`\Theta^{res} = \Theta^{res}_{r_g,p,t_g}` + is the set of all :math:`(r,t,v)` processes contributing to this reserve margin in this period. + """ -def _required_available_activity( - model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay -) -> ExprLike: + regions = geography.gather_group_regions(model, r_g) - # In most Temoa input databases, demand is endogenous, so we use electricity - # generation instead as a proxy for electricity demand. - # Non-annual generation - total_generation = sum( - model.v_flow_out[r, p, s, d, S_i, t, S_v, S_o] - for (t, S_v) in model.process_reserve_periods[r, p] - if t not in model.tech_annual - for S_i in model.process_inputs[r, p, t, S_v] - for S_o in model.process_outputs_by_input[r, p, t, S_v, S_i] + # Non-annual activity + activity = quicksum( + model.v_flow_out[r, p, s, d, i, t, v, o] + for (r, t, v) in processes + if t not in model.tech_annual and _into_region(model, r, t, regions) + for i in model.process_inputs[r, p, t, v] + for o in model.process_outputs_by_input[r, p, t, v, i] ) - # Generators might serve demands directly - # Annual generation - total_generation += sum( + # Annual activity (could also just be a demand tech) + activity += quicksum( ( - value(model.demand_specific_distribution[r, p, s, d, S_o]) - if S_o in model.commodity_demand + value(model.demand_specific_distribution[r, p, s, d, o]) + if o in model.commodity_demand else value(model.segment_fraction[s, d]) ) - * model.v_flow_out_annual[r, p, S_i, t, S_v, S_o] - for (t, S_v) in model.process_reserve_periods[r, p] - if t in model.tech_annual - for S_i in model.process_inputs[r, p, t, S_v] - for S_o in model.process_outputs_by_input[r, p, t, S_v, S_i] + * model.v_flow_out_annual[r, p, i, t, v, o] + for (r, t, v) in processes + if t in model.tech_annual and _into_region(model, r, t, regions) + for i in model.process_inputs[r, p, t, v] + for o in model.process_outputs_by_input[r, p, t, v, i] ) # We must take into account flows into storage technologies. # Flows into storage technologies need to be subtracted from the - # load calculation. - total_generation -= sum( - model.v_flow_in[r, p, s, d, S_i, t, S_v, S_o] - for (t, S_v) in model.process_reserve_periods[r, p] - if t in model.tech_storage - for S_i in model.process_inputs[r, p, t, S_v] - for S_o in model.process_outputs_by_input[r, p, t, S_v, S_i] + # load calculation. Flow_out already summed above. + activity -= quicksum( + model.v_flow_in[r, p, s, d, i, t, v, o] + for (r, t, v) in processes + if t in model.tech_storage and t not in model.tech_exchange + for i in model.process_inputs[r, p, t, v] + for o in model.process_outputs_by_input[r, p, t, v, i] ) - # Electricity imports and exports via exchange techs are accounted - # for below: - for r1r2 in model.regional_indices: # ensure the region is of the form r1-r2 - if '-' not in r1r2: - continue - if ( - r1r2, - p, - ) not in model.process_reserve_periods: # ensure r1r2 is a valid reserve provider in p - continue - r1, r2 = r1r2.split('-') - # First, determine the exports, and subtract this value from the - # total generation. - if r1 == r: - total_generation -= sum( - model.v_flow_out[r1r2, p, s, d, S_i, t, S_v, S_o] - / get_variable_efficiency(model, r1r2, p, s, d, S_i, t, S_v, S_o) - for (t, S_v) in model.process_reserve_periods[r1r2, p] - for S_i in model.process_inputs[r1r2, p, t, S_v] - for S_o in model.process_outputs_by_input[r1r2, p, t, S_v, S_i] - ) - # Second, determine the imports, and add this value from the - # total generation. - elif r2 == r: - total_generation += sum( - model.v_flow_out[r1r2, p, s, d, S_i, t, S_v, S_o] - for (t, S_v) in model.process_reserve_periods[r1r2, p] - for S_i in model.process_inputs[r1r2, p, t, S_v] - for S_o in model.process_outputs_by_input[r1r2, p, t, S_v, S_i] - ) + # Subtract exchange exports + # Non-annual exports + activity -= quicksum( + model.v_flow_out[r, p, s, d, i, t, v, o] + / get_variable_efficiency(model, r, p, s, d, i, t, v, o) + for (r, t, v) in processes + if t not in model.tech_annual and _out_of_region(model, r, t, regions) + for i in model.process_inputs[r, p, t, v] + for o in model.process_outputs_by_input[r, p, t, v, i] + ) + # Annual exports (could feed a demand) + activity -= quicksum( + ( + value(model.demand_specific_distribution[r, p, s, d, o]) + if o in model.commodity_demand + else value(model.segment_fraction[s, d]) + ) + * model.v_flow_out_annual[r, p, i, t, v, o] + / value(model.efficiency[r, i, t, v, o]) + for (r, t, v) in processes + if t in model.tech_annual and _out_of_region(model, r, t, regions) + for i in model.process_inputs[r, p, t, v] + for o in model.process_outputs_by_input[r, p, t, v, i] + ) - requirement = total_generation * (1 + value(model.planning_reserve_margin[r])) - return requirement + return activity # ============================================================================ -# PYOMO CONSTRAINT RULE +# PYOMO CONSTRAINT RULES # ============================================================================ -def reserve_margin_dynamic( - model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay +def operating_reserve_margin_constraint( + model: TemoaModel, r_g: Region, p: Period, s: Season, d: TimeOfDay, t_g: Technology ) -> Constraint: r""" - A dynamic alternative to the traditional, static reserve margin constraint. Capacity values - are calculated from availability of generation in each hour—like an operating reserve margin—\ - accounting for a capacity derate factor subtracting, for example, forced outage due to icing. + A dynamic alternative to the planning reserve margin constraint. Capacity values are + calculated from process output availability in each time slice, accounting for capacity + factors, unit commitment (if the extension is enabled), and a seasonal derating factor + which may adjust for, for example, seasonal forced outage rates. A derate factor of 1 + indicates no derating while a factor of 0 indicates zero dependable output in that season. + Technologies in the tech group are used to calculate the proxy demand and so must include + these fully derated processes as well. + + **The default derate factor is 1** if not set (i.e., we assume technologies are fully + available, up to their capacity factor, by default). + + For exchange technologies (e.g., inter-regional transmission), reserve + contributions are added for available output into the region-group but *subtracted* + for capacity out of it. + + The availability of storage technologies is a non-trivial problem as it depends on state + of charge, which has a temporal dependency (i.e., if we consider a storage technology to + contribute reserve in time t can it also contribute in time t+1?). In this implementation, + we let storage contribute only what it actually outputs (net) in each time slice, as a + conservative but tractable approach (use it or lose it). .. math:: - :label: reserve_margin_dynamic - - &\sum_{t \in T^{res} \setminus T^{x} \setminus T^s,\ V} CFP_{r,s^*,d^*,t,v}\ - \cdot RCD_{r,s^*,t,v}\ - \cdot \mathbf{CAP}_{r,p,t,v} \cdot SEG_{s^*,d^*}\ - \cdot C2A_{r,t} \\ - &+ \sum_{t \in T^{res} \cap T^{x} \setminus T^s,\ V} CFP_{r_i - r, s^*, d^*, t, v}\ - \cdot RCD_{r_i - r, s^*, t, v}\ - \cdot \mathbf{CAP}_{r_i - r,p,t,v} \cdot SEG_{s^*,d^*}\ - \cdot C2A_{r_i - r, t} \\ - &- \sum_{t \in T^{res} \cap T^{x} \setminus T^s,\ V} CFP_{r - r_i, s^*, d^*, t, v}\ - \cdot RCD_{r - r_i, s^*, t, v}\ - \cdot \mathbf{CAP}_{r - r_i,p,t,v}\ - \cdot SEG_{s^*,d^*} \cdot C2A_{r - r_i, t} \\ - &+ \sum_{t \in (T^s \cap T^{res}), V, I, O} \ - \left(\ - \mathbf{FO}_{r,p,s,d,i,t,v,o} - \mathbf{FI}_{r,p,s,d,i,t,v,o}\ - \right)\ - \cdot RCD_{r,s,t,v} \\ - &\geq\ - \left[\ - \sum_{t \in T^{res} \setminus T^{x} \setminus T^a, V, I, O}\ - \mathbf{FO}_{r, p, s, d, i, t, v, o}\ - \right. \\ - &+ \sum_{t \in T^{res} \cap T^a, V, I, O} - \begin{cases} DSD_{r,s,d,o} & \text{if } o \in C^d \\ - SEG_{s,d} & \text{otherwise} \end{cases} - \cdot \mathbf{FOA}_{r, p, i, t, v, o} \\ - &+ \sum_{t \in T^{res} \cap T^{x}, V, I, O} \ - \mathbf{FO}_{r_i - r, p, s, d, i, t, v, o} \\ - &- \sum_{t \in T^{res} \cap T^{x}, V, I, O} \ - \mathbf{FI}_{r - r_i, p, s, d, i, t, v, o} \\ - &- \left. \sum_{t \in T^{res} \cap T^{s}, V, I, O} \ - \mathbf{FI}_{r, p, s, d, i, t, v, o} \right] \cdot (1 + PRM_r) \\ - \\ - &\qquad \qquad \forall \{r, p, s, d\} \in \ - \Theta_{\text{ReserveMargin}} \text{ and } \forall r_i \in R + :label: operating_reserve_margin + + \begin{aligned} + &\sum_{(r,t,v) \in \Theta^{res} \setminus T^x \setminus T^s} + CFP_{r,s,d,t,v} \cdot ORD_{r,s,t} \cdot \mathbf{CAP}_{r,p,t,v} + \cdot SEG_{s,d} \cdot C2A_{r,t} + && \text{(firm production)} \\ + &+ \sum_{(r,t,v) \in \Theta^{res} \cap T^s,\, I, O} + \left( \mathbf{FO}_{r,p,s,d,i,t,v,o} - \mathbf{FI}_{r,p,s,d,i,t,v,o} \right) + \cdot ORD_{r,s,t} + && \text{(net storage output)} \\ + &+ \sum_{(r,t,v) \in \Theta^{res} \cap T^x} + \sigma_{r,r_g} \cdot CFP_{r,s,d,t,v} \cdot ORD_{r,s,t} + \cdot \mathbf{CAP}_{r,p,t,v} \cdot SEG_{s,d} \cdot C2A_{r,t} + && \text{(net firm imports)} \\ + &\geq D^{proxy}_{r_g,p,s,d} \cdot (1 + ORM_{r_g,t_g}) \\ + &\forall \{r_g, p, s, d, t_g\} \in \Theta_{\text{OperatingReserveMargin}} + \end{aligned} + + where :math:`\sigma_{r,r_g} = +1` for an exchange process delivering into + region-group :math:`r_g` and :math:`-1` for one delivering out of it, and + :math:`D^{proxy}_{r_g,p,s,d}` is the group's proxy demand defined in + :eq:`reserve_margin_proxy_demand`. """ - return _available_activity_dynamic(model, r, p, s, d) >= _required_available_activity( - model, r, p, s, d + processes = model.operating_reserve_processes[r_g, p, t_g] + + # Everything but storage and exchange techs + # Derated available generation + available = quicksum( + get_available_output(model, r, p, s, d, t, v) + * value(model.operating_reserve_derate[r, s, t]) + for (r, t, v) in processes + if not (t in model.tech_uncap or t in model.tech_storage or t in model.tech_exchange) ) + # Storage + # Derated net output flow + available += quicksum( + model.v_flow_out[r, p, s, d, i, t, v, o] * value(model.operating_reserve_derate[r, s, t]) + for (r, t, v) in processes + if t in model.tech_storage and t not in model.tech_exchange + for i in model.process_inputs[r, p, t, v] + for o in model.process_outputs_by_input[r, p, t, v, i] + ) + available -= quicksum( + model.v_flow_in[r, p, s, d, i, t, v, o] * value(model.operating_reserve_derate[r, s, t]) + for (r, t, v) in processes + if t in model.tech_storage and t not in model.tech_exchange + for i in model.process_inputs[r, p, t, v] + for o in model.process_outputs_by_input[r, p, t, v, i] + ) + + # Exchange technologies + # Add available imports into the group, subtract available exports out of it + regions = geography.gather_group_regions(model, r_g) + for r1r2, t, v in processes: + if t not in model.tech_exchange: + continue + + _available = get_available_output(model, r1r2, p, s, d, t, v) * value( + model.operating_reserve_derate[r1r2, s, t] + ) + + r1, r2 = r1r2.split('-') + if r2 in regions and r1 not in regions: + available += _available + elif r1 in regions and r2 not in regions: + available -= _available + + demand = reserve_margin_proxy_demand(model, processes, r_g, p, s, d) + return available >= demand * (1 + value(model.operating_reserve_margin[r_g, t_g])) -def reserve_margin_static( - model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay + +def planning_reserve_margin_constraint( + model: TemoaModel, r_g: Region, p: Period, s: Season, d: TimeOfDay, t_g: Technology ) -> Constraint: r""" During each period :math:`p`, the sum of capacity values of all reserve - technologies :math:`\sum_{t \in T^{res}} \textbf{CAP}_{r,p,t,v}`, which are - defined in the set :math:`\textbf{T}^{res}`, should exceed the peak load by - :math:`PRM`, the regional reserve margin. Note that the reserve - margin is expressed in percentage of the peak load. Generally speaking, in - a database we may not know the peak demand before running the model, therefore, - we write this equation for all the time-slices defined in the database in each region. - Each generator is allowed to contribute its available capacity times a pre-defined - capacity credit, :math:`CC_{r,p,t,v}`. - - For exchange technologies (i.e., inter-regional transmission), reserve contributions - are added to the downstream region but *subtracted* from the upstream region. This is - because, since they are not generating any power, their summed contribution across - regions should be zero. + technologies, weighted by their planning reserve credit, must exceed the + region-group's proxy demand by :math:`PRM_{r_g,t_g}` in every time slice. + + This credit represents the expected availability of nameplate capacity + during peak demand conditions and is usually determined from stochastic + modelling of demand/supply scenarios. A credit of 1 indicates the technology is + expected to be able to output its full nameplate capacity at high demand, + while 0 indicates it offers no reliable capacity. Technologies in the tech + group are used to calculate the proxy demand and so must include these zero + credit processes as well. + + **The default credit is 0** if not set (i.e., we assume technologies offer + no capacity value by default). + + For exchange technologies (e.g., inter-regional transmission), reserve + contributions are added for capacity into the region-group but *subtracted* + for capacity out of it. .. math:: :label: reserve_margin_static - &\sum_{t \in T^{res} \setminus T^{x}, V} {CC_{r,p,t,v} - \cdot \textbf{CAP}_{r,p,t,v} \cdot - SEG_{s^*,d^*} \cdot C2A_{r,t} }\\ - &+ \sum_{t \in T^{res} \cap T^{x}, V} {CC_{r_i-r,p,t,v} - \cdot \textbf{CAP}_{r_i-r,p,t,v} \cdot - SEG_{s^*,d^*} \cdot C2A_{r_i-r,t} }\\ - &- \sum_{t \in T^{res} \cap T^{x}, V} {CC_{r-r_i,p,t,v} - \cdot \textbf{CAP}_{r-r_i,p,t,v} \cdot - SEG_{s^*,d^*} \cdot C2A_{r-r_i,t} }\\ - &\geq \left [ \sum_{ t \in T^{res} \setminus T^{x} \setminus T^a,V,I,O } - \textbf{FO}_{r, p, s, d, i, t, v, o}\right.\\ - &+ \sum_{ t \in T^{res} \cap T^a,V,I,O } - \begin{cases} DSD_{r,s,d,o} & \text{if } o \in C^d \\ - SEG_{s,d} & \text{otherwise} \end{cases} - \cdot \textbf{FOA}_{r, p, i, t, v, o}\\ - &+ \sum_{ t \in T^{res} \cap T^{x},V,I,O } \textbf{FO}_{r_i-r, p, s, d, i, t, v, o}\\ - &- \sum_{ t \in T^{res} \cap T^{x},V,I,O } \textbf{FI}_{r-r_i, p, s, d, i, t, v, o}\\ - &- \left.\sum_{ t \in T^{res} \cap T^{s},V,I,O } \textbf{FI}_{r, p, s, d, i, t, v, o} - \right] - \cdot (1 + PRM_r)\\ - - \\ - &\qquad\qquad\forall \{r, p, s, d\} \in \Theta_{\text{ReserveMargin}} \text{and} \forall - r_i \in R + \begin{aligned} + &\sum_{(r,t,v) \in \Theta^{res} \setminus T^x} + PRC_{r,t} \cdot \mathbf{CAP}_{r,p,t,v} \cdot SEG_{s,d} \cdot C2A_{r,t} + && \text{(production capacity, includes storage)} \\ + &+ \sum_{(r,t,v) \in \Theta^{res} \cap T^x} + \sigma_{r,r_g} \cdot PRC_{r,t} \cdot \mathbf{CAP}_{r,p,t,v} + \cdot SEG_{s,d} \cdot C2A_{r,t} + && \text{(net import capacity)} \\ + &\geq D^{proxy}_{r_g,p,s,d} \cdot (1 + PRM_{r_g,t_g}) \\ + &\forall \{r_g, p, s, d, t_g\} \in \Theta_{\text{PlanningReserveMargin}} + \end{aligned} + + where :math:`\sigma_{r,r_g} = +1` for an exchange process delivering into + region-group :math:`r_g` and :math:`-1` for one delivering out of it, and + :math:`D^{proxy}_{r_g,p,s,d}` is the group's proxy demand defined in + :eq:`reserve_margin_proxy_demand`. """ - return _available_activity_static(model, r, p, s, d) >= _required_available_activity( - model, r, p, s, d + processes = model.planning_reserve_processes[r_g, p, t_g] + + available = quicksum( + value(model.planning_reserve_credit[r, t]) + * model.v_capacity[r, p, t, v] + * value(model.capacity_to_activity[r, t]) + * value(model.segment_fraction[s, d]) + for (r, t, v) in processes + if t not in model.tech_uncap and t not in model.tech_exchange ) + # Exchange technologies + # Add credited imports into the group, subtract credited exports out of it + regions = geography.gather_group_regions(model, r_g) + for r1r2, t, v in processes: + if t not in model.tech_exchange: + continue -def reserve_margin_constraint( - model: TemoaModel, - r: Region, - p: Period, - s: Season, - d: TimeOfDay, -) -> ExprLike: - """Returns the appropriate reserve margin constraint rule.""" - if (not model.tech_reserve) or ( - (r, p) not in model.process_reserve_periods - ): # If reserve set empty or if r,p not in M.processReservePeriod, skip the constraint - return Constraint.Skip - mode = model.reserve_margin_method.first() - if mode == 'dynamic': - return reserve_margin_dynamic(model, r, p, s, d) - elif mode == 'static': - return reserve_margin_static(model, r, p, s, d) - else: - raise ValueError( - f"Invalid reserve margin method: {mode}. Must be either 'dynamic' or 'static'." + _available = ( + value(model.planning_reserve_credit[r1r2, t]) + * model.v_capacity[r1r2, p, t, v] + * value(model.capacity_to_activity[r1r2, t]) + * value(model.segment_fraction[s, d]) ) + + r1, r2 = r1r2.split('-') + if r2 in regions and r1 not in regions: + available += _available + elif r1 in regions and r2 not in regions: + available -= _available + + demand = reserve_margin_proxy_demand(model, processes, r_g, p, s, d) + return available >= demand * (1 + value(model.planning_reserve_margin[r_g, t_g])) diff --git a/temoa/components/technology.py b/temoa/components/technology.py index 9957db979..d7666b4ec 100644 --- a/temoa/components/technology.py +++ b/temoa/components/technology.py @@ -20,8 +20,6 @@ from temoa.components.utils import get_adjusted_existing_capacity if TYPE_CHECKING: - from collections.abc import Iterable - from temoa.core.model import TemoaModel from temoa.types import Period, Region, Technology, Vintage @@ -32,13 +30,11 @@ # ============================================================================ -def gather_group_techs(model: TemoaModel, t_or_g: Technology) -> Iterable[Technology]: +def gather_group_techs(model: TemoaModel, t_or_g: Technology) -> set[Technology]: if t_or_g in model.tech_group_names: - return model.tech_group_members[t_or_g] - elif '+' in t_or_g: - return [cast('Technology', tech) for tech in t_or_g.split('+')] + return set(model.tech_group_members[t_or_g]) else: - return (t_or_g,) + return {t_or_g} # ============================================================================ diff --git a/temoa/core/config.py b/temoa/core/config.py index 3e68c9225..50015d091 100644 --- a/temoa/core/config.py +++ b/temoa/core/config.py @@ -50,7 +50,6 @@ def __init__( save_lp_file: bool = False, time_sequencing: str | None = None, days_per_period: int = 365, - reserve_margin: str | None = None, MGA: dict[str, object] | None = None, SVMGA: dict[str, object] | None = None, myopic: dict[str, object] | None = None, @@ -137,7 +136,6 @@ def __init__( self.save_lp_file = save_lp_file self.time_sequencing = time_sequencing self.days_per_period = days_per_period - self.reserve_margin = reserve_margin self.mga_inputs = MGA self.svmga_inputs = SVMGA @@ -374,7 +372,6 @@ def __repr__(self) -> str: msg += spacer msg += '{:>{}s}: {}\n'.format('Time sequencing', width, self.time_sequencing) msg += '{:>{}s}: {}\n'.format('Days per period', width, self.days_per_period) - msg += '{:>{}s}: {}\n'.format('Planning reserve margin', width, self.reserve_margin) if self.scenario_mode == TemoaMode.MYOPIC and self.myopic_inputs is not None: msg += spacer diff --git a/temoa/core/model.py b/temoa/core/model.py index b52ec1779..203fa68f0 100755 --- a/temoa/core/model.py +++ b/temoa/core/model.py @@ -48,7 +48,6 @@ validate_0to1, validate_efficiency, validate_linked_tech, - validate_reserve_margin, validate_tech_sets, ) @@ -162,8 +161,9 @@ def __init__( self.retirement_production_processes: t.RetirementProductionProcessesDict = {} self.process_inputs_by_output: t.ProcessInputsByOutputDict = {} self.process_outputs_by_input: t.ProcessOutputsByInputDict = {} - self.process_reserve_periods: t.ProcessReservePeriodsDict = {} self.process_periods: t.ProcessPeriodsDict = {} # {(r, t, v): set(p)} + self.planning_reserve_processes: t.ReserveProcessesDict = {} + self.operating_reserve_processes: t.ReserveProcessesDict = {} # {(r, t, v): set(p)} periods in which a process can economically or naturally retire self.retirement_periods: t.RetirementPeriodsDict = {} self.process_vintages: t.ProcessVintagesDict = {} @@ -263,7 +263,6 @@ def __init__( self.tech_demand = Set(within=self.tech_all) # annual storage not supported in Storage constraint or TableWriter, so exclude from domain self.tech_storage = Set(within=self.tech_all) - self.tech_reserve = Set(within=self.tech_all) self.tech_upramping = Set(within=self.tech_all) self.tech_downramping = Set(within=self.tech_all) self.tech_curtailment = Set(within=self.tech_all) @@ -279,7 +278,7 @@ def __init__( self.tech_seasonal_storage = Set(within=self.tech_storage) """storage technologies using the interseasonal storage feature""" - self.tech_uncap = Set(within=self.tech_all - self.tech_reserve) + self.tech_uncap = Set(within=self.tech_all) """techs with unlimited capacity, ALWAYS available within lifespan""" self.tech_exist = Set() @@ -517,13 +516,6 @@ def __init__( validate=validate_0to1, ) - self.renewable_portfolio_standard_constraint_rpg = Set( - within=self.regions * self.time_optimize * self.tech_group_names - ) - self.renewable_portfolio_standard = Param( - self.renewable_portfolio_standard_constraint_rpg, validate=validate_0to1 - ) - # The method below creates a series of helper functions that are used to # perform the sparse matrix of indexing for the parameters, variables, and # equations below. @@ -721,24 +713,21 @@ def __init__( self.linked_techs = Param(self.regional_indices, self.tech_all, self.commodity_emissions) # Define parameters associated with electric sector operation - self.reserve_margin_method = Set() # How contributions to the reserve margin are calculated - self.capacity_credit = Param( + self.planning_reserve_margin = Param(self.regional_global_indices, self.tech_or_group) + self.planning_reserve_credit = Param( self.regional_indices, - self.time_optimize, - self.tech_reserve, - self.vintage_all, + self.tech_all, default=0, validate=validate_0to1, ) - self.reserve_capacity_derate = Param( + self.operating_reserve_margin = Param(self.regional_global_indices, self.tech_or_group) + self.operating_reserve_derate = Param( self.regional_indices, self.time_season, - self.tech_reserve, - self.vintage_all, + self.tech_all, default=1, validate=validate_0to1, ) - self.planning_reserve_margin = Param(self.regions) self.emission_embodied = Param( self.regions, @@ -982,10 +971,14 @@ def __init__( self.ramp_up_constraint_rpsdtv, rule=operations.ramp_up_constraint ) - self.reserve_margin_rpsd = Set(dimen=4, initialize=reserves.reserve_margin_indices) - self.validate_reserve_margin = BuildAction(rule=validate_reserve_margin) - self.reserve_margin_constraint = Constraint( - self.reserve_margin_rpsd, rule=reserves.reserve_margin_constraint + self.initialize_reserve_margins = BuildAction(rule=reserves.initialize_reserve_margins) + self.operating_reserve_rpsdt = Set(dimen=5, initialize=reserves.operating_reserve_indices) + self.operating_reserve_margin_constraint = Constraint( + self.operating_reserve_rpsdt, rule=reserves.operating_reserve_margin_constraint + ) + self.planning_reserve_rpsdt = Set(dimen=5, initialize=reserves.planning_reserve_indices) + self.planning_reserve_margin_constraint = Constraint( + self.planning_reserve_rpsdt, rule=reserves.planning_reserve_margin_constraint ) self.limit_emission_constraint = Constraint( @@ -1091,11 +1084,6 @@ def __init__( rule=limits.limit_tech_output_split_average_constraint, ) - self.renewable_portfolio_standard_constraint = Constraint( - self.renewable_portfolio_standard_constraint_rpg, - rule=limits.renewable_portfolio_standard_constraint, - ) - self.linked_emissions_tech_constraint_rpsdtve = Set( dimen=7, initialize=emissions.linked_tech_constraint_indices ) diff --git a/temoa/data_io/component_manifest.py b/temoa/data_io/component_manifest.py index 7521b34c8..620c7f8de 100644 --- a/temoa/data_io/component_manifest.py +++ b/temoa/data_io/component_manifest.py @@ -101,15 +101,6 @@ def build_manifest(model: TemoaModel, extension_ids: Sequence[str] | None = None validation_map=(0,), is_period_filtered=False, ), - LoadItem( - component=model.tech_reserve, - table='technology', - columns=['tech'], - where_clause='reserve > 0', - validator_name='viable_techs', - validation_map=(0,), - is_period_filtered=False, - ), LoadItem( component=model.tech_curtailment, table='technology', @@ -499,34 +490,38 @@ def build_manifest(model: TemoaModel, extension_ids: Sequence[str] | None = None is_period_filtered=False, ), LoadItem( - component=model.renewable_portfolio_standard, - table='rps_requirement', - columns=['region', 'period', 'tech_group', 'requirement'], - custom_loader_name='_load_rps_requirement', + component=model.planning_reserve_credit, + table='planning_reserve_credit', + columns=['region', 'tech', 'credit'], + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, is_table_required=False, - index_set=model.renewable_portfolio_standard_constraint_rpg, ), LoadItem( - component=model.capacity_credit, - table='capacity_credit', - columns=['region', 'period', 'tech', 'vintage', 'credit'], - validator_name='viable_rtv', - validation_map=(0, 2, 3), + component=model.planning_reserve_margin, + table='planning_reserve_margin', + columns=['region', 'tech_or_group', 'margin'], + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, is_table_required=False, ), LoadItem( - component=model.reserve_capacity_derate, - table='reserve_capacity_derate', - columns=['region', 'season', 'tech', 'vintage', 'factor'], - validator_name='viable_rtv', - validation_map=(0, 2, 3), + component=model.operating_reserve_derate, + table='operating_reserve_derate', + columns=['region', 'season', 'tech', 'factor'], + validator_name='viable_rt', + validation_map=(0, 2), is_period_filtered=False, is_table_required=False, ), LoadItem( - component=model.planning_reserve_margin, - table='planning_reserve_margin', - columns=['region', 'margin'], + component=model.operating_reserve_margin, + table='operating_reserve_margin', + columns=['region', 'tech_or_group', 'margin'], + validator_name='viable_rt', + validation_map=(0, 1), is_period_filtered=False, is_table_required=False, ), diff --git a/temoa/data_io/hybrid_loader.py b/temoa/data_io/hybrid_loader.py index 6f7e26b1c..2c129af0e 100644 --- a/temoa/data_io/hybrid_loader.py +++ b/temoa/data_io/hybrid_loader.py @@ -66,6 +66,8 @@ 'limit_capacity_share': 'region', 'limit_new_capacity_share': 'region', 'limit_resource': 'region', + 'planning_reserve_margin': 'region', + 'operating_reserve_margin': 'region', } @@ -284,9 +286,6 @@ def create_data_dict(self, myopic_index: MyopicIndex | None = None) -> dict[str, # Load simple config-based or myopic-specific values self._load_component_data(data, model.time_sequencing, [(self.config.time_sequencing,)]) self._load_component_data(data, model.days_per_period, [(self.config.days_per_period,)]) - self._load_component_data( - data, model.reserve_margin_method, [(self.config.reserve_margin,)] - ) if myopic_index: p0_result = cur.execute( "SELECT min(period) FROM time_period WHERE flag == 'f'" @@ -856,22 +855,6 @@ def _load_ramping_up( ) self._load_component_data(data, model.tech_upramping, tech_filtered) - def _load_rps_requirement( - self, - data: dict[str, object], - raw_data: Sequence[tuple[object, ...]], - filtered_data: Sequence[tuple[object, ...]], - ) -> None: - """Handles deprecation warning for renewable_portfolio_standard.""" - model = self.model - self._load_component_data(data, model.renewable_portfolio_standard, filtered_data) - if filtered_data: - logger.warning( - 'The renewable_portfolio_standard constraint is deprecated. Use ' - 'limit_activity_share instead. ' - 'The constraint has been applied but this feature may be removed in the future.' - ) - def _load_limit_tech_input_split( self, data: dict[str, object], diff --git a/temoa/db_schema/temoa_schema_v4_1.sql b/temoa/db_schema/temoa_schema_v4_1.sql new file mode 100644 index 000000000..eca18c1b7 --- /dev/null +++ b/temoa/db_schema/temoa_schema_v4_1.sql @@ -0,0 +1,994 @@ +PRAGMA foreign_keys = OFF; +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS metadata +( + element TEXT, + value INT, + notes TEXT, + PRIMARY KEY (element) +); +REPLACE INTO metadata +VALUES ('DB_MAJOR', 4, 'DB major version number'); +REPLACE INTO metadata +VALUES ('DB_MINOR', 1, 'DB minor version number'); + +CREATE TABLE IF NOT EXISTS metadata_real +( + element TEXT, + value REAL, + notes TEXT, + + PRIMARY KEY (element) +); +REPLACE INTO metadata_real +VALUES ('global_discount_rate', 0.05, 'Discount Rate for future costs'); +REPLACE INTO metadata_real +VALUES ('default_loan_rate', 0.05, 'Default Loan Rate if not specified in LoanRate table'); + +CREATE TABLE IF NOT EXISTS output_dual_variable +( + scenario TEXT, + constraint_name TEXT, + dual REAL, + PRIMARY KEY (constraint_name, scenario) +); +CREATE TABLE IF NOT EXISTS output_objective +( + scenario TEXT, + objective_name TEXT, + total_system_cost REAL +); +CREATE TABLE IF NOT EXISTS sector_label +( + sector TEXT PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS capacity_factor_process +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, season, tod, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS capacity_factor_tech +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + factor REAL, + notes TEXT, + PRIMARY KEY (region, season, tod, tech), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS capacity_to_activity +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + c2a REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS commodity +( + name TEXT + PRIMARY KEY, + flag TEXT + REFERENCES commodity_type (label), + description TEXT, + units TEXT +); +CREATE TABLE IF NOT EXISTS commodity_type +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO commodity_type +VALUES ('s', 'source commodity'); +REPLACE INTO commodity_type +VALUES ('a', 'annual commodity'); +REPLACE INTO commodity_type +VALUES ('p', 'physical commodity'); +REPLACE INTO commodity_type +VALUES ('d', 'demand commodity'); +REPLACE INTO commodity_type +VALUES ('e', 'emissions commodity'); +REPLACE INTO commodity_type +VALUES ('w', 'waste commodity'); +REPLACE INTO commodity_type +VALUES ('wa', 'waste annual commodity'); +REPLACE INTO commodity_type +VALUES ('wp', 'waste physical commodity'); +CREATE TABLE IF NOT EXISTS construction_input +( + region TEXT, + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS cost_emission +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + emis_comm TEXT NOT NULL + REFERENCES commodity (name), + cost REAL NOT NULL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm) +); +CREATE TABLE IF NOT EXISTS cost_fixed +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES time_period (period), + tech TEXT NOT NULL + REFERENCES technology (tech), + vintage INTEGER NOT NULL + REFERENCES time_period (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS cost_invest +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS cost_variable +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES time_period (period), + tech TEXT NOT NULL + REFERENCES technology (tech), + vintage INTEGER NOT NULL + REFERENCES time_period (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS demand +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + commodity TEXT + REFERENCES commodity (name), + demand REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, commodity) +); +CREATE TABLE IF NOT EXISTS demand_specific_distribution +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + demand_name TEXT + REFERENCES commodity (name), + dsd REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, demand_name), + CHECK (dsd >= 0 AND dsd <= 1) +); +CREATE TABLE IF NOT EXISTS end_of_life_output +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS efficiency +( + region TEXT, + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + efficiency REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +CREATE TABLE IF NOT EXISTS efficiency_variable +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + efficiency REAL, + notes TEXT, + PRIMARY KEY (region, season, tod, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +CREATE TABLE IF NOT EXISTS emission_activity +( + region TEXT, + emis_comm TEXT + REFERENCES commodity (name), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS emission_embodied +( + region TEXT, + emis_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS emission_end_of_life +( + region TEXT, + emis_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS existing_capacity +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS tech_group +( + group_name TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS loan_lifetime_process +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + lifetime REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS loan_rate +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS lifetime_process +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + lifetime REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS lifetime_tech +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + lifetime REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS operating_reserve_derate +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tech TEXT + REFERENCES technology (tech), + factor REAL, + notes TEXT, + PRIMARY KEY (region, season, tech), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS operating_reserve_margin +( + region TEXT, + tech_or_group TEXT, + margin REAL, + notes TEXT, + PRIMARY KEY (region, tech_or_group), + CHECK (margin >= 0) +); +CREATE TABLE IF NOT EXISTS operator +( + operator TEXT PRIMARY KEY, + notes TEXT +); +REPLACE INTO operator VALUES('e','equal to'); +REPLACE INTO operator VALUES('le','less than or equal to'); +REPLACE INTO operator VALUES('ge','greater than or equal to'); +CREATE TABLE IF NOT EXISTS limit_storage_level_fraction +( + region TEXT, + season TEXT, + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + fraction REAL, + notes TEXT, + CHECK (fraction >= 0 AND fraction <= 1), + PRIMARY KEY(region, season, tod, tech, operator) +); +CREATE TABLE IF NOT EXISTS limit_activity +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_activity_share +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_annual_capacity_factor +( + region TEXT, + tech_or_group TEXT, + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY (region, tech_or_group, vintage, output_comm, operator), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS limit_capacity +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_capacity_share +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_new_capacity +( + region TEXT, + tech_or_group TEXT, + vintage INTEGER + REFERENCES time_period (period), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + new_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, vintage, operator) +); +CREATE TABLE IF NOT EXISTS limit_new_capacity_share +( + region TEXT, + sub_group TEXT, + super_group TEXT, + vintage INTEGER + REFERENCES time_period (period), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, sub_group, super_group, vintage, operator) +); +CREATE TABLE IF NOT EXISTS limit_resource +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + cum_act REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_seasonal_capacity_factor +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY(region, season, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_tech_input_split +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE IF NOT EXISTS limit_tech_input_split_annual +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE IF NOT EXISTS limit_tech_output_split +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + output_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +CREATE TABLE IF NOT EXISTS limit_tech_output_split_annual +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + output_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +CREATE TABLE IF NOT EXISTS limit_emission +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + emis_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm, operator) +); +CREATE TABLE IF NOT EXISTS linked_tech +( + primary_region TEXT, + primary_tech TEXT + REFERENCES technology (tech), + emis_comm TEXT + REFERENCES commodity (name), + driven_tech TEXT + REFERENCES technology (tech), + notes TEXT, + PRIMARY KEY (primary_region, primary_tech, emis_comm) +); +CREATE TABLE IF NOT EXISTS output_curtailment +( + scenario TEXT, + region TEXT, + sector TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + curtailment REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS output_net_capacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + capacity REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS output_built_capacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + capacity REAL, + units TEXT, + PRIMARY KEY (region, scenario, tech, vintage) +); +CREATE TABLE IF NOT EXISTS output_retired_capacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + cap_eol REAL, + cap_early REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS output_flow_in +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + flow REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS output_flow_out +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + flow REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS output_storage_level +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + season TEXT, + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + level REAL, + units TEXT, + PRIMARY KEY (scenario, region, period, season, tod, tech, vintage) +); +CREATE TABLE IF NOT EXISTS planning_reserve_credit +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + credit REAL, + notes TEXT, + PRIMARY KEY (region, tech), + CHECK (credit >= 0 AND credit <= 1) +); +CREATE TABLE IF NOT EXISTS planning_reserve_margin +( + region TEXT, + tech_or_group TEXT, + margin REAL, + notes TEXT, + PRIMARY KEY (region, tech_or_group), + CHECK (margin >= 0) +); +CREATE TABLE IF NOT EXISTS ramp_down_hourly +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS ramp_up_hourly +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS region +( + region TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS storage_duration +( + region TEXT, + tech TEXT, + duration REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS lifetime_survival_curve +( + region TEXT NOT NULL, + period INTEGER NOT NULL, + tech TEXT NOT NULL + REFERENCES technology (tech), + vintage INTEGER NOT NULL + REFERENCES time_period (period), + fraction REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS technology_type +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO technology_type +VALUES ('p', 'production technology'); +REPLACE INTO technology_type +VALUES ('pb', 'baseload production technology'); +REPLACE INTO technology_type +VALUES ('ps', 'storage production technology'); +-- CREATE TABLE IF NOT EXISTS time_manual +-- ( +-- season TEXT +-- REFERENCES time_season (season), +-- tod TEXT +-- REFERENCES time_of_day (tod), +-- season_next TEXT +-- REFERENCES time_season (season), +-- tod_next TEXT +-- REFERENCES time_of_day (tod), +-- notes TEXT, +-- PRIMARY KEY (season, tod) +-- ); +CREATE TABLE IF NOT EXISTS time_of_day +( + sequence INTEGER UNIQUE, + tod TEXT + PRIMARY KEY, + hours REAL NOT NULL DEFAULT 1, + notes TEXT, + CHECK (hours > 0) +); +CREATE TABLE IF NOT EXISTS time_period +( + sequence INTEGER UNIQUE, + period INTEGER + PRIMARY KEY, + flag TEXT + REFERENCES time_period_type (label) +); +CREATE TABLE IF NOT EXISTS time_period_type +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO time_period_type +VALUES('e', 'existing vintages'); +REPLACE INTO time_period_type +VALUES('f', 'future'); +CREATE TABLE IF NOT EXISTS output_emission +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + emis_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + emission REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS tech_group_member +( + group_name TEXT + REFERENCES tech_group (group_name), + tech TEXT + REFERENCES technology (tech), + PRIMARY KEY (group_name, tech) +); +CREATE TABLE IF NOT EXISTS technology +( + tech TEXT NOT NULL PRIMARY KEY, + flag TEXT NOT NULL, + sector TEXT, + category TEXT, + sub_category TEXT, + unlim_cap INTEGER NOT NULL DEFAULT 0, + annual INTEGER NOT NULL DEFAULT 0, + curtail INTEGER NOT NULL DEFAULT 0, + retire INTEGER NOT NULL DEFAULT 0, + flex INTEGER NOT NULL DEFAULT 0, + exchange INTEGER NOT NULL DEFAULT 0, + seas_stor INTEGER NOT NULL DEFAULT 0, + description TEXT, + FOREIGN KEY (flag) REFERENCES technology_type (label) +); +CREATE TABLE IF NOT EXISTS output_cost +( + scenario TEXT, + region TEXT, + sector TEXT REFERENCES sector_label (sector), + period INTEGER REFERENCES time_period (period), + tech TEXT, + vintage INTEGER REFERENCES time_period (period), + d_invest REAL, + d_fixed REAL, + d_var REAL, + d_emiss REAL, + invest REAL, + fixed REAL, + var REAL, + emiss REAL, + units TEXT, + PRIMARY KEY (scenario, region, period, tech, vintage), + FOREIGN KEY (vintage) REFERENCES time_period (period) +); + +CREATE TABLE IF NOT EXISTS time_season +( + sequence INTEGER UNIQUE, + season TEXT, + segment_fraction REAL NOT NULL, + notes TEXT, + PRIMARY KEY (season), + CHECK (segment_fraction >= 0 AND segment_fraction <= 1) +); + +CREATE TABLE IF NOT EXISTS time_season_sequential +( + sequence INTEGER UNIQUE, + seas_seq TEXT, + season TEXT REFERENCES time_season(season), + segment_fraction REAL NOT NULL, + notes TEXT, + PRIMARY KEY (seas_seq), + CHECK (segment_fraction >= 0 AND segment_fraction <= 1) +); + +CREATE TABLE IF NOT EXISTS myopic_efficiency +( + base_year integer, + region text, + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + efficiency real, + lifetime integer, + PRIMARY KEY (region, input_comm, tech, vintage, output_comm) +); +-- for efficient searching by rtv: +CREATE INDEX IF NOT EXISTS region_tech_vintage ON myopic_efficiency (region, tech, vintage); + +CREATE TABLE IF NOT EXISTS output_flow_out_summary +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + flow REAL, + PRIMARY KEY (scenario, region, period, input_comm, tech, vintage, output_comm) +); + +COMMIT; +PRAGMA foreign_keys = ON; diff --git a/temoa/model_checking/validators.py b/temoa/model_checking/validators.py index 4db893b28..e84b524ce 100644 --- a/temoa/model_checking/validators.py +++ b/temoa/model_checking/validators.py @@ -39,7 +39,6 @@ 'validate_0to1', 'validate_efficiency', 'validate_linked_tech', - 'validate_reserve_margin', 'validate_tech_sets', ] @@ -337,17 +336,6 @@ def validate_efficiency( return False -def validate_reserve_margin(model: TemoaModel) -> None: - for r in model.planning_reserve_margin.sparse_keys(): - if all((r, p) not in model.process_reserve_periods for p in model.time_optimize): - logger.warning( - 'Planning reserve margin provided but there are no reserve technologies serving ' - 'this ' - 'region: %s', - (r, model.planning_reserve_margin[r]), - ) - - def validate_tech_sets(model: TemoaModel) -> None: """ Check tech sets for any forbidden intersections @@ -361,7 +349,6 @@ def validate_tech_sets(model: TemoaModel) -> None: check_no_intersection(model.tech_annual, model.tech_curtailment), check_no_intersection(model.tech_curtailment, model.tech_flex), check_no_intersection(model.tech_all, model.tech_group_names), - check_no_intersection(model.tech_uncap, model.tech_reserve), ) ): raise ValueError('Technology sets failed to validate. Check log file for details.') diff --git a/temoa/tutorial_assets/config_sample.toml b/temoa/tutorial_assets/config_sample.toml index df3d7b20a..4fd91766b 100644 --- a/temoa/tutorial_assets/config_sample.toml +++ b/temoa/tutorial_assets/config_sample.toml @@ -87,7 +87,7 @@ solver_name = "appsi_highs" save_excel = true # save the duals in the output Database (may slow execution slightly?) -save_duals = true +save_duals = false # save storage levels by time slice (may be a large amount of data) save_storage_levels = true @@ -136,19 +136,6 @@ time_sequencing = 'seasonal_timeslices' # E.g. 365 if all seasons collectively represent a year, 7 if modelling a single representative week. days_per_period = 365 -# How contributions to the planning reserve margin are calculated -# Options: -# 'static' -# Traditional planning reserve formulation. Contributions are independent of hourly availability: -# capacity value = net capacity * capacity credit -# 'dynamic' -# Contributions are available output including a capacity derate factor (e.g., forced outage rate). -# For most generators, contributions are available (derated) output in each time slice: -# capacity value = net capacity * reserve capacity derate * capacity factor -# For storage, contributions are (derated) actual output in each time slice: -# capacity value = flow out * reserve capacity derate -reserve_margin = 'dynamic' - # ------------------------------------ # SQLITE PERFORMANCE TUNING # ------------------------------------ diff --git a/temoa/tutorial_assets/utopia.sql b/temoa/tutorial_assets/utopia.sql index 1814f34f9..8117d2c40 100644 --- a/temoa/tutorial_assets/utopia.sql +++ b/temoa/tutorial_assets/utopia.sql @@ -415,25 +415,25 @@ REPLACE INTO "sector_label" VALUES('transport',NULL); REPLACE INTO "sector_label" VALUES('commercial',NULL); REPLACE INTO "sector_label" VALUES('residential',NULL); REPLACE INTO "sector_label" VALUES('industrial',NULL); -REPLACE INTO "technology" VALUES('IMPDSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported diesel'); -REPLACE INTO "technology" VALUES('IMPGSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported gasoline'); -REPLACE INTO "technology" VALUES('IMPHCO1','p','supply','coal','',1,0,0,0,0,0,0,0,' imported coal'); -REPLACE INTO "technology" VALUES('IMPOIL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported crude oil'); -REPLACE INTO "technology" VALUES('IMPURN1','p','supply','nuclear','',1,0,0,0,0,0,0,0,' imported uranium'); -REPLACE INTO "technology" VALUES('IMPFEQ','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported fossil equivalent'); -REPLACE INTO "technology" VALUES('IMPHYD','p','supply','hydro','',1,0,0,0,0,0,0,0,' imported water -- doesnt exist in Utopia'); -REPLACE INTO "technology" VALUES('E01','pb','electric','coal','',0,0,0,0,0,0,0,0,' coal power plant'); -REPLACE INTO "technology" VALUES('E21','pb','electric','nuclear','',0,0,0,0,0,0,0,0,' nuclear power plant'); -REPLACE INTO "technology" VALUES('E31','pb','electric','hydro','',0,0,0,0,0,0,0,0,' hydro power'); -REPLACE INTO "technology" VALUES('E51','ps','electric','electric','',0,0,0,0,0,0,0,0,' electric storage'); -REPLACE INTO "technology" VALUES('E70','p','electric','petroleum','',0,0,0,0,0,0,0,0,' diesel power plant'); -REPLACE INTO "technology" VALUES('RHE','p','residential','electric','',0,0,0,0,0,0,0,0,' electric residential heating'); -REPLACE INTO "technology" VALUES('RHO','p','residential','petroleum','',0,0,0,0,0,0,0,0,' diesel residential heating'); -REPLACE INTO "technology" VALUES('RL1','p','residential','electric','',0,0,0,0,0,0,0,0,' residential lighting'); -REPLACE INTO "technology" VALUES('SRE','p','supply','petroleum','',0,0,0,0,0,0,0,0,' crude oil processor'); -REPLACE INTO "technology" VALUES('TXD','p','transport','petroleum','',0,0,0,0,0,0,0,0,' diesel powered vehicles'); -REPLACE INTO "technology" VALUES('TXE','p','transport','electric','',0,0,0,0,0,0,0,0,' electric powered vehicles'); -REPLACE INTO "technology" VALUES('TXG','p','transport','petroleum','',0,0,0,0,0,0,0,0,' gasoline powered vehicles'); +REPLACE INTO "technology" VALUES('IMPDSL1','p','supply','petroleum','',1,0,0,0,0,0,0,' imported diesel'); +REPLACE INTO "technology" VALUES('IMPGSL1','p','supply','petroleum','',1,0,0,0,0,0,0,' imported gasoline'); +REPLACE INTO "technology" VALUES('IMPHCO1','p','supply','coal','',1,0,0,0,0,0,0,' imported coal'); +REPLACE INTO "technology" VALUES('IMPOIL1','p','supply','petroleum','',1,0,0,0,0,0,0,' imported crude oil'); +REPLACE INTO "technology" VALUES('IMPURN1','p','supply','nuclear','',1,0,0,0,0,0,0,' imported uranium'); +REPLACE INTO "technology" VALUES('IMPFEQ','p','supply','petroleum','',1,0,0,0,0,0,0,' imported fossil equivalent'); +REPLACE INTO "technology" VALUES('IMPHYD','p','supply','hydro','',1,0,0,0,0,0,0,' imported water -- doesnt exist in Utopia'); +REPLACE INTO "technology" VALUES('E01','pb','electric','coal','',0,0,0,0,0,0,0,' coal power plant'); +REPLACE INTO "technology" VALUES('E21','pb','electric','nuclear','',0,0,0,0,0,0,0,' nuclear power plant'); +REPLACE INTO "technology" VALUES('E31','pb','electric','hydro','',0,0,0,0,0,0,0,' hydro power'); +REPLACE INTO "technology" VALUES('E51','ps','electric','electric','',0,0,0,0,0,0,0,' electric storage'); +REPLACE INTO "technology" VALUES('E70','p','electric','petroleum','',0,0,0,0,0,0,0,' diesel power plant'); +REPLACE INTO "technology" VALUES('RHE','p','residential','electric','',0,0,0,0,0,0,0,' electric residential heating'); +REPLACE INTO "technology" VALUES('RHO','p','residential','petroleum','',0,0,0,0,0,0,0,' diesel residential heating'); +REPLACE INTO "technology" VALUES('RL1','p','residential','electric','',0,0,0,0,0,0,0,' residential lighting'); +REPLACE INTO "technology" VALUES('SRE','p','supply','petroleum','',0,0,0,0,0,0,0,' crude oil processor'); +REPLACE INTO "technology" VALUES('TXD','p','transport','petroleum','',0,0,0,0,0,0,0,' diesel powered vehicles'); +REPLACE INTO "technology" VALUES('TXE','p','transport','electric','',0,0,0,0,0,0,0,' electric powered vehicles'); +REPLACE INTO "technology" VALUES('TXG','p','transport','petroleum','',0,0,0,0,0,0,0,' gasoline powered vehicles'); REPLACE INTO "time_of_day" (sequence, tod, hours) VALUES(1,'day',16); REPLACE INTO "time_of_day" (sequence, tod, hours) VALUES(2,'night',8); REPLACE INTO "time_period" VALUES(1,1960,'e'); diff --git a/temoa/types/__init__.py b/temoa/types/__init__.py index 78d4a98ac..e6f7e913f 100644 --- a/temoa/types/__init__.py +++ b/temoa/types/__init__.py @@ -37,8 +37,7 @@ 'ProcessOutputsByInputDict', 'ProcessOutputsDict', 'ProcessPeriodsDict', - 'ProcessReservePeriodsDict', - 'ProcessTechsDict', + 'ReserveProcessesDict', 'ProcessVintagesDict', 'GroupBuiltProcessesDict', 'GroupActiveProcessesDict', @@ -117,11 +116,10 @@ ProcessOutputsByInputDict, ProcessOutputsDict, ProcessPeriodsDict, - ProcessReservePeriodsDict, - ProcessTechsDict, ProcessVintagesDict, RampDownVintagesDict, RampUpVintagesDict, + ReserveProcessesDict, RetirementPeriodsDict, RetirementProductionProcessesDict, SeasonalStorageDict, diff --git a/temoa/types/dict_types.py b/temoa/types/dict_types.py index 380e4f772..477c1f119 100644 --- a/temoa/types/dict_types.py +++ b/temoa/types/dict_types.py @@ -17,8 +17,9 @@ ProcessOutputsByInputDict = dict[ tuple[Region, Period, Technology, Vintage, Commodity], set[Commodity] ] -ProcessTechsDict = dict[tuple[Region, Period, Commodity], set[Technology]] -ProcessReservePeriodsDict = dict[tuple[Region, Period], set[tuple[Technology, Vintage]]] +ReserveProcessesDict = dict[ + tuple[Region, Period, Technology], set[tuple[Region, Technology, Vintage]] +] ProcessPeriodsDict = dict[tuple[Region, Technology, Vintage], set[Period]] RetirementPeriodsDict = dict[tuple[Region, Technology, Vintage], set[Period]] ProcessVintagesDict = dict[tuple[Region, Period, Technology], set[Vintage]] diff --git a/temoa/types/model_types.py b/temoa/types/model_types.py index 108a70f62..491ebb249 100644 --- a/temoa/types/model_types.py +++ b/temoa/types/model_types.py @@ -105,7 +105,6 @@ class TemoaModelProtocol(Protocol): tech_all: Set tech_production: Set tech_storage: Set - tech_reserve: Set tech_exchange: Set # Commodity sets @@ -176,7 +175,6 @@ class TemoaModel(AbstractModel): tech_baseload: Set tech_annual: Set tech_storage: Set - tech_reserve: Set tech_exchange: Set tech_uncap: Set tech_with_capacity: Set diff --git a/temoa/utilities/migrate_v4_to_v4_1.py b/temoa/utilities/migrate_v4_to_v4_1.py new file mode 100644 index 000000000..7d5ed7eff --- /dev/null +++ b/temoa/utilities/migrate_v4_to_v4_1.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 +""" +migrate_v4_to_v4_1.py + +Migrates a Temoa v4 database (SQLite or SQL dump) to v4.1 schema format. + +Key changes from v4 to v4.1: + - capacity_credit -> planning_reserve_credit (period, vintage dropped; AVG credit) + - reserve_capacity_derate -> operating_reserve_derate (vintage dropped; AVG factor) + - planning_reserve_margin -> planning_reserve_margin (tech_or_group from reserve-flagged techs) + - rps_requirement -> limit_activity_share (tech_group=sub_group, reserve=super_group) + - operating_reserve_margin is new (no v4 equivalent) + - DB_MINOR bumped: 0 -> 1 +""" + +from __future__ import annotations + +import argparse +import os +import sqlite3 +import tempfile +from pathlib import Path + + +def get_table_cols(conn: sqlite3.Connection, table: str) -> list[str]: + return [r[1] for r in conn.execute(f'PRAGMA table_info({table})').fetchall()] + + +def _migrate_planning_reserve_credit( + con_old: sqlite3.Connection, con_new: sqlite3.Connection +) -> int: + """Migrate capacity_credit -> planning_reserve_credit, dropping period and vintage.""" + try: + rows = con_old.execute( + 'SELECT region, tech, AVG(credit), notes FROM capacity_credit GROUP BY region, tech' + ).fetchall() + except sqlite3.OperationalError: + return 0 + if not rows: + return 0 + print( + 'WARNING: Dropping period and vintage from capacity_credit; ' + 'using average credit for each region/tech' + ) + con_new.executemany( + 'INSERT OR REPLACE INTO planning_reserve_credit (region, tech, credit, notes) ' + 'VALUES (?, ?, ?, ?)', + rows, + ) + print(f'Migrated {len(rows)} rows: capacity_credit -> planning_reserve_credit') + return len(rows) + + +def _migrate_operating_reserve_derate( + con_old: sqlite3.Connection, con_new: sqlite3.Connection +) -> int: + """Migrate reserve_capacity_derate -> operating_reserve_derate, dropping vintage.""" + try: + rows = con_old.execute( + 'SELECT region, season, tech, AVG(factor), notes ' + 'FROM reserve_capacity_derate GROUP BY region, season, tech' + ).fetchall() + except sqlite3.OperationalError: + return 0 + if not rows: + return 0 + print( + 'WARNING: Dropping vintage from reserve_capacity_derate; ' + 'using average factor for each region/season/tech' + ) + con_new.executemany( + 'INSERT OR REPLACE INTO operating_reserve_derate (region, season, tech, factor, notes) ' + 'VALUES (?, ?, ?, ?, ?)', + rows, + ) + print(f'Migrated {len(rows)} rows: reserve_capacity_derate -> operating_reserve_derate') + return len(rows) + + +RESERVE_GROUP_NAME = 'migrated_reserve_techs' + + +def _build_reserve_tech_group( + con_old: sqlite3.Connection, con_new: sqlite3.Connection +) -> list[str]: + """Create a tech_group from techs with reserve=1; return the list of reserve tech names.""" + try: + reserve_techs = [ + r[0] + for r in con_old.execute('SELECT tech FROM technology WHERE reserve > 0').fetchall() + ] + except sqlite3.OperationalError: + return [] + if not reserve_techs: + return [] + con_new.execute( + 'INSERT OR IGNORE INTO tech_group (group_name) VALUES (?)', (RESERVE_GROUP_NAME,) + ) + con_new.executemany( + 'INSERT OR IGNORE INTO tech_group_member (group_name, tech) VALUES (?, ?)', + [(RESERVE_GROUP_NAME, t) for t in reserve_techs], + ) + print(f'Built reserve tech group "{RESERVE_GROUP_NAME}" with {len(reserve_techs)} member(s)') + return reserve_techs + + +def _migrate_planning_reserve_margin( + con_old: sqlite3.Connection, con_new: sqlite3.Connection, reserve_group_built: bool +) -> int: + """Migrate planning_reserve_margin using the reserve tech group as tech_or_group.""" + try: + rows = con_old.execute( + 'SELECT region, margin, notes FROM planning_reserve_margin' + ).fetchall() + except sqlite3.OperationalError: + return 0 + if not rows: + return 0 + if not reserve_group_built: + print( + f'WARNING: planning_reserve_margin has {len(rows)} row(s) but no reserve-flagged ' + 'techs found; skipping migration. Populate planning_reserve_margin manually.' + ) + return 0 + migrated = [(region, RESERVE_GROUP_NAME, margin, notes) for region, margin, notes in rows] + con_new.executemany( + 'INSERT OR REPLACE INTO planning_reserve_margin (region, tech_or_group, margin, notes) ' + 'VALUES (?, ?, ?, ?)', + migrated, + ) + print( + f'Migrated {len(migrated)} rows: planning_reserve_margin' + f' (tech_or_group={RESERVE_GROUP_NAME!r})' + ) + return len(migrated) + + +def _migrate_rps_requirement( + con_old: sqlite3.Connection, con_new: sqlite3.Connection, reserve_group_built: bool +) -> int: + """Migrate rps_requirement -> limit_activity_share. + + sub_group = tech_group (the RPS-eligible group) + super_group = RESERVE_GROUP_NAME (all reserve-flagged techs) + operator = 'ge' (must meet minimum share) + """ + try: + rows = con_old.execute( + 'SELECT region, period, tech_group, requirement, notes FROM rps_requirement' + ).fetchall() + except sqlite3.OperationalError: + return 0 + if not rows: + return 0 + if not reserve_group_built: + print( + 'WARNING: rps_requirement has rows but no reserve-flagged techs found to use as ' + 'super_group; skipping migration. Populate limit_activity_share manually.' + ) + return 0 + migrated = [ + (region, period, sub_group, RESERVE_GROUP_NAME, 'ge', requirement, notes) + for region, period, sub_group, requirement, notes in rows + ] + con_new.executemany( + 'INSERT OR REPLACE INTO limit_activity_share ' + '(region, period, sub_group, super_group, operator, share, notes) ' + 'VALUES (?, ?, ?, ?, ?, ?, ?)', + migrated, + ) + print(f'Migrated {len(migrated)} rows: rps_requirement -> limit_activity_share') + return len(migrated) + + +def _migrate_common_tables(con_old: sqlite3.Connection, con_new: sqlite3.Connection) -> int: + """Copy all tables that exist in both schemas, skipping custom-handled ones.""" + skip = { + 'capacity_credit', + 'reserve_capacity_derate', + 'rps_requirement', + 'planning_reserve_margin', + 'metadata', + 'operator', + 'commodity_type', + } + old_tables = { + r[0] + for r in con_old.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall() + } + new_tables = { + r[0] + for r in con_new.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall() + } + total = 0 + for table in sorted(old_tables & new_tables): + if table.startswith('sqlite_') or table in skip: + continue + old_cols = set(get_table_cols(con_old, table)) + new_cols = get_table_cols(con_new, table) + shared = [c for c in new_cols if c in old_cols] + if not shared: + continue + col_list = ', '.join(shared) + rows = con_old.execute(f'SELECT {col_list} FROM {table}').fetchall() + if not rows: + continue + placeholders = ', '.join(['?'] * len(shared)) + con_new.executemany( + f'INSERT OR REPLACE INTO {table} ({col_list}) VALUES ({placeholders})', rows + ) + print(f'Copied {len(rows)} rows: {table}') + total += len(rows) + return total + + +def execute_v4_to_v4_1_migration(con_old: sqlite3.Connection, con_new: sqlite3.Connection) -> None: + """Run all v4 -> v4.1 migration steps.""" + total = 0 + print('--- Migrating common tables ---') + total += _migrate_common_tables(con_old, con_new) + + print('--- Migrating restructured tables ---') + total += _migrate_planning_reserve_credit(con_old, con_new) + total += _migrate_operating_reserve_derate(con_old, con_new) + + print('--- Building reserve tech group ---') + reserve_techs = _build_reserve_tech_group(con_old, con_new) + reserve_group_built = len(reserve_techs) > 0 + total += _migrate_planning_reserve_margin(con_old, con_new, reserve_group_built) + total += _migrate_rps_requirement(con_old, con_new, reserve_group_built) + + con_new.execute("INSERT OR REPLACE INTO metadata VALUES ('DB_MAJOR', 4, 'DB major version')") + con_new.execute("INSERT OR REPLACE INTO metadata VALUES ('DB_MINOR', 1, 'DB minor version')") + print(f'Total rows successfully copied: {total}') + + +def migrate_database(source_path: Path, schema_path: Path, output_path: Path) -> None: + if not source_path.is_file(): + raise FileNotFoundError(f'Input database not found: {source_path}') + if not schema_path.is_file(): + raise FileNotFoundError(f'Schema file not found: {schema_path}') + + fd, temp_str = tempfile.mkstemp( + suffix='.sqlite', prefix='temp_v4_1_migration_', dir=output_path.parent + ) + os.close(fd) + temp_path = Path(temp_str) + + con_old = sqlite3.connect(source_path) + con_new = sqlite3.connect(temp_path) + try: + con_new.executescript(schema_path.read_text(encoding='utf-8')) + con_new.execute('PRAGMA foreign_keys = 0;') + execute_v4_to_v4_1_migration(con_old, con_new) + con_new.commit() + con_new.execute('PRAGMA foreign_keys = 1;') + con_old.close() + con_new.close() + os.replace(temp_path, output_path) + except Exception: + if temp_path.exists(): + os.remove(temp_path) + raise + finally: + con_old.close() + con_new.close() + + +def migrate_sql_dump(source_path: Path, schema_path: Path, output_path: Path) -> None: + if not source_path.is_file(): + raise FileNotFoundError(f'Input SQL dump not found: {source_path}') + if not schema_path.is_file(): + raise FileNotFoundError(f'Schema file not found: {schema_path}') + + con_old = sqlite3.connect(':memory:') + con_new = sqlite3.connect(':memory:') + temp_path: Path | None = None + + try: + con_old.executescript(source_path.read_text(encoding='utf-8')) + con_new.executescript(schema_path.read_text(encoding='utf-8')) + + con_new.execute('PRAGMA foreign_keys = 0;') + execute_v4_to_v4_1_migration(con_old, con_new) + con_new.commit() + con_new.execute('PRAGMA foreign_keys = 1;') + + fd, temp_str = tempfile.mkstemp( + suffix='.sql', prefix='temp_v4_1_sql_', dir=output_path.parent + ) + temp_path = Path(temp_str) + with os.fdopen(fd, 'w', encoding='utf-8') as f: + for line in con_new.iterdump(): + f.write(line + '\n') + f.flush() + os.fsync(f.fileno()) + os.replace(temp_path, output_path) + except Exception: + if temp_path is not None and temp_path.exists(): + os.remove(temp_path) + raise + finally: + con_old.close() + con_new.close() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Migrate Temoa database from v4 to v4.1') + parser.add_argument('--input', '-i', required=True, help='Input DB or SQL file') + parser.add_argument('--schema', '-s', required=True, help='Path to v4.1 schema SQL') + parser.add_argument('--output', '-o', required=True, help='Output DB or SQL file') + parser.add_argument('--type', choices=['db', 'sql'], required=True, help='Migration type') + args = parser.parse_args() + + input_path = Path(args.input) + schema_path = Path(args.schema) + output_path = Path(args.output) + + if args.type == 'db': + migrate_database(input_path, schema_path, output_path) + else: + migrate_sql_dump(input_path, schema_path, output_path) + print('Migration complete.') diff --git a/tests/conftest.py b/tests/conftest.py index 444aebd50..c7c97098c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,7 @@ from _pytest.config import Config from pyomo.opt import SolverResults +from temoa.__about__ import DB_SCHEMA from temoa._internal.temoa_sequencer import TemoaSequencer from temoa.core.config import TemoaConfig from temoa.core.model import TemoaModel @@ -37,7 +38,7 @@ # Central paths TEST_DATA_PATH = Path(__file__).parent / 'testing_data' TEST_OUTPUT_PATH = Path(__file__).parent / 'testing_outputs' -SCHEMA_PATH = Path(__file__).parent.parent / 'temoa' / 'db_schema' / 'temoa_schema_v4.sql' +SCHEMA_PATH = Path(__file__).parent.parent / 'temoa' / 'db_schema' / DB_SCHEMA def _build_test_db( @@ -101,6 +102,7 @@ def refresh_databases() -> None: # Feature tests (separate for temporal consistency) ('emissions.sql', 'emissions.sqlite'), ('materials.sql', 'materials.sqlite'), + ('reserve_margins.sql', 'reserve_margins.sqlite'), ('simple_linked_tech.sql', 'simple_linked_tech.sqlite'), ('storageville.sql', 'storageville.sqlite'), ('test_week.sql', 'test_week.sqlite'), diff --git a/tests/test_reserve_margins.py b/tests/test_reserve_margins.py new file mode 100644 index 000000000..0dcab64a4 --- /dev/null +++ b/tests/test_reserve_margins.py @@ -0,0 +1,103 @@ +from __future__ import annotations + +import logging +from pathlib import Path +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + +import pytest + +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core.config import TemoaConfig +from temoa.core.modes import TemoaMode +from tests.utilities.compare_lp import LpDiff, compare_lp_files + +logger = logging.getLogger(__name__) + +TEST_CONFIG = Path(__file__).parent / 'testing_configs' / 'config_reserve_margins.toml' +CACHED_LP = Path(__file__).parent / 'testing_data' / 'reserve_margins.lp' + + +# --------------------------------------------------------------------------- +# Fixture +# --------------------------------------------------------------------------- + + +@pytest.fixture(scope='module') +def reserve_run(tmp_path_factory: pytest.TempPathFactory) -> tuple[TemoaModel, Path]: + """Build, solve, and return (model, lp_path) for the reserve_margins scenario.""" + tmp = tmp_path_factory.mktemp('reserve_margins') + config = TemoaConfig.build_config( + config_file=TEST_CONFIG, + output_path=tmp, + silent=True, + ) + config.save_lp_file = True + + seq = TemoaSequencer(config=config, mode_override=TemoaMode.BUILD_ONLY) + instance = seq.build_model() + + lp_files = list(tmp.glob('*.lp')) + assert lp_files, 'No LP file was written to the output directory' + return instance, lp_files[0] + + +def test_planning_ab_group_includes_exchange(reserve_run: tuple[TemoaModel, Path]) -> None: + model, _ = reserve_run + exchange_regions = { + r + for r_g, p, t_g in model.planning_reserve_processes + if r_g == 'A+B' + for r, t, v in model.planning_reserve_processes[r_g, p, t_g] + if '-' in r + } + assert all(r in exchange_regions for r in ['A-C', 'C-A', 'B-C', 'C-B']), ( + 'Exchange regions A-C / C-A / B-C / C-B not auto-included in planning reserve group A+B' + ) + + +def test_single_region_a_includes_exchange(reserve_run: tuple[TemoaModel, Path]) -> None: + model, _ = reserve_run + exchange_regions = { + r + for r_g, p, t_g in model.operating_reserve_processes + if r_g == 'A' and t_g == 'elec_A' + for r, t, v in model.operating_reserve_processes[r_g, p, t_g] + if '-' in r + } + assert all(r in exchange_regions for r in ['A-C', 'C-A', 'B-A', 'A-B']), ( + 'Exchange regions A-C / C-A / B-A / A-B not auto-included in operating reserve group A' + ) + + +def test_single_tech_region_a_not_includes_exchange(reserve_run: tuple[TemoaModel, Path]) -> None: + model, _ = reserve_run + exchange_regions = { + r + for r_g, p, t_g in model.planning_reserve_processes + if r_g == 'A' and t_g == 'NGCC' + for r, t, v in model.planning_reserve_processes[r_g, p, t_g] + if '-' in r + } + assert not exchange_regions, ( + 'Single-region planning margin on A should ' + f'not include exchange regions: {exchange_regions}' + ) + + +def test_lp_matches(reserve_run: tuple[TemoaModel, Path]) -> None: + _, lp_path = reserve_run + + if not CACHED_LP.exists(): + import shutil + + shutil.copy(lp_path, CACHED_LP) + pytest.skip( + f'No cached LP found — saved current output as new cache: {CACHED_LP}. ' + 'Re-run the test to validate against it.' + ) + + diff: LpDiff = compare_lp_files(CACHED_LP, lp_path) + assert diff.is_identical, f'LP file differs from cached ({CACHED_LP.name}):\n{diff.summary()}' diff --git a/tests/test_v4_1_migration.py b/tests/test_v4_1_migration.py new file mode 100644 index 000000000..47e47192f --- /dev/null +++ b/tests/test_v4_1_migration.py @@ -0,0 +1,186 @@ +import contextlib +import sqlite3 +import subprocess +import sys +from pathlib import Path + +import pytest + +from temoa.utilities.migrate_v4_to_v4_1 import migrate_database, migrate_sql_dump + +REPO_ROOT = Path(__file__).parents[1] +UTILITIES_DIR = REPO_ROOT / 'temoa' / 'utilities' +SCHEMA_V4 = REPO_ROOT / 'temoa' / 'db_schema' / 'temoa_schema_v4.sql' +SCHEMA_V4_1 = REPO_ROOT / 'temoa' / 'db_schema' / 'temoa_schema_v4_1.sql' +MOCK_DATA_V4 = REPO_ROOT / 'tests' / 'testing_data' / 'migration_v4_mock.sql' +MIGRATION_SCRIPT = UTILITIES_DIR / 'migrate_v4_to_v4_1.py' + + +def _make_v4_db(tmp_path: Path) -> Path: + """Create a v4 SQLite database populated with mock data.""" + db = tmp_path / 'test_v4.sqlite' + with contextlib.closing(sqlite3.connect(db)) as conn: + conn.execute('PRAGMA foreign_keys = OFF') + conn.executescript(SCHEMA_V4.read_text()) + conn.executescript(MOCK_DATA_V4.read_text()) + conn.execute('PRAGMA foreign_keys = ON') + return db + + +def _verify_migrated_db(conn: sqlite3.Connection) -> None: + # Metadata version updated + major = conn.execute("SELECT value FROM metadata WHERE element='DB_MAJOR'").fetchone()[0] + minor = conn.execute("SELECT value FROM metadata WHERE element='DB_MINOR'").fetchone()[0] + assert major == 4 + assert minor == 1 + + # planning_reserve_credit: capacity_credit aggregated by (region, tech), period/vintage dropped + credits = { + (r[0], r[1]): r[2] + for r in conn.execute('SELECT region, tech, credit FROM planning_reserve_credit').fetchall() + } + # GasTurbine had credit 0.85 (2030) and 0.80 (2040) -> AVG = 0.825 + assert ('R1', 'GasTurbine') in credits + assert credits[('R1', 'GasTurbine')] == pytest.approx(0.825) + assert credits[('R1', 'WindFarm')] == pytest.approx(0.25) + + # operating_reserve_derate: reserve_capacity_derate aggregated by (region, season, tech) + derates = { + (r[0], r[1], r[2]): r[3] + for r in conn.execute( + 'SELECT region, season, tech, factor FROM operating_reserve_derate' + ).fetchall() + } + assert ('R1', 'summer', 'GasTurbine') in derates + assert derates[('R1', 'summer', 'GasTurbine')] == pytest.approx(0.95) + assert derates[('R1', 'summer', 'WindFarm')] == pytest.approx(0.20) + assert derates[('R1', 'winter', 'WindFarm')] == pytest.approx(0.10) + + # Removed v4 tables must not be present + tables = { + r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall() + } + assert 'capacity_credit' not in tables + assert 'reserve_capacity_derate' not in tables + assert 'rps_requirement' not in tables + + # planning_reserve_margin: migrated using reserve tech group as tech_or_group + from temoa.utilities.migrate_v4_to_v4_1 import RESERVE_GROUP_NAME + + margins = conn.execute( + 'SELECT region, tech_or_group, margin FROM planning_reserve_margin' + ).fetchall() + assert len(margins) == 1 + assert margins[0] == ('R1', RESERVE_GROUP_NAME, pytest.approx(0.15)) + + # reserve tech group must exist with GasTurbine and WindFarm (reserve=1), not CoalPlant + members = { + r[0] + for r in conn.execute( + 'SELECT tech FROM tech_group_member WHERE group_name = ?', (RESERVE_GROUP_NAME,) + ).fetchall() + } + assert 'GasTurbine' in members + assert 'WindFarm' in members + assert 'CoalPlant' not in members + + # rps_requirement: migrated to limit_activity_share, one row per period + activity_shares = { + (r[0], r[1], r[2]): r[4] + for r in conn.execute( + 'SELECT region, period, sub_group, super_group, share FROM limit_activity_share' + ).fetchall() + } + assert ('R1', 2030, 'renewables') in activity_shares + assert activity_shares[('R1', 2030, 'renewables')] == pytest.approx(0.30) + assert activity_shares[('R1', 2040, 'renewables')] == pytest.approx(0.40) + + # Common table data preserved + efficiencies = conn.execute('SELECT region, tech, vintage FROM efficiency').fetchall() + assert len(efficiencies) == 3 + + +def test_v4_1_migration_db(tmp_path: Path) -> None: + """Test SQLite DB migration from v4 to v4.1.""" + db_v4 = _make_v4_db(tmp_path) + db_v4_1 = tmp_path / 'test_v4_1.sqlite' + + subprocess.run( + [ + sys.executable, + str(MIGRATION_SCRIPT), + '--type', + 'db', + '--input', + str(db_v4), + '--schema', + str(SCHEMA_V4_1), + '--output', + str(db_v4_1), + ], + check=True, + ) + + with contextlib.closing(sqlite3.connect(db_v4_1)) as conn: + _verify_migrated_db(conn) + + +def test_v4_1_migration_sql(tmp_path: Path) -> None: + """Test SQL dump migration from v4 to v4.1.""" + db_v4 = _make_v4_db(tmp_path) + + sql_v4 = tmp_path / 'test_v4.sql' + with open(sql_v4, 'w') as f: + with contextlib.closing(sqlite3.connect(db_v4)) as conn: + for line in conn.iterdump(): + f.write(line + '\n') + + sql_v4_1 = tmp_path / 'test_v4_1.sql' + subprocess.run( + [ + sys.executable, + str(MIGRATION_SCRIPT), + '--type', + 'sql', + '--input', + str(sql_v4), + '--schema', + str(SCHEMA_V4_1), + '--output', + str(sql_v4_1), + ], + check=True, + ) + + with contextlib.closing(sqlite3.connect(':memory:')) as conn: + conn.executescript(sql_v4_1.read_text()) + _verify_migrated_db(conn) + + +def test_v4_1_migration_db_inthread(tmp_path: Path) -> None: + """In-process variant of test_v4_1_migration_db for coverage.""" + db_v4 = _make_v4_db(tmp_path) + db_v4_1 = tmp_path / 'test_v4_1.sqlite' + + migrate_database(db_v4, SCHEMA_V4_1, db_v4_1) + + with contextlib.closing(sqlite3.connect(db_v4_1)) as conn: + _verify_migrated_db(conn) + + +def test_v4_1_migration_sql_inthread(tmp_path: Path) -> None: + """In-process variant of test_v4_1_migration_sql for coverage.""" + db_v4 = _make_v4_db(tmp_path) + + sql_v4 = tmp_path / 'test_v4.sql' + with open(sql_v4, 'w') as f: + with contextlib.closing(sqlite3.connect(db_v4)) as conn: + for line in conn.iterdump(): + f.write(line + '\n') + + sql_v4_1 = tmp_path / 'test_v4_1.sql' + migrate_sql_dump(sql_v4, SCHEMA_V4_1, sql_v4_1) + + with contextlib.closing(sqlite3.connect(':memory:')) as conn: + conn.executescript(sql_v4_1.read_text()) + _verify_migrated_db(conn) diff --git a/tests/testing_configs/config_annualised_demand.toml b/tests/testing_configs/config_annualised_demand.toml index 198a7982f..847b1de38 100644 --- a/tests/testing_configs/config_annualised_demand.toml +++ b/tests/testing_configs/config_annualised_demand.toml @@ -9,7 +9,6 @@ save_duals = false save_lp_file = false time_sequencing = "representative_periods" days_per_period = 365 -reserve_margin = "static" [MGA] cost_epsilon = 0.03 diff --git a/tests/testing_configs/config_emissions.toml b/tests/testing_configs/config_emissions.toml index a3ec86a4b..b63e2c38a 100644 --- a/tests/testing_configs/config_emissions.toml +++ b/tests/testing_configs/config_emissions.toml @@ -9,7 +9,6 @@ save_duals = false save_lp_file = false time_sequencing = "seasonal_timeslices" days_per_period = 365 -reserve_margin = "static" [MGA] slack = 0.1 diff --git a/tests/testing_configs/config_link_test.toml b/tests/testing_configs/config_link_test.toml index 88192e4f4..93356f666 100644 --- a/tests/testing_configs/config_link_test.toml +++ b/tests/testing_configs/config_link_test.toml @@ -12,7 +12,6 @@ save_duals = false save_lp_file = false time_sequencing = "seasonal_timeslices" days_per_period = 365 -reserve_margin = "static" [MGA] slack = 0.1 diff --git a/tests/testing_configs/config_materials.toml b/tests/testing_configs/config_materials.toml index 3eae4926f..6567dd309 100644 --- a/tests/testing_configs/config_materials.toml +++ b/tests/testing_configs/config_materials.toml @@ -9,7 +9,6 @@ save_duals = false save_lp_file = false time_sequencing = "seasonal_timeslices" days_per_period = 365 -reserve_margin = "static" [MGA] slack = 0.1 diff --git a/tests/testing_configs/config_mediumville.toml b/tests/testing_configs/config_mediumville.toml index 935abdbaa..e6d214edd 100644 --- a/tests/testing_configs/config_mediumville.toml +++ b/tests/testing_configs/config_mediumville.toml @@ -9,7 +9,6 @@ save_duals = false save_lp_file = false time_sequencing = "seasonal_timeslices" days_per_period = 365 -reserve_margin = "static" [MGA] slack = 0.1 diff --git a/tests/testing_configs/config_myopic_capacities.toml b/tests/testing_configs/config_myopic_capacities.toml index 77b49abde..5787108dd 100644 --- a/tests/testing_configs/config_myopic_capacities.toml +++ b/tests/testing_configs/config_myopic_capacities.toml @@ -10,7 +10,6 @@ save_duals = false save_lp_file = false time_sequencing = "seasonal_timeslices" days_per_period = 365 -reserve_margin = "static" [MGA] slack = 0.1 diff --git a/tests/testing_configs/config_reserve_margins.toml b/tests/testing_configs/config_reserve_margins.toml new file mode 100644 index 000000000..7cf68aee0 --- /dev/null +++ b/tests/testing_configs/config_reserve_margins.toml @@ -0,0 +1,11 @@ +scenario = "reserve_margins" +scenario_mode = "perfect_foresight" +input_database = "tests/testing_outputs/reserve_margins.sqlite" +output_database = "tests/testing_outputs/reserve_margins.sqlite" +neos = false +solver_name = "appsi_highs" +save_excel = false +save_duals = false +save_lp_file = true +time_sequencing = "seasonal_timeslices" +days_per_period = 365 diff --git a/tests/testing_configs/config_seasonal_storage.toml b/tests/testing_configs/config_seasonal_storage.toml index 927446558..5c61a78a8 100644 --- a/tests/testing_configs/config_seasonal_storage.toml +++ b/tests/testing_configs/config_seasonal_storage.toml @@ -9,7 +9,6 @@ save_duals = false save_lp_file = false time_sequencing = "representative_periods" days_per_period = 365 -reserve_margin = "static" [MGA] cost_epsilon = 0.03 diff --git a/tests/testing_configs/config_storageville.toml b/tests/testing_configs/config_storageville.toml index 79a2585d0..a8216a6aa 100644 --- a/tests/testing_configs/config_storageville.toml +++ b/tests/testing_configs/config_storageville.toml @@ -9,7 +9,6 @@ save_duals = false save_lp_file = false time_sequencing = "seasonal_timeslices" days_per_period = 365 -reserve_margin = "static" [MGA] slack = 0.1 diff --git a/tests/testing_configs/config_survival_curve.toml b/tests/testing_configs/config_survival_curve.toml index fa28822a1..6796bb64d 100644 --- a/tests/testing_configs/config_survival_curve.toml +++ b/tests/testing_configs/config_survival_curve.toml @@ -9,7 +9,6 @@ save_duals = false save_lp_file = false time_sequencing = "seasonal_timeslices" days_per_period = 365 -reserve_margin = "static" [MGA] cost_epsilon = 0.03 diff --git a/tests/testing_configs/config_test_system.toml b/tests/testing_configs/config_test_system.toml index 25acdfe59..7b5072a97 100644 --- a/tests/testing_configs/config_test_system.toml +++ b/tests/testing_configs/config_test_system.toml @@ -9,7 +9,6 @@ save_duals = false save_lp_file = false time_sequencing = "seasonal_timeslices" days_per_period = 365 -reserve_margin = "static" [MGA] slack = 0.1 diff --git a/tests/testing_configs/config_test_week.toml b/tests/testing_configs/config_test_week.toml index 484272eae..7e7a1face 100644 --- a/tests/testing_configs/config_test_week.toml +++ b/tests/testing_configs/config_test_week.toml @@ -9,7 +9,6 @@ save_excel = false save_duals = false save_lp_file = false days_per_period = 7 -reserve_margin = "static" [MGA] slack = 0.1 diff --git a/tests/testing_configs/config_utopia.toml b/tests/testing_configs/config_utopia.toml index 1e7080c4a..ec7d69f3b 100644 --- a/tests/testing_configs/config_utopia.toml +++ b/tests/testing_configs/config_utopia.toml @@ -9,7 +9,6 @@ save_duals = false save_lp_file = false time_sequencing = "seasonal_timeslices" days_per_period = 365 -reserve_margin = "static" [MGA] cost_epsilon = 0.03 diff --git a/tests/testing_configs/config_utopia_gv.toml b/tests/testing_configs/config_utopia_gv.toml index cc5d93a5e..deb7bfdfa 100644 --- a/tests/testing_configs/config_utopia_gv.toml +++ b/tests/testing_configs/config_utopia_gv.toml @@ -9,7 +9,6 @@ save_duals = false save_lp_file = false time_sequencing = "seasonal_timeslices" days_per_period = 365 -reserve_margin = "static" graphviz_output = true [MGA] diff --git a/tests/testing_configs/config_utopia_mc.toml b/tests/testing_configs/config_utopia_mc.toml index 40f8993dc..3eae138be 100644 --- a/tests/testing_configs/config_utopia_mc.toml +++ b/tests/testing_configs/config_utopia_mc.toml @@ -11,7 +11,6 @@ solver_name = "appsi_highs" time_sequencing = 'seasonal_timeslices' days_per_period = 365 -reserve_margin = 'static' [monte_carlo] run_settings = "tests/testing_data/mc_settings_utopia.csv" diff --git a/tests/testing_configs/config_utopia_myopic.toml b/tests/testing_configs/config_utopia_myopic.toml index 2744e9809..4077dafc7 100644 --- a/tests/testing_configs/config_utopia_myopic.toml +++ b/tests/testing_configs/config_utopia_myopic.toml @@ -9,7 +9,6 @@ save_duals = false save_lp_file = false time_sequencing = "seasonal_timeslices" days_per_period = 365 -reserve_margin = "static" [MGA] slack = 0.1 diff --git a/tests/testing_data/annualised_demand.sql b/tests/testing_data/annualised_demand.sql index 76d910927..f2407ac4c 100644 --- a/tests/testing_data/annualised_demand.sql +++ b/tests/testing_data/annualised_demand.sql @@ -32,16 +32,16 @@ REPLACE INTO "limit_activity" VALUES('region',2000,'non_annual','le',0.5,NULL,NU REPLACE INTO "loan_lifetime_process" VALUES('region', 'annual', 2000, 1.0, NULL, NULL); REPLACE INTO "loan_lifetime_process" VALUES('region', 'non_annual', 2000, 1.0, NULL, NULL); REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); -REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',1,''); REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,'Discount Rate for future costs'); REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); REPLACE INTO "operator" VALUES('e','equal to'); REPLACE INTO "operator" VALUES('le','less than or equal to'); REPLACE INTO "operator" VALUES('ge','greater than or equal to'); REPLACE INTO "region" VALUES('region',NULL); -REPLACE INTO "technology" VALUES('annual','p','energy',NULL,NULL,0,1,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('import','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('non_annual','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('annual','p','energy',NULL,NULL,0,1,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('import','p','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('non_annual','p','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); REPLACE INTO "technology_type" VALUES('p','production technology'); REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); REPLACE INTO "technology_type" VALUES('ps','storage production technology'); diff --git a/tests/testing_data/emissions.sql b/tests/testing_data/emissions.sql index 0aaa11b02..1d2bcc6c6 100644 --- a/tests/testing_data/emissions.sql +++ b/tests/testing_data/emissions.sql @@ -68,21 +68,21 @@ REPLACE INTO "limit_capacity" VALUES('Testregion',2000,'TechCurtailment','ge',1. REPLACE INTO "limit_capacity" VALUES('Testregion',2000,'TechOrdinary','le',1.0,NULL,NULL); REPLACE INTO "limit_capacity" VALUES('Testregion',2000,'TechCurtailment','le',1.0,NULL,NULL); REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); -REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',1,''); REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,'Discount Rate for future costs'); REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); REPLACE INTO "operator" VALUES('e','equal to'); REPLACE INTO "operator" VALUES('le','less than or equal to'); REPLACE INTO "operator" VALUES('ge','greater than or equal to'); REPLACE INTO "region" VALUES('Testregion',NULL); -REPLACE INTO "technology" VALUES('TechAnnual','p','energy',NULL,NULL,0,1,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('TechFlex','p','energy',NULL,NULL,0,0,0,0,0,1,0,0,NULL); -REPLACE INTO "technology" VALUES('TechOrdinary','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('TechCurtailment','p','energy',NULL,NULL,0,0,0,1,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('TechFlexNull','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('TechAnnualFlex','p','energy',NULL,NULL,0,1,0,0,0,1,0,0,NULL); -REPLACE INTO "technology" VALUES('TechEmbodied','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('TechEndOfLife','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('TechAnnual','p','energy',NULL,NULL,0,1,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('TechFlex','p','energy',NULL,NULL,0,0,0,0,1,0,0,NULL); +REPLACE INTO "technology" VALUES('TechOrdinary','p','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('TechCurtailment','p','energy',NULL,NULL,0,0,1,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('TechFlexNull','p','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('TechAnnualFlex','p','energy',NULL,NULL,0,1,0,0,1,0,0,NULL); +REPLACE INTO "technology" VALUES('TechEmbodied','p','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('TechEndOfLife','p','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); REPLACE INTO "technology_type" VALUES('p','production technology'); REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); REPLACE INTO "technology_type" VALUES('ps','storage production technology'); diff --git a/tests/testing_data/materials.sql b/tests/testing_data/materials.sql index 95f24b4fa..b9631c9f7 100644 --- a/tests/testing_data/materials.sql +++ b/tests/testing_data/materials.sql @@ -330,7 +330,7 @@ REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2010,'diesel','CAR REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2020,'electricity','CAR_PHEV','le',0.2,''); REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2020,'diesel','CAR_PHEV','le',0.8,NULL); REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); -REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',1,''); REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,'Discount Rate for future costs'); REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); REPLACE INTO "operator" VALUES('e','equal to'); @@ -345,25 +345,25 @@ REPLACE INTO "sector_label" VALUES('residential',NULL); REPLACE INTO "sector_label" VALUES('fuels',NULL); REPLACE INTO "storage_duration" VALUES('regionA','BATT_GRID',2.0,'2 hours energy storage'); REPLACE INTO "storage_duration" VALUES('regionB','BATT_GRID',2.0,'2 hours energy storage'); -REPLACE INTO "technology" VALUES('IMPORT_LI','p','materials',NULL,NULL,1,1,0,0,0,0,0,0,'lithium importer'); -REPLACE INTO "technology" VALUES('IMPORT_CO','p','materials',NULL,NULL,1,1,0,0,0,0,0,0,'cobalt importer'); -REPLACE INTO "technology" VALUES('IMPORT_P','p','materials',NULL,NULL,1,1,0,0,0,0,0,0,'phosphorous importer'); -REPLACE INTO "technology" VALUES('CAR_BEV','p','transportation',NULL,NULL,0,0,0,0,0,0,0,0,'car - battery electric'); -REPLACE INTO "technology" VALUES('CAR_PHEV','p','transportation',NULL,NULL,0,0,0,0,0,0,0,0,'car - plug in hybrid'); -REPLACE INTO "technology" VALUES('CAR_ICE','p','transportation',NULL,NULL,0,0,0,0,0,0,0,0,'car - internal combustion'); -REPLACE INTO "technology" VALUES('RECYCLE_NMC','p','materials',NULL,NULL,0,1,0,0,0,0,0,0,'nmc battery recycler'); -REPLACE INTO "technology" VALUES('RECYCLE_LFP','p','materials',NULL,NULL,0,1,0,0,0,0,0,0,'lfp battery recycler'); -REPLACE INTO "technology" VALUES('MANUFAC_NMC','p','materials',NULL,NULL,0,1,0,0,0,0,0,0,'nmc battery manufacturing'); -REPLACE INTO "technology" VALUES('MANUFAC_LFP','p','materials',NULL,NULL,0,1,0,0,0,0,0,0,'lfp battery manufacturing'); -REPLACE INTO "technology" VALUES('IMPORT_NI','p','materials',NULL,NULL,1,1,0,0,0,0,0,0,'nickel importer'); -REPLACE INTO "technology" VALUES('DOMESTIC_NI','p','materials',NULL,NULL,1,1,0,0,0,0,0,0,'domestic nickel production'); -REPLACE INTO "technology" VALUES('GEN_DSL','p','electricity',NULL,NULL,0,0,0,0,0,0,0,0,'diesel generators'); -REPLACE INTO "technology" VALUES('SOL_PV','p','electricity',NULL,NULL,0,0,0,1,0,0,0,0,'solar panels'); -REPLACE INTO "technology" VALUES('BATT_GRID','ps','electricity',NULL,NULL,0,0,0,0,0,0,0,0,'grid battery storage'); -REPLACE INTO "technology" VALUES('FURNACE','p','residential',NULL,NULL,1,0,0,0,0,0,0,0,'diesel furnace heater'); -REPLACE INTO "technology" VALUES('HEATPUMP','p','residential',NULL,NULL,1,0,0,0,0,0,0,0,'heat pump'); -REPLACE INTO "technology" VALUES('IMPORT_DSL','p','fuels',NULL,NULL,1,1,0,0,0,0,0,0,'diesel importer'); -REPLACE INTO "technology" VALUES('ELEC_INTERTIE','p','electricity',NULL,NULL,0,0,0,0,0,0,1,0,'dummy tech to make landfill feasible'); +REPLACE INTO "technology" VALUES('IMPORT_LI','p','materials',NULL,NULL,1,1,0,0,0,0,0,'lithium importer'); +REPLACE INTO "technology" VALUES('IMPORT_CO','p','materials',NULL,NULL,1,1,0,0,0,0,0,'cobalt importer'); +REPLACE INTO "technology" VALUES('IMPORT_P','p','materials',NULL,NULL,1,1,0,0,0,0,0,'phosphorous importer'); +REPLACE INTO "technology" VALUES('CAR_BEV','p','transportation',NULL,NULL,0,0,0,0,0,0,0,'car - battery electric'); +REPLACE INTO "technology" VALUES('CAR_PHEV','p','transportation',NULL,NULL,0,0,0,0,0,0,0,'car - plug in hybrid'); +REPLACE INTO "technology" VALUES('CAR_ICE','p','transportation',NULL,NULL,0,0,0,0,0,0,0,'car - internal combustion'); +REPLACE INTO "technology" VALUES('RECYCLE_NMC','p','materials',NULL,NULL,0,1,0,0,0,0,0,'nmc battery recycler'); +REPLACE INTO "technology" VALUES('RECYCLE_LFP','p','materials',NULL,NULL,0,1,0,0,0,0,0,'lfp battery recycler'); +REPLACE INTO "technology" VALUES('MANUFAC_NMC','p','materials',NULL,NULL,0,1,0,0,0,0,0,'nmc battery manufacturing'); +REPLACE INTO "technology" VALUES('MANUFAC_LFP','p','materials',NULL,NULL,0,1,0,0,0,0,0,'lfp battery manufacturing'); +REPLACE INTO "technology" VALUES('IMPORT_NI','p','materials',NULL,NULL,1,1,0,0,0,0,0,'nickel importer'); +REPLACE INTO "technology" VALUES('DOMESTIC_NI','p','materials',NULL,NULL,1,1,0,0,0,0,0,'domestic nickel production'); +REPLACE INTO "technology" VALUES('GEN_DSL','p','electricity',NULL,NULL,0,0,0,0,0,0,0,'diesel generators'); +REPLACE INTO "technology" VALUES('SOL_PV','p','electricity',NULL,NULL,0,0,1,0,0,0,0,'solar panels'); +REPLACE INTO "technology" VALUES('BATT_GRID','ps','electricity',NULL,NULL,0,0,0,0,0,0,0,'grid battery storage'); +REPLACE INTO "technology" VALUES('FURNACE','p','residential',NULL,NULL,1,0,0,0,0,0,0,'diesel furnace heater'); +REPLACE INTO "technology" VALUES('HEATPUMP','p','residential',NULL,NULL,1,0,0,0,0,0,0,'heat pump'); +REPLACE INTO "technology" VALUES('IMPORT_DSL','p','fuels',NULL,NULL,1,1,0,0,0,0,0,'diesel importer'); +REPLACE INTO "technology" VALUES('ELEC_INTERTIE','p','electricity',NULL,NULL,0,0,0,0,0,1,0,'dummy tech to make landfill feasible'); REPLACE INTO "technology_type" VALUES('p','production technology'); REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); REPLACE INTO "technology_type" VALUES('ps','storage production technology'); diff --git a/tests/testing_data/mediumville.sql b/tests/testing_data/mediumville.sql index 2f253a8ce..4538c7881 100644 --- a/tests/testing_data/mediumville.sql +++ b/tests/testing_data/mediumville.sql @@ -1,4 +1,4 @@ -REPLACE INTO "capacity_credit" VALUES('A',2025,'EF',2025,0.6,NULL); +REPLACE INTO "planning_reserve_credit" VALUES('A','EF',0.6,NULL); REPLACE INTO "capacity_factor_process" VALUES('A','s2','d1','EFL',2025,0.8,NULL); REPLACE INTO "capacity_factor_process" VALUES('A','s1','d2','EFL',2025,0.9,NULL); REPLACE INTO "capacity_factor_tech" VALUES('A','s1','d1','EF',0.8,NULL); @@ -108,6 +108,7 @@ REPLACE INTO "limit_activity" VALUES('B',2025,'EH','le',10000.0,'stuff',NULL); REPLACE INTO "limit_activity" VALUES('A',2025,'EF','le',10000.0,'stuff',NULL); REPLACE INTO "limit_activity" VALUES('A',2025,'A_tech_grp_1','ge',0.05,'',NULL); REPLACE INTO "limit_activity" VALUES('A',2025,'A_tech_grp_1','le',10000.0,'',NULL); +REPLACE INTO "limit_activity_share" VALUES('B',2025,'EF','RPS_common','ge',0.3,NULL); REPLACE INTO "limit_capacity" VALUES('A',2025,'EH','ge',0.1,'',''); REPLACE INTO "limit_capacity" VALUES('B',2025,'batt','ge',0.1,'',''); REPLACE INTO "limit_capacity" VALUES('A',2025,'EH','le',20000.0,'',''); @@ -140,20 +141,19 @@ REPLACE INTO "loan_lifetime_process" VALUES('B', 'GeoThermal', 2025, 10.0, NULL, REPLACE INTO "loan_lifetime_process" VALUES('A-B', 'FGF_pipe', 2025, 10.0, NULL, NULL); REPLACE INTO "loan_lifetime_process" VALUES('B-A', 'FGF_pipe', 2025, 10.0, NULL, NULL); REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); -REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',1,''); REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); REPLACE INTO "metadata_real" VALUES('global_discount_rate',4.2000000000000004e-01,''); REPLACE INTO "operator" VALUES('e','equal to'); REPLACE INTO "operator" VALUES('le','less than or equal to'); REPLACE INTO "operator" VALUES('ge','greater than or equal to'); -REPLACE INTO "planning_reserve_margin" VALUES('A',0.05,NULL); +REPLACE INTO "planning_reserve_margin" VALUES('A','EF',0.05,NULL); REPLACE INTO "ramp_down_hourly" VALUES('A','EH',0.05,NULL); REPLACE INTO "ramp_down_hourly" VALUES('B','EH',0.05,NULL); REPLACE INTO "ramp_up_hourly" VALUES('B','EH',0.05,NULL); REPLACE INTO "ramp_up_hourly" VALUES('A','EH',0.05,NULL); REPLACE INTO "region" VALUES('A','main region'); REPLACE INTO "region" VALUES('B','just a 2nd region'); -REPLACE INTO "rps_requirement" VALUES('B',2025,'RPS_common',0.3,NULL); REPLACE INTO "sector_label" VALUES('supply',NULL); REPLACE INTO "sector_label" VALUES('electric',NULL); REPLACE INTO "sector_label" VALUES('transport',NULL); @@ -166,16 +166,16 @@ REPLACE INTO "tech_group" VALUES('A_tech_grp_1','converted from old db'); REPLACE INTO "tech_group_member" VALUES('RPS_common','EF'); REPLACE INTO "tech_group_member" VALUES('A_tech_grp_1','EH'); REPLACE INTO "tech_group_member" VALUES('A_tech_grp_1','EF'); -REPLACE INTO "technology" VALUES('well','p','supply','water','',0,0,0,0,0,0,0,0,'plain old water'); -REPLACE INTO "technology" VALUES('bulbs','p','residential','electric','',0,0,0,0,0,0,0,0,'residential lighting'); -REPLACE INTO "technology" VALUES('EH','pb','electric','hydro','',0,0,0,1,1,0,0,0,'hydro power electric plant'); -REPLACE INTO "technology" VALUES('batt','ps','electric','electric','',0,0,0,0,0,0,0,0,'big battery'); -REPLACE INTO "technology" VALUES('EF','p','electric','electric','',0,0,1,0,0,0,0,0,'fusion plant'); -REPLACE INTO "technology" VALUES('EFL','p','electric','electric','',0,0,0,0,0,1,0,0,'linked (to Fusion) producer'); -REPLACE INTO "technology" VALUES('heater','p','residential','electric','',0,0,0,0,0,0,0,0,'heater'); -REPLACE INTO "technology" VALUES('FGF_pipe','p','transport',NULL,'',0,0,0,0,0,0,1,0,'transportation line A->B'); -REPLACE INTO "technology" VALUES('GeoThermal','p','residential','hydro','',0,1,0,0,0,0,0,0,'geothermal hot water source'); -REPLACE INTO "technology" VALUES('GeoHeater','p','residential','hydro','',0,0,0,0,0,0,0,0,'geothermal heater from geo hyd'); +REPLACE INTO "technology" VALUES('well','p','supply','water','',0,0,0,0,0,0,0,'plain old water'); +REPLACE INTO "technology" VALUES('bulbs','p','residential','electric','',0,0,0,0,0,0,0,'residential lighting'); +REPLACE INTO "technology" VALUES('EH','pb','electric','hydro','',0,0,1,1,0,0,0,'hydro power electric plant'); +REPLACE INTO "technology" VALUES('batt','ps','electric','electric','',0,0,0,0,0,0,0,'big battery'); +REPLACE INTO "technology" VALUES('EF','p','electric','electric','',0,0,0,0,0,0,0,'fusion plant'); +REPLACE INTO "technology" VALUES('EFL','p','electric','electric','',0,0,0,0,1,0,0,'linked (to Fusion) producer'); +REPLACE INTO "technology" VALUES('heater','p','residential','electric','',0,0,0,0,0,0,0,'heater'); +REPLACE INTO "technology" VALUES('FGF_pipe','p','transport',NULL,'',0,0,0,0,0,1,0,'transportation line A->B'); +REPLACE INTO "technology" VALUES('GeoThermal','p','residential','hydro','',0,1,0,0,0,0,0,'geothermal hot water source'); +REPLACE INTO "technology" VALUES('GeoHeater','p','residential','hydro','',0,0,0,0,0,0,0,'geothermal heater from geo hyd'); REPLACE INTO "technology_type" VALUES('p','production technology'); REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); REPLACE INTO "technology_type" VALUES('ps','storage production technology'); diff --git a/tests/testing_data/mediumville_sets.json b/tests/testing_data/mediumville_sets.json index 3aca6a682..a56a77ab6 100644 --- a/tests/testing_data/mediumville_sets.json +++ b/tests/testing_data/mediumville_sets.json @@ -34,7 +34,7 @@ "lifetime_tech_rt": "6786f2e9acdccce907ebad50d2ea70481bff72a749253e78168d29b480b07f65", "lifetime_process_rtv": "212639322f29aa7687eb9962fb9d74ef60961d720a2a753e2af53473b9e1648f", "limit_activity_constraint_rpt": "90461349933d0b32167abdca242233004b66212b57ed57213dce6f6818203963", - "limit_activity_share_constraint_rpgg": "(empty)", + "limit_activity_share_constraint_rpgg": "0df34850f785baf3f30a829f838bb853edd65e1969b3df81885a2ef40c130495", "limit_annual_capacity_factor_constraint_rptvo": "(empty)", "limit_annual_capacity_factor_constraint_rtvo": "(empty)", "limit_capacity_constraint_rpt": "118826dabd14af75ae09adbd5bdbba750528b3d59907cbfba3690223ffc7def7", @@ -56,8 +56,10 @@ "linked_emissions_tech_constraint_rpsdtve": "f5accc0ee9eaaf16e584566a50e576405f8b5e1eb7d13815b87cb2f8b33a19b8", "loan_lifetime_process_rtv": "212639322f29aa7687eb9962fb9d74ef60961d720a2a753e2af53473b9e1648f", "new_capacity_var_rtv": "212639322f29aa7687eb9962fb9d74ef60961d720a2a753e2af53473b9e1648f", + "operating_reserve_rpsdt": "(empty)", "operator": "74d830836f1399fb336a0432dde7d7bd36cffa3ff76b1c42d7945350cfb9bf91", "ordered_season_sequential": "(empty)", + "planning_reserve_rpsdt": "e39f44175bf0c956bfc8083bfaeef1ab450d2cf9c0963327a62e52595e902bf4", "process_life_frac_rptv": "fdb177f119430de12c8b1083c9275e3a256f45f859d6b6819a84bff5c8e92184", "ramp_down_constraint_rpsdtv": "d766f05fa377b69768214641fda5d15617b697a43e64c863fe53503f11db4a89", "ramp_up_constraint_rpsdtv": "d766f05fa377b69768214641fda5d15617b697a43e64c863fe53503f11db4a89", @@ -65,9 +67,6 @@ "regional_global_indices": "a4bd2969735cb437072971a2fa02a2d8fbb20a707a1bdfa695b92922daeb5d10", "regional_indices": "6d8dc3bc6dc8cd485bcf2a59d752df20974b0fd78cc623e3a51e705a01edeea4", "regions": "f2314439597c11e286fd3c2fdf8eb70d3739e136f83e9533249ce3ecc43ece3e", - "renewable_portfolio_standard_constraint_rpg": "ab39415b5df1f906b11f06034cbfca390f7332b6a17adae60a71a74d7b421f67", - "reserve_margin_method": "7869283c0d14273f720716309207a8f0c24606d03c679d6b68e656ed8d86241d", - "reserve_margin_rpsd": "9bd91053e57055456e04648c00d8e581addc9aa503cf31cf44c7a439c6f4fe0f", "retired_capacity_var_rptv": "(empty)", "seasonal_storage_constraints_rpsdtv": "(empty)", "seasonal_storage_level_rpstv": "(empty)", @@ -87,7 +86,6 @@ "tech_group_names": "6b923047f11d1c00a828b0f02aed79e853d81fd35ffd4667fbdc828ce356e515", "tech_or_group": "4899ca300eb8ed5ba168b1f2ee669cacd36d041f493fb3dd00218d6764598cf9", "tech_production": "24f75b6f705a36033456d02638e7c50667908c45c474151fb8490666d928c63f", - "tech_reserve": "b28d0fd7ec11abdd0e645c2a9d83b05c08ecadcfb944d4a7bbd844ff4b83bfbf", "tech_retirement": "ddcf6ff7665c2a8acc4dff1b43655fae1b5a265135cbee18cec638df4e954346", "tech_seasonal_storage": "(empty)", "tech_storage": "e4e91128cadba8c633bf98029cf6666adc3959c63967c6f396028b78e2e9f0cf", diff --git a/tests/testing_data/migration_v4_mock.sql b/tests/testing_data/migration_v4_mock.sql new file mode 100644 index 000000000..2379b482e --- /dev/null +++ b/tests/testing_data/migration_v4_mock.sql @@ -0,0 +1,56 @@ +-- Mock data for v4 -> v4.1 migration testing +PRAGMA foreign_keys = OFF; +INSERT OR IGNORE INTO commodity_type (label, description) VALUES ('p', 'physical'); +INSERT OR IGNORE INTO commodity_type (label, description) VALUES ('e', 'emissions'); +INSERT OR IGNORE INTO sector_label (sector) VALUES ('electric'); + +INSERT INTO commodity (name, flag) VALUES ('FuelIn', 'p'); +INSERT INTO commodity (name, flag) VALUES ('EnergyOut', 'p'); + +INSERT INTO time_period (period, flag) VALUES (2020, 'e'); +INSERT INTO time_period (period, flag) VALUES (2030, 'f'); +INSERT INTO time_period (period, flag) VALUES (2040, 'f'); + +INSERT INTO time_season (season, segment_fraction) VALUES ('summer', 0.6); +INSERT INTO time_season (season, segment_fraction) VALUES ('winter', 0.4); + +INSERT INTO time_of_day (tod, hours) VALUES ('day', 14.4); +INSERT INTO time_of_day (tod, hours) VALUES ('night', 9.6); + +INSERT OR IGNORE INTO technology_type (label, description) VALUES ('p', 'production'); +INSERT INTO technology (tech, flag, reserve) VALUES ('GasTurbine', 'p', 1); +INSERT INTO technology (tech, flag, reserve) VALUES ('WindFarm', 'p', 1); +INSERT INTO technology (tech, flag, reserve) VALUES ('CoalPlant', 'p', 0); + +INSERT INTO efficiency (region, input_comm, tech, vintage, output_comm, efficiency) + VALUES ('R1', 'FuelIn', 'GasTurbine', 2030, 'EnergyOut', 0.4); +INSERT INTO efficiency (region, input_comm, tech, vintage, output_comm, efficiency) + VALUES ('R1', 'FuelIn', 'WindFarm', 2030, 'EnergyOut', 1.0); +INSERT INTO efficiency (region, input_comm, tech, vintage, output_comm, efficiency) + VALUES ('R1', 'FuelIn', 'CoalPlant', 2030, 'EnergyOut', 0.35); + +-- capacity_credit will migrate to planning_reserve_credit (dropping period, vintage) +INSERT INTO capacity_credit (region, period, tech, vintage, credit) + VALUES ('R1', 2030, 'GasTurbine', 2030, 0.85); +INSERT INTO capacity_credit (region, period, tech, vintage, credit) + VALUES ('R1', 2040, 'GasTurbine', 2030, 0.80); +INSERT INTO capacity_credit (region, period, tech, vintage, credit) + VALUES ('R1', 2030, 'WindFarm', 2030, 0.25); + +-- reserve_capacity_derate will migrate to operating_reserve_derate (dropping vintage) +INSERT INTO reserve_capacity_derate (region, season, tech, vintage, factor) + VALUES ('R1', 'summer', 'GasTurbine', 2030, 0.95); +INSERT INTO reserve_capacity_derate (region, season, tech, vintage, factor) + VALUES ('R1', 'summer', 'WindFarm', 2030, 0.20); +INSERT INTO reserve_capacity_derate (region, season, tech, vintage, factor) + VALUES ('R1', 'winter', 'WindFarm', 2030, 0.10); + +-- planning_reserve_margin will migrate using reserve-flagged tech group as tech_or_group +INSERT INTO planning_reserve_margin (region, margin) VALUES ('R1', 0.15); + +-- rps_requirement will migrate to limit_activity_share +INSERT INTO tech_group (group_name) VALUES ('renewables'); +INSERT INTO rps_requirement (region, period, tech_group, requirement) + VALUES ('R1', 2030, 'renewables', 0.30); +INSERT INTO rps_requirement (region, period, tech_group, requirement) + VALUES ('R1', 2040, 'renewables', 0.40); diff --git a/tests/testing_data/myopic_capacities.sql b/tests/testing_data/myopic_capacities.sql index ca11c4158..d9f03b2e9 100644 --- a/tests/testing_data/myopic_capacities.sql +++ b/tests/testing_data/myopic_capacities.sql @@ -1,5 +1,5 @@ REPLACE INTO metadata VALUES('DB_MAJOR',4,''); -REPLACE INTO metadata VALUES('DB_MINOR',0,''); +REPLACE INTO metadata VALUES('DB_MINOR',1,''); REPLACE INTO metadata_real VALUES('global_discount_rate',0.05000000000000000277,'Discount Rate for future costs'); REPLACE INTO metadata_real VALUES('default_loan_rate',0.05000000000000000277,'Default Loan Rate if not specified in loan_rate table'); REPLACE INTO sector_label VALUES('energy',NULL); @@ -234,18 +234,18 @@ REPLACE INTO time_period VALUES(5,2050,'f'); REPLACE INTO time_period VALUES(6,2055,'f'); REPLACE INTO time_period_type VALUES('e','existing vintages'); REPLACE INTO time_period_type VALUES('f','future'); -REPLACE INTO technology VALUES('tech_ancient','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); -REPLACE INTO technology VALUES('tech_old','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); -REPLACE INTO technology VALUES('tech_current','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); -REPLACE INTO technology VALUES('tech_future','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); -REPLACE INTO technology VALUES('tech_retire','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); REPLACE INTO time_season VALUES(0,'s',1.0,NULL); REPLACE INTO commodity VALUES('demand_dc','d',NULL,NULL); REPLACE INTO commodity VALUES('demand_dnc','d',NULL,NULL); -REPLACE INTO technology VALUES('tech_discrete_cap','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); -REPLACE INTO technology VALUES('tech_dc_dummy','p','energy',NULL,NULL,1,0,0,0,0,0,0,0,NULL); -REPLACE INTO technology VALUES('tech_discrete_new_cap','p','energy',NULL,NULL,0,0,0,0,1,0,0,0,NULL); -REPLACE INTO technology VALUES('tech_dnc_dummy','p','energy',NULL,NULL,1,0,0,0,0,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_ancient','p','energy',NULL,NULL,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_old','p','energy',NULL,NULL,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_current','p','energy',NULL,NULL,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_future','p','energy',NULL,NULL,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_retire','p','energy',NULL,NULL,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_discrete_cap','p','energy',NULL,NULL,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_dc_dummy','p','energy',NULL,NULL,1,0,0,0,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_discrete_new_cap','p','energy',NULL,NULL,0,0,0,1,0,0,0,NULL); +REPLACE INTO technology VALUES('tech_dnc_dummy','p','energy',NULL,NULL,1,0,0,0,0,0,0,NULL); REPLACE INTO lifetime_tech VALUES('region','tech_discrete_cap',35.0,NULL,NULL); REPLACE INTO lifetime_tech VALUES('region','tech_dc_dummy',35.0,NULL,NULL); REPLACE INTO lifetime_tech VALUES('region','tech_discrete_new_cap',5.0,NULL,NULL); diff --git a/tests/testing_data/reserve_margins.lp b/tests/testing_data/reserve_margins.lp new file mode 100644 index 000000000..1bf0565cb --- /dev/null +++ b/tests/testing_data/reserve_margins.lp @@ -0,0 +1,3127 @@ +\* Source Pyomo model name=unknown *\ + +min +total_cost: ++0.0 ONE_VAR_CONSTANT + +s.t. + +c_u_capacity_constraint(C_A_2020_summer_night_E_TRANS_2015)_: ++1 v_flow_out(C_A_2020_summer_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(C_2020_winter_night_HEAT_ANN_2020)_: +-0.25 v_capacity(C_2020_HEAT_ANN_2020) +<= -0.25 + +c_u_capacity_constraint(B_C_2020_winter_day_E_TRANS_2015)_: ++1 v_flow_out(B_C_2020_winter_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_C_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2025_winter_night_HEAT_SYS_2020)_: ++1 v_flow_out(B_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) +-0.25 v_capacity(B_2025_HEAT_SYS_2020) +<= 0 + +c_u_capacity_constraint(A_C_2020_winter_day_E_TRANS_2015)_: ++1 v_flow_out(A_C_2020_winter_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_C_2020_E_TRANS_2015) +<= 0 + +c_e_capacity_constraint(A_2020_winter_night_SOLPV_2020)_: +-1 v_flow_out(A_2020_winter_night_ethos_SOLPV_2020_ELC) +-1 v_curtailment(A_2020_winter_night_ethos_SOLPV_2020_ELC) += 0 + +c_u_capacity_constraint(B_2020_winter_night_NGCC_2020)_: ++1 v_flow_out(B_2020_winter_night_NG_NGCC_2020_ELC) +-2.19 v_capacity(B_2020_NGCC_2020) +<= 0 + +c_u_capacity_constraint(A_2025_summer_day_HEAT_SYS_2025)_: ++1 v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) +-0.25 v_capacity(A_2025_HEAT_SYS_2025) +<= 0 + +c_u_capacity_constraint(B_A_2020_summer_day_E_TRANS_2015)_: ++1 v_flow_out(B_A_2020_summer_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_A_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(C_B_2025_summer_night_E_TRANS_2015)_: ++1 v_flow_out(C_B_2025_summer_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_B_2025_E_TRANS_2015) +<= 0 + +c_e_capacity_constraint(A_2020_summer_day_SOLPV_2020)_: +-1 v_flow_out(A_2020_summer_day_ethos_SOLPV_2020_ELC) ++1.3139999999999998 v_capacity(A_2020_SOLPV_2020) +-1 v_curtailment(A_2020_summer_day_ethos_SOLPV_2020_ELC) += 0 + +c_u_capacity_constraint(A_2025_summer_night_NGCC_2025)_: ++1 v_flow_out(A_2025_summer_night_NG_NGCC_2025_ELC) +-2.19 v_capacity(A_2025_NGCC_2025) +<= 0 + +c_e_capacity_constraint(A_2025_summer_day_SOLPV_2015)_: +-1 v_flow_out(A_2025_summer_day_ethos_SOLPV_2015_ELC) ++1.3139999999999998 v_capacity(A_2025_SOLPV_2015) +-1 v_curtailment(A_2025_summer_day_ethos_SOLPV_2015_ELC) += 0 + +c_e_capacity_constraint(A_2025_winter_night_SOLPV_2015)_: +-1 v_flow_out(A_2025_winter_night_ethos_SOLPV_2015_ELC) +-1 v_curtailment(A_2025_winter_night_ethos_SOLPV_2015_ELC) += 0 + +c_e_capacity_constraint(A_2025_summer_night_SOLPV_2015)_: +-1 v_flow_out(A_2025_summer_night_ethos_SOLPV_2015_ELC) +-1 v_curtailment(A_2025_summer_night_ethos_SOLPV_2015_ELC) += 0 + +c_u_capacity_constraint(A_2025_summer_day_NGCC_2015)_: ++1 v_flow_out(A_2025_summer_day_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2025_NGCC_2015) +<= 0 + +c_u_capacity_constraint(B_2025_winter_night_NGCC_2020)_: ++1 v_flow_out(B_2025_winter_night_NG_NGCC_2020_ELC) +-2.19 v_capacity(B_2025_NGCC_2020) +<= 0 + +c_u_capacity_constraint(A_B_2025_summer_day_E_TRANS_2015)_: ++1 v_flow_out(A_B_2025_summer_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_B_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_2025_winter_night_NGCC_2015)_: ++1 v_flow_out(A_2025_winter_night_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2025_NGCC_2015) +<= 0 + +c_u_capacity_constraint(C_2020_summer_night_HEAT_ANN_2020)_: +-0.25 v_capacity(C_2020_HEAT_ANN_2020) +<= -0.25 + +c_u_capacity_constraint(C_2025_winter_day_HEAT_ANN_2020)_: +-0.25 v_capacity(C_2025_HEAT_ANN_2020) +<= -0.275 + +c_u_capacity_constraint(C_A_2025_winter_night_E_TRANS_2015)_: ++1 v_flow_out(C_A_2025_winter_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2025_summer_day_NGCC_2025)_: ++1 v_flow_out(B_2025_summer_day_NG_NGCC_2025_ELC) +-2.19 v_capacity(B_2025_NGCC_2025) +<= 0 + +c_u_capacity_constraint(A_2025_winter_day_NGCC_2025)_: ++1 v_flow_out(A_2025_winter_day_NG_NGCC_2025_ELC) +-2.19 v_capacity(A_2025_NGCC_2025) +<= 0 + +c_u_capacity_constraint(B_A_2020_winter_day_E_TRANS_2015)_: ++1 v_flow_out(B_A_2020_winter_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_A_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(C_B_2025_summer_day_E_TRANS_2015)_: ++1 v_flow_out(C_B_2025_summer_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_B_2025_E_TRANS_2015) +<= 0 + +c_e_capacity_constraint(A_2025_winter_day_SOLPV_2015)_: +-1 v_flow_out(A_2025_winter_day_ethos_SOLPV_2015_ELC) ++0.6569999999999999 v_capacity(A_2025_SOLPV_2015) +-1 v_curtailment(A_2025_winter_day_ethos_SOLPV_2015_ELC) += 0 + +c_u_capacity_constraint(B_C_2020_summer_day_E_TRANS_2015)_: ++1 v_flow_out(B_C_2020_summer_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_C_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2020_winter_night_NGCC_2015)_: ++1 v_flow_out(B_2020_winter_night_NG_NGCC_2015_ELC) +-2.19 v_capacity(B_2020_NGCC_2015) +<= 0 + +c_u_capacity_constraint(A_2025_summer_day_HEAT_SYS_2020)_: ++1 v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) +-0.25 v_capacity(A_2025_HEAT_SYS_2020) +<= 0 + +c_e_capacity_constraint(A_2020_summer_day_SOLPV_2015)_: +-1 v_flow_out(A_2020_summer_day_ethos_SOLPV_2015_ELC) ++1.3139999999999998 v_capacity(A_2020_SOLPV_2015) +-1 v_curtailment(A_2020_summer_day_ethos_SOLPV_2015_ELC) += 0 + +c_u_capacity_constraint(A_B_2025_winter_day_E_TRANS_2015)_: ++1 v_flow_out(A_B_2025_winter_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_B_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_2025_summer_night_NGCC_2020)_: ++1 v_flow_out(A_2025_summer_night_NG_NGCC_2020_ELC) +-2.19 v_capacity(A_2025_NGCC_2020) +<= 0 + +c_u_capacity_constraint(C_2020_winter_day_HEAT_ANN_2020)_: +-0.25 v_capacity(C_2020_HEAT_ANN_2020) +<= -0.25 + +c_u_capacity_constraint(C_2020_summer_day_HEAT_ANN_2020)_: +-0.25 v_capacity(C_2020_HEAT_ANN_2020) +<= -0.25 + +c_u_capacity_constraint(A_B_2020_summer_night_E_TRANS_2015)_: ++1 v_flow_out(A_B_2020_summer_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_B_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2025_winter_night_NGCC_2015)_: ++1 v_flow_out(B_2025_winter_night_NG_NGCC_2015_ELC) +-2.19 v_capacity(B_2025_NGCC_2015) +<= 0 + +c_u_capacity_constraint(A_2025_winter_night_HEAT_SYS_2025)_: ++1 v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) +-0.25 v_capacity(A_2025_HEAT_SYS_2025) +<= 0 + +c_u_capacity_constraint(C_B_2020_summer_day_E_TRANS_2015)_: ++1 v_flow_out(C_B_2020_summer_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_B_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2025_summer_day_NGCC_2020)_: ++1 v_flow_out(B_2025_summer_day_NG_NGCC_2020_ELC) +-2.19 v_capacity(B_2025_NGCC_2020) +<= 0 + +c_e_capacity_constraint(A_2020_summer_night_SOLPV_2020)_: +-1 v_flow_out(A_2020_summer_night_ethos_SOLPV_2020_ELC) +-1 v_curtailment(A_2020_summer_night_ethos_SOLPV_2020_ELC) += 0 + +c_u_capacity_constraint(A_2025_winter_day_NGCC_2020)_: ++1 v_flow_out(A_2025_winter_day_NG_NGCC_2020_ELC) +-2.19 v_capacity(A_2025_NGCC_2020) +<= 0 + +c_u_capacity_constraint(A_B_2025_winter_night_E_TRANS_2015)_: ++1 v_flow_out(A_B_2025_winter_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_B_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2020_winter_day_NGCC_2020)_: ++1 v_flow_out(B_2020_winter_day_NG_NGCC_2020_ELC) +-2.19 v_capacity(B_2020_NGCC_2020) +<= 0 + +c_u_capacity_constraint(A_2025_winter_day_HEAT_SYS_2025)_: ++1 v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) +-0.25 v_capacity(A_2025_HEAT_SYS_2025) +<= 0 + +c_u_capacity_constraint(A_2020_summer_day_NGCC_2020)_: ++1 v_flow_out(A_2020_summer_day_NG_NGCC_2020_ELC) +-2.19 v_capacity(A_2020_NGCC_2020) +<= 0 + +c_u_capacity_constraint(A_2025_summer_night_NGCC_2015)_: ++1 v_flow_out(A_2025_summer_night_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2025_NGCC_2015) +<= 0 + +c_u_capacity_constraint(B_2025_summer_night_NGCC_2025)_: ++1 v_flow_out(B_2025_summer_night_NG_NGCC_2025_ELC) +-2.19 v_capacity(B_2025_NGCC_2025) +<= 0 + +c_e_capacity_constraint(A_2020_winter_day_SOLPV_2020)_: +-1 v_flow_out(A_2020_winter_day_ethos_SOLPV_2020_ELC) ++0.6569999999999999 v_capacity(A_2020_SOLPV_2020) +-1 v_curtailment(A_2020_winter_day_ethos_SOLPV_2020_ELC) += 0 + +c_u_capacity_constraint(B_2020_summer_night_NGCC_2020)_: ++1 v_flow_out(B_2020_summer_night_NG_NGCC_2020_ELC) +-2.19 v_capacity(B_2020_NGCC_2020) +<= 0 + +c_u_capacity_constraint(A_2025_winter_night_HEAT_SYS_2020)_: ++1 v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) +-0.25 v_capacity(A_2025_HEAT_SYS_2020) +<= 0 + +c_u_capacity_constraint(A_C_2020_summer_day_E_TRANS_2015)_: ++1 v_flow_out(A_C_2020_summer_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_C_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(C_B_2020_summer_night_E_TRANS_2015)_: ++1 v_flow_out(C_B_2020_summer_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_B_2020_E_TRANS_2015) +<= 0 + +c_e_capacity_constraint(A_2020_winter_night_SOLPV_2015)_: +-1 v_flow_out(A_2020_winter_night_ethos_SOLPV_2015_ELC) +-1 v_curtailment(A_2020_winter_night_ethos_SOLPV_2015_ELC) += 0 + +c_u_capacity_constraint(B_2025_summer_day_NGCC_2015)_: ++1 v_flow_out(B_2025_summer_day_NG_NGCC_2015_ELC) +-2.19 v_capacity(B_2025_NGCC_2015) +<= 0 + +c_u_capacity_constraint(A_C_2025_summer_day_E_TRANS_2015)_: ++1 v_flow_out(A_C_2025_summer_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_C_2025_E_TRANS_2015) +<= 0 + +c_e_capacity_constraint(A_2020_summer_night_SOLPV_2015)_: +-1 v_flow_out(A_2020_summer_night_ethos_SOLPV_2015_ELC) +-1 v_curtailment(A_2020_summer_night_ethos_SOLPV_2015_ELC) += 0 + +c_u_capacity_constraint(A_2025_winter_day_NGCC_2015)_: ++1 v_flow_out(A_2025_winter_day_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2025_NGCC_2015) +<= 0 + +c_u_capacity_constraint(A_B_2020_summer_day_E_TRANS_2015)_: ++1 v_flow_out(A_B_2020_summer_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_B_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2020_winter_night_HEAT_SYS_2020)_: +-0.25 v_capacity(B_2020_HEAT_SYS_2020) +<= -0.75 + +c_u_capacity_constraint(C_B_2025_winter_day_E_TRANS_2015)_: ++1 v_flow_out(C_B_2025_winter_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_B_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2020_winter_day_NGCC_2015)_: ++1 v_flow_out(B_2020_winter_day_NG_NGCC_2015_ELC) +-2.19 v_capacity(B_2020_NGCC_2015) +<= 0 + +c_u_capacity_constraint(C_A_2025_winter_day_E_TRANS_2015)_: ++1 v_flow_out(C_A_2025_winter_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2020_summer_day_NGCC_2020)_: ++1 v_flow_out(B_2020_summer_day_NG_NGCC_2020_ELC) +-2.19 v_capacity(B_2020_NGCC_2020) +<= 0 + +c_u_capacity_constraint(B_A_2025_winter_day_E_TRANS_2015)_: ++1 v_flow_out(B_A_2025_winter_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_A_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_2020_summer_day_NGCC_2015)_: ++1 v_flow_out(A_2020_summer_day_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2020_NGCC_2015) +<= 0 + +c_u_capacity_constraint(A_C_2020_winter_night_E_TRANS_2015)_: ++1 v_flow_out(A_C_2020_winter_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_C_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_2025_summer_night_HEAT_SYS_2025)_: ++1 v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) +-0.25 v_capacity(A_2025_HEAT_SYS_2025) +<= 0 + +c_e_capacity_constraint(A_2020_winter_day_SOLPV_2015)_: +-1 v_flow_out(A_2020_winter_day_ethos_SOLPV_2015_ELC) ++0.6569999999999999 v_capacity(A_2020_SOLPV_2015) +-1 v_curtailment(A_2020_winter_day_ethos_SOLPV_2015_ELC) += 0 + +c_u_capacity_constraint(B_2025_summer_night_NGCC_2020)_: ++1 v_flow_out(B_2025_summer_night_NG_NGCC_2020_ELC) +-2.19 v_capacity(B_2025_NGCC_2020) +<= 0 + +c_u_capacity_constraint(B_2020_summer_night_NGCC_2015)_: ++1 v_flow_out(B_2020_summer_night_NG_NGCC_2015_ELC) +-2.19 v_capacity(B_2020_NGCC_2015) +<= 0 + +c_u_capacity_constraint(C_2025_winter_night_HEAT_ANN_2020)_: +-0.25 v_capacity(C_2025_HEAT_ANN_2020) +<= -0.275 + +c_u_capacity_constraint(B_A_2025_summer_night_E_TRANS_2015)_: ++1 v_flow_out(B_A_2025_summer_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_A_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_2020_winter_night_NGCC_2020)_: ++1 v_flow_out(A_2020_winter_night_NG_NGCC_2020_ELC) +-2.19 v_capacity(A_2020_NGCC_2020) +<= 0 + +c_e_capacity_constraint(A_2025_winter_day_SOLPV_2025)_: +-1 v_flow_out(A_2025_winter_day_ethos_SOLPV_2025_ELC) ++0.6569999999999999 v_capacity(A_2025_SOLPV_2025) +-1 v_curtailment(A_2025_winter_day_ethos_SOLPV_2025_ELC) += 0 + +c_u_capacity_constraint(B_2020_winter_day_HEAT_SYS_2020)_: +-0.25 v_capacity(B_2020_HEAT_SYS_2020) +<= -0.75 + +c_u_capacity_constraint(B_2025_winter_day_HEAT_SYS_2025)_: ++1 v_flow_out(B_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) +-0.25 v_capacity(B_2025_HEAT_SYS_2025) +<= 0 + +c_u_capacity_constraint(A_2020_summer_night_NGCC_2020)_: ++1 v_flow_out(A_2020_summer_night_NG_NGCC_2020_ELC) +-2.19 v_capacity(A_2020_NGCC_2020) +<= 0 + +c_u_capacity_constraint(C_A_2025_summer_night_E_TRANS_2015)_: ++1 v_flow_out(C_A_2025_summer_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2025_summer_day_HEAT_SYS_2025)_: ++1 v_flow_out(B_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) +-0.25 v_capacity(B_2025_HEAT_SYS_2025) +<= 0 + +c_u_capacity_constraint(B_C_2025_winter_day_E_TRANS_2015)_: ++1 v_flow_out(B_C_2025_winter_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_C_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2025_winter_day_NGCC_2025)_: ++1 v_flow_out(B_2025_winter_day_NG_NGCC_2025_ELC) +-2.19 v_capacity(B_2025_NGCC_2025) +<= 0 + +c_u_capacity_constraint(B_2025_summer_night_HEAT_SYS_2025)_: ++1 v_flow_out(B_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) +-0.25 v_capacity(B_2025_HEAT_SYS_2025) +<= 0 + +c_u_capacity_constraint(B_2020_summer_day_NGCC_2015)_: ++1 v_flow_out(B_2020_summer_day_NG_NGCC_2015_ELC) +-2.19 v_capacity(B_2020_NGCC_2015) +<= 0 + +c_u_capacity_constraint(C_B_2025_winter_night_E_TRANS_2015)_: ++1 v_flow_out(C_B_2025_winter_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_B_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_A_2025_summer_day_E_TRANS_2015)_: ++1 v_flow_out(B_A_2025_summer_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_A_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_2020_winter_day_NGCC_2020)_: ++1 v_flow_out(A_2020_winter_day_NG_NGCC_2020_ELC) +-2.19 v_capacity(A_2020_NGCC_2020) +<= 0 + +c_u_capacity_constraint(A_B_2020_winter_night_E_TRANS_2015)_: ++1 v_flow_out(A_B_2020_winter_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_B_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_2025_summer_night_HEAT_SYS_2020)_: ++1 v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) +-0.25 v_capacity(A_2025_HEAT_SYS_2020) +<= 0 + +c_u_capacity_constraint(B_A_2025_winter_night_E_TRANS_2015)_: ++1 v_flow_out(B_A_2025_winter_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_A_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_C_2025_summer_night_E_TRANS_2015)_: ++1 v_flow_out(A_C_2025_summer_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_C_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_C_2025_winter_night_E_TRANS_2015)_: ++1 v_flow_out(A_C_2025_winter_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_C_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_A_2020_summer_night_E_TRANS_2015)_: ++1 v_flow_out(B_A_2020_summer_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_A_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_2020_winter_night_NGCC_2015)_: ++1 v_flow_out(A_2020_winter_night_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2020_NGCC_2015) +<= 0 + +c_e_capacity_constraint(A_2025_winter_day_SOLPV_2020)_: +-1 v_flow_out(A_2025_winter_day_ethos_SOLPV_2020_ELC) ++0.6569999999999999 v_capacity(A_2025_SOLPV_2020) +-1 v_curtailment(A_2025_winter_day_ethos_SOLPV_2020_ELC) += 0 + +c_u_capacity_constraint(B_2025_winter_day_HEAT_SYS_2020)_: ++1 v_flow_out(B_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) +-0.25 v_capacity(B_2025_HEAT_SYS_2020) +<= 0 + +c_e_capacity_constraint(A_2025_summer_day_SOLPV_2025)_: +-1 v_flow_out(A_2025_summer_day_ethos_SOLPV_2025_ELC) ++1.3139999999999998 v_capacity(A_2025_SOLPV_2025) +-1 v_curtailment(A_2025_summer_day_ethos_SOLPV_2025_ELC) += 0 + +c_u_capacity_constraint(A_2020_summer_night_NGCC_2015)_: ++1 v_flow_out(A_2020_summer_night_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2020_NGCC_2015) +<= 0 + +c_u_capacity_constraint(B_2025_summer_day_HEAT_SYS_2020)_: ++1 v_flow_out(B_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) +-0.25 v_capacity(B_2025_HEAT_SYS_2020) +<= 0 + +c_u_capacity_constraint(C_B_2020_winter_night_E_TRANS_2015)_: ++1 v_flow_out(C_B_2020_winter_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_B_2020_E_TRANS_2015) +<= 0 + +c_e_capacity_constraint(A_2025_winter_night_SOLPV_2025)_: +-1 v_flow_out(A_2025_winter_night_ethos_SOLPV_2025_ELC) +-1 v_curtailment(A_2025_winter_night_ethos_SOLPV_2025_ELC) += 0 + +c_u_capacity_constraint(C_2025_summer_night_HEAT_ANN_2020)_: +-0.25 v_capacity(C_2025_HEAT_ANN_2020) +<= -0.275 + +c_e_capacity_constraint(A_2025_summer_night_SOLPV_2025)_: +-1 v_flow_out(A_2025_summer_night_ethos_SOLPV_2025_ELC) +-1 v_curtailment(A_2025_summer_night_ethos_SOLPV_2025_ELC) += 0 + +c_u_capacity_constraint(A_2025_winter_day_HEAT_SYS_2020)_: ++1 v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) +-0.25 v_capacity(A_2025_HEAT_SYS_2020) +<= 0 + +c_u_capacity_constraint(A_2025_summer_day_NGCC_2025)_: ++1 v_flow_out(A_2025_summer_day_NG_NGCC_2025_ELC) +-2.19 v_capacity(A_2025_NGCC_2025) +<= 0 + +c_u_capacity_constraint(C_A_2020_winter_night_E_TRANS_2015)_: ++1 v_flow_out(C_A_2020_winter_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_2025_winter_night_NGCC_2025)_: ++1 v_flow_out(A_2025_winter_night_NG_NGCC_2025_ELC) +-2.19 v_capacity(A_2025_NGCC_2025) +<= 0 + +c_u_capacity_constraint(A_2020_summer_night_HEAT_SYS_2020)_: +-0.25 v_capacity(A_2020_HEAT_SYS_2020) +<= -1.25 + +c_u_capacity_constraint(B_A_2020_winter_night_E_TRANS_2015)_: ++1 v_flow_out(B_A_2020_winter_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_A_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2025_winter_day_NGCC_2020)_: ++1 v_flow_out(B_2025_winter_day_NG_NGCC_2020_ELC) +-2.19 v_capacity(B_2025_NGCC_2020) +<= 0 + +c_u_capacity_constraint(B_2025_summer_night_HEAT_SYS_2020)_: ++1 v_flow_out(B_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) +-0.25 v_capacity(B_2025_HEAT_SYS_2020) +<= 0 + +c_u_capacity_constraint(A_C_2025_winter_day_E_TRANS_2015)_: ++1 v_flow_out(A_C_2025_winter_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_C_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(C_A_2020_summer_day_E_TRANS_2015)_: ++1 v_flow_out(C_A_2020_summer_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_C_2025_summer_night_E_TRANS_2015)_: ++1 v_flow_out(B_C_2025_summer_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_C_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_2020_summer_day_HEAT_SYS_2020)_: +-0.25 v_capacity(A_2020_HEAT_SYS_2020) +<= -1.25 + +c_u_capacity_constraint(A_2020_winter_night_HEAT_SYS_2020)_: +-0.25 v_capacity(A_2020_HEAT_SYS_2020) +<= -1.25 + +c_u_capacity_constraint(A_2020_winter_day_NGCC_2015)_: ++1 v_flow_out(A_2020_winter_day_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2020_NGCC_2015) +<= 0 + +c_u_capacity_constraint(B_C_2020_summer_night_E_TRANS_2015)_: ++1 v_flow_out(B_C_2020_summer_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_C_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_2025_winter_night_HEAT_SYS_2025)_: ++1 v_flow_out(B_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) +-0.25 v_capacity(B_2025_HEAT_SYS_2025) +<= 0 + +c_u_capacity_constraint(C_2025_summer_day_HEAT_ANN_2020)_: +-0.25 v_capacity(C_2025_HEAT_ANN_2020) +<= -0.275 + +c_u_capacity_constraint(A_B_2020_winter_day_E_TRANS_2015)_: ++1 v_flow_out(A_B_2020_winter_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_B_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_B_2025_summer_night_E_TRANS_2015)_: ++1 v_flow_out(A_B_2025_summer_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_B_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_2020_winter_day_HEAT_SYS_2020)_: +-0.25 v_capacity(A_2020_HEAT_SYS_2020) +<= -1.25 + +c_u_capacity_constraint(B_2020_summer_night_HEAT_SYS_2020)_: +-0.25 v_capacity(B_2020_HEAT_SYS_2020) +<= -0.75 + +c_u_capacity_constraint(C_B_2020_winter_day_E_TRANS_2015)_: ++1 v_flow_out(C_B_2020_winter_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_B_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_C_2025_winter_night_E_TRANS_2015)_: ++1 v_flow_out(B_C_2025_winter_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_C_2025_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(C_A_2020_winter_day_E_TRANS_2015)_: ++1 v_flow_out(C_A_2020_winter_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_C_2020_winter_night_E_TRANS_2015)_: ++1 v_flow_out(B_C_2020_winter_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_C_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(A_C_2020_summer_night_E_TRANS_2015)_: ++1 v_flow_out(A_C_2020_summer_night_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(A_C_2020_E_TRANS_2015) +<= 0 + +c_u_capacity_constraint(B_C_2025_summer_day_E_TRANS_2015)_: ++1 v_flow_out(B_C_2025_summer_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(B_C_2025_E_TRANS_2015) +<= 0 + +c_e_capacity_constraint(A_2025_summer_day_SOLPV_2020)_: +-1 v_flow_out(A_2025_summer_day_ethos_SOLPV_2020_ELC) ++1.3139999999999998 v_capacity(A_2025_SOLPV_2020) +-1 v_curtailment(A_2025_summer_day_ethos_SOLPV_2020_ELC) += 0 + +c_u_capacity_constraint(C_A_2025_summer_day_E_TRANS_2015)_: ++1 v_flow_out(C_A_2025_summer_day_ELC_E_TRANS_2015_ELC) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +<= 0 + +c_e_capacity_constraint(A_2025_winter_night_SOLPV_2020)_: +-1 v_flow_out(A_2025_winter_night_ethos_SOLPV_2020_ELC) +-1 v_curtailment(A_2025_winter_night_ethos_SOLPV_2020_ELC) += 0 + +c_e_capacity_constraint(A_2025_summer_night_SOLPV_2020)_: +-1 v_flow_out(A_2025_summer_night_ethos_SOLPV_2020_ELC) +-1 v_curtailment(A_2025_summer_night_ethos_SOLPV_2020_ELC) += 0 + +c_u_capacity_constraint(B_2025_winter_night_NGCC_2025)_: ++1 v_flow_out(B_2025_winter_night_NG_NGCC_2025_ELC) +-2.19 v_capacity(B_2025_NGCC_2025) +<= 0 + +c_u_capacity_constraint(A_2025_summer_day_NGCC_2020)_: ++1 v_flow_out(A_2025_summer_day_NG_NGCC_2020_ELC) +-2.19 v_capacity(A_2025_NGCC_2020) +<= 0 + +c_u_capacity_constraint(A_2025_winter_night_NGCC_2020)_: ++1 v_flow_out(A_2025_winter_night_NG_NGCC_2020_ELC) +-2.19 v_capacity(A_2025_NGCC_2020) +<= 0 + +c_u_capacity_constraint(B_2025_winter_day_NGCC_2015)_: ++1 v_flow_out(B_2025_winter_day_NG_NGCC_2015_ELC) +-2.19 v_capacity(B_2025_NGCC_2015) +<= 0 + +c_u_capacity_constraint(B_2025_summer_night_NGCC_2015)_: ++1 v_flow_out(B_2025_summer_night_NG_NGCC_2015_ELC) +-2.19 v_capacity(B_2025_NGCC_2015) +<= 0 + +c_u_capacity_constraint(B_2020_summer_day_HEAT_SYS_2020)_: +-0.25 v_capacity(B_2020_HEAT_SYS_2020) +<= -0.75 + +c_u_capacity_annual_constraint(C_2025_ANN_IMP_2020)_: +-1 v_capacity(C_2025_ANN_IMP_2020) ++1 v_flow_out_annual(C_2025_ethos_ANN_IMP_2020_ELC_C) +<= 0 + +c_u_capacity_annual_constraint(C_2020_ANN_IMP_2020)_: +-1 v_capacity(C_2020_ANN_IMP_2020) ++1 v_flow_out_annual(C_2020_ethos_ANN_IMP_2020_ELC_C) +<= 0 + +c_e_capacity_available_by_period_and_tech_constraint(B_2025_NGCC)_: +-1 v_capacity(B_2025_NGCC_2025) +-1 v_capacity(B_2025_NGCC_2015) +-1 v_capacity(B_2025_NGCC_2020) ++1 v_capacity_available_by_period_and_tech(B_2025_NGCC) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(A_2020_E_BATT)_: +-1 v_capacity(A_2020_E_BATT_2020) ++1 v_capacity_available_by_period_and_tech(A_2020_E_BATT) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(B_2025_HEAT_SYS)_: +-1 v_capacity(B_2025_HEAT_SYS_2020) +-1 v_capacity(B_2025_HEAT_SYS_2025) ++1 v_capacity_available_by_period_and_tech(B_2025_HEAT_SYS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(C_2025_HEAT_ANN)_: +-1 v_capacity(C_2025_HEAT_ANN_2020) ++1 v_capacity_available_by_period_and_tech(C_2025_HEAT_ANN) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(A_B_2025_E_TRANS)_: +-1 v_capacity(A_B_2025_E_TRANS_2015) ++1 v_capacity_available_by_period_and_tech(A_B_2025_E_TRANS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(B_C_2025_E_TRANS)_: +-1 v_capacity(B_C_2025_E_TRANS_2015) ++1 v_capacity_available_by_period_and_tech(B_C_2025_E_TRANS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(A_C_2020_E_TRANS)_: +-1 v_capacity(A_C_2020_E_TRANS_2015) ++1 v_capacity_available_by_period_and_tech(A_C_2020_E_TRANS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(C_2020_HEAT_ANN)_: +-1 v_capacity(C_2020_HEAT_ANN_2020) ++1 v_capacity_available_by_period_and_tech(C_2020_HEAT_ANN) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(C_B_2025_E_TRANS)_: +-1 v_capacity(C_B_2025_E_TRANS_2015) ++1 v_capacity_available_by_period_and_tech(C_B_2025_E_TRANS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(A_2025_SOLPV)_: +-1 v_capacity(A_2025_SOLPV_2015) +-1 v_capacity(A_2025_SOLPV_2020) +-1 v_capacity(A_2025_SOLPV_2025) ++1 v_capacity_available_by_period_and_tech(A_2025_SOLPV) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(B_2020_NGCC)_: +-1 v_capacity(B_2020_NGCC_2015) +-1 v_capacity(B_2020_NGCC_2020) ++1 v_capacity_available_by_period_and_tech(B_2020_NGCC) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(A_B_2020_E_TRANS)_: +-1 v_capacity(A_B_2020_E_TRANS_2015) ++1 v_capacity_available_by_period_and_tech(A_B_2020_E_TRANS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(B_A_2025_E_TRANS)_: +-1 v_capacity(B_A_2025_E_TRANS_2015) ++1 v_capacity_available_by_period_and_tech(B_A_2025_E_TRANS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(B_C_2020_E_TRANS)_: +-1 v_capacity(B_C_2020_E_TRANS_2015) ++1 v_capacity_available_by_period_and_tech(B_C_2020_E_TRANS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(B_2020_HEAT_SYS)_: +-1 v_capacity(B_2020_HEAT_SYS_2020) ++1 v_capacity_available_by_period_and_tech(B_2020_HEAT_SYS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(A_2025_NGCC)_: +-1 v_capacity(A_2025_NGCC_2015) +-1 v_capacity(A_2025_NGCC_2020) +-1 v_capacity(A_2025_NGCC_2025) ++1 v_capacity_available_by_period_and_tech(A_2025_NGCC) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(C_2025_ANN_IMP)_: +-1 v_capacity(C_2025_ANN_IMP_2020) ++1 v_capacity_available_by_period_and_tech(C_2025_ANN_IMP) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(C_B_2020_E_TRANS)_: +-1 v_capacity(C_B_2020_E_TRANS_2015) ++1 v_capacity_available_by_period_and_tech(C_B_2020_E_TRANS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(A_2025_HEAT_SYS)_: +-1 v_capacity(A_2025_HEAT_SYS_2020) +-1 v_capacity(A_2025_HEAT_SYS_2025) ++1 v_capacity_available_by_period_and_tech(A_2025_HEAT_SYS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(C_A_2025_E_TRANS)_: +-1 v_capacity(C_A_2025_E_TRANS_2015) ++1 v_capacity_available_by_period_and_tech(C_A_2025_E_TRANS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(A_2020_SOLPV)_: +-1 v_capacity(A_2020_SOLPV_2015) +-1 v_capacity(A_2020_SOLPV_2020) ++1 v_capacity_available_by_period_and_tech(A_2020_SOLPV) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(B_A_2020_E_TRANS)_: +-1 v_capacity(B_A_2020_E_TRANS_2015) ++1 v_capacity_available_by_period_and_tech(B_A_2020_E_TRANS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(A_2025_E_BATT)_: +-1 v_capacity(A_2025_E_BATT_2020) +-1 v_capacity(A_2025_E_BATT_2025) ++1 v_capacity_available_by_period_and_tech(A_2025_E_BATT) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(A_C_2025_E_TRANS)_: +-1 v_capacity(A_C_2025_E_TRANS_2015) ++1 v_capacity_available_by_period_and_tech(A_C_2025_E_TRANS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(A_2020_NGCC)_: +-1 v_capacity(A_2020_NGCC_2020) +-1 v_capacity(A_2020_NGCC_2015) ++1 v_capacity_available_by_period_and_tech(A_2020_NGCC) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(C_2020_ANN_IMP)_: +-1 v_capacity(C_2020_ANN_IMP_2020) ++1 v_capacity_available_by_period_and_tech(C_2020_ANN_IMP) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(C_A_2020_E_TRANS)_: +-1 v_capacity(C_A_2020_E_TRANS_2015) ++1 v_capacity_available_by_period_and_tech(C_A_2020_E_TRANS) += 0 + +c_e_capacity_available_by_period_and_tech_constraint(A_2020_HEAT_SYS)_: +-1 v_capacity(A_2020_HEAT_SYS_2020) ++1 v_capacity_available_by_period_and_tech(A_2020_HEAT_SYS) += 0 + +c_e_adjusted_capacity_constraint(C_2025_HEAT_ANN_2020)_: ++1 v_capacity(C_2025_HEAT_ANN_2020) +-1 v_new_capacity(C_HEAT_ANN_2020) += 0 + +c_e_adjusted_capacity_constraint(A_2020_HEAT_SYS_2020)_: ++1 v_capacity(A_2020_HEAT_SYS_2020) +-1 v_new_capacity(A_HEAT_SYS_2020) += 0 + +c_e_adjusted_capacity_constraint(A_2025_SOLPV_2015)_: ++1 v_capacity(A_2025_SOLPV_2015) += 0.2 + +c_e_adjusted_capacity_constraint(A_2025_E_BATT_2020)_: ++1 v_capacity(A_2025_E_BATT_2020) +-1 v_new_capacity(A_E_BATT_2020) += 0 + +c_e_adjusted_capacity_constraint(A_2025_NGCC_2015)_: ++1 v_capacity(A_2025_NGCC_2015) += 0.5 + +c_e_adjusted_capacity_constraint(A_B_2025_E_TRANS_2015)_: ++1 v_capacity(A_B_2025_E_TRANS_2015) += 1.0 + +c_e_adjusted_capacity_constraint(A_2020_NGCC_2020)_: ++1 v_capacity(A_2020_NGCC_2020) +-1 v_new_capacity(A_NGCC_2020) += 0 + +c_e_adjusted_capacity_constraint(B_2025_NGCC_2025)_: ++1 v_capacity(B_2025_NGCC_2025) +-1 v_new_capacity(B_NGCC_2025) += 0 + +c_e_adjusted_capacity_constraint(B_C_2020_E_TRANS_2015)_: ++1 v_capacity(B_C_2020_E_TRANS_2015) += 1.0 + +c_e_adjusted_capacity_constraint(C_B_2025_E_TRANS_2015)_: ++1 v_capacity(C_B_2025_E_TRANS_2015) += 1.0 + +c_e_adjusted_capacity_constraint(A_2025_SOLPV_2020)_: ++1 v_capacity(A_2025_SOLPV_2020) +-1 v_new_capacity(A_SOLPV_2020) += 0 + +c_e_adjusted_capacity_constraint(C_A_2025_E_TRANS_2015)_: ++1 v_capacity(C_A_2025_E_TRANS_2015) += 1.0 + +c_e_adjusted_capacity_constraint(A_2025_NGCC_2020)_: ++1 v_capacity(A_2025_NGCC_2020) +-1 v_new_capacity(A_NGCC_2020) += 0 + +c_e_adjusted_capacity_constraint(A_2025_E_BATT_2025)_: ++1 v_capacity(A_2025_E_BATT_2025) +-1 v_new_capacity(A_E_BATT_2025) += 0 + +c_e_adjusted_capacity_constraint(A_2020_SOLPV_2015)_: ++1 v_capacity(A_2020_SOLPV_2015) += 0.2 + +c_e_adjusted_capacity_constraint(B_2020_NGCC_2015)_: ++1 v_capacity(B_2020_NGCC_2015) += 0.3 + +c_e_adjusted_capacity_constraint(A_2025_HEAT_SYS_2020)_: ++1 v_capacity(A_2025_HEAT_SYS_2020) +-1 v_new_capacity(A_HEAT_SYS_2020) += 0 + +c_e_adjusted_capacity_constraint(B_2025_NGCC_2015)_: ++1 v_capacity(B_2025_NGCC_2015) += 0.3 + +c_e_adjusted_capacity_constraint(B_2025_HEAT_SYS_2020)_: ++1 v_capacity(B_2025_HEAT_SYS_2020) +-1 v_new_capacity(B_HEAT_SYS_2020) += 0 + +c_e_adjusted_capacity_constraint(B_A_2025_E_TRANS_2015)_: ++1 v_capacity(B_A_2025_E_TRANS_2015) += 1.0 + +c_e_adjusted_capacity_constraint(C_2020_ANN_IMP_2020)_: ++1 v_capacity(C_2020_ANN_IMP_2020) +-1 v_new_capacity(C_ANN_IMP_2020) += 0 + +c_e_adjusted_capacity_constraint(C_2020_HEAT_ANN_2020)_: ++1 v_capacity(C_2020_HEAT_ANN_2020) +-1 v_new_capacity(C_HEAT_ANN_2020) += 0 + +c_e_adjusted_capacity_constraint(A_B_2020_E_TRANS_2015)_: ++1 v_capacity(A_B_2020_E_TRANS_2015) += 1.0 + +c_e_adjusted_capacity_constraint(A_C_2020_E_TRANS_2015)_: ++1 v_capacity(A_C_2020_E_TRANS_2015) += 1.0 + +c_e_adjusted_capacity_constraint(A_2020_E_BATT_2020)_: ++1 v_capacity(A_2020_E_BATT_2020) +-1 v_new_capacity(A_E_BATT_2020) += 0 + +c_e_adjusted_capacity_constraint(A_C_2025_E_TRANS_2015)_: ++1 v_capacity(A_C_2025_E_TRANS_2015) += 1.0 + +c_e_adjusted_capacity_constraint(B_2020_HEAT_SYS_2020)_: ++1 v_capacity(B_2020_HEAT_SYS_2020) +-1 v_new_capacity(B_HEAT_SYS_2020) += 0 + +c_e_adjusted_capacity_constraint(A_2025_SOLPV_2025)_: ++1 v_capacity(A_2025_SOLPV_2025) +-1 v_new_capacity(A_SOLPV_2025) += 0 + +c_e_adjusted_capacity_constraint(B_C_2025_E_TRANS_2015)_: ++1 v_capacity(B_C_2025_E_TRANS_2015) += 1.0 + +c_e_adjusted_capacity_constraint(A_2025_NGCC_2025)_: ++1 v_capacity(A_2025_NGCC_2025) +-1 v_new_capacity(A_NGCC_2025) += 0 + +c_e_adjusted_capacity_constraint(A_2025_HEAT_SYS_2025)_: ++1 v_capacity(A_2025_HEAT_SYS_2025) +-1 v_new_capacity(A_HEAT_SYS_2025) += 0 + +c_e_adjusted_capacity_constraint(A_2020_SOLPV_2020)_: ++1 v_capacity(A_2020_SOLPV_2020) +-1 v_new_capacity(A_SOLPV_2020) += 0 + +c_e_adjusted_capacity_constraint(A_2020_NGCC_2015)_: ++1 v_capacity(A_2020_NGCC_2015) += 0.5 + +c_e_adjusted_capacity_constraint(B_2020_NGCC_2020)_: ++1 v_capacity(B_2020_NGCC_2020) +-1 v_new_capacity(B_NGCC_2020) += 0 + +c_e_adjusted_capacity_constraint(B_2025_NGCC_2020)_: ++1 v_capacity(B_2025_NGCC_2020) +-1 v_new_capacity(B_NGCC_2020) += 0 + +c_e_adjusted_capacity_constraint(C_B_2020_E_TRANS_2015)_: ++1 v_capacity(C_B_2020_E_TRANS_2015) += 1.0 + +c_e_adjusted_capacity_constraint(B_2025_HEAT_SYS_2025)_: ++1 v_capacity(B_2025_HEAT_SYS_2025) +-1 v_new_capacity(B_HEAT_SYS_2025) += 0 + +c_e_adjusted_capacity_constraint(B_A_2020_E_TRANS_2015)_: ++1 v_capacity(B_A_2020_E_TRANS_2015) += 1.0 + +c_e_adjusted_capacity_constraint(C_A_2020_E_TRANS_2015)_: ++1 v_capacity(C_A_2020_E_TRANS_2015) += 1.0 + +c_e_adjusted_capacity_constraint(C_2025_ANN_IMP_2020)_: ++1 v_capacity(C_2025_ANN_IMP_2020) +-1 v_new_capacity(C_ANN_IMP_2020) += 0 + +c_e_demand_constraint(A_2025_HEAT)_: ++1 v_flow_out_annual(A_2025_ELC_HEAT_SYS_2025_HEAT) ++1 v_flow_out_annual(A_2025_ELC_HEAT_SYS_2020_HEAT) += 5.5 + +c_e_demand_constraint(B_2025_HEAT)_: ++1 v_flow_out_annual(B_2025_ELC_HEAT_SYS_2020_HEAT) ++1 v_flow_out_annual(B_2025_ELC_HEAT_SYS_2025_HEAT) += 3.3 + +c_e_demand_activity_constraint(A_2025_winter_day_HEAT_SYS_2020_HEAT)_: +-1 v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) ++0.25 v_flow_out_annual(A_2025_ELC_HEAT_SYS_2020_HEAT) += 0 + +c_e_demand_activity_constraint(B_2025_summer_night_HEAT_SYS_2025_HEAT)_: +-1 v_flow_out(B_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) ++0.25 v_flow_out_annual(B_2025_ELC_HEAT_SYS_2025_HEAT) += 0 + +c_e_demand_activity_constraint(B_2025_winter_night_HEAT_SYS_2025_HEAT)_: +-1 v_flow_out(B_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) ++0.25 v_flow_out_annual(B_2025_ELC_HEAT_SYS_2025_HEAT) += 0 + +c_e_demand_activity_constraint(B_2025_summer_night_HEAT_SYS_2020_HEAT)_: +-1 v_flow_out(B_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) ++0.25 v_flow_out_annual(B_2025_ELC_HEAT_SYS_2020_HEAT) += 0 + +c_e_demand_activity_constraint(B_2025_winter_night_HEAT_SYS_2020_HEAT)_: +-1 v_flow_out(B_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) ++0.25 v_flow_out_annual(B_2025_ELC_HEAT_SYS_2020_HEAT) += 0 + +c_e_demand_activity_constraint(B_2025_summer_day_HEAT_SYS_2025_HEAT)_: +-1 v_flow_out(B_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) ++0.25 v_flow_out_annual(B_2025_ELC_HEAT_SYS_2025_HEAT) += 0 + +c_e_demand_activity_constraint(A_2025_winter_night_HEAT_SYS_2025_HEAT)_: +-1 v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) ++0.25 v_flow_out_annual(A_2025_ELC_HEAT_SYS_2025_HEAT) += 0 + +c_e_demand_activity_constraint(A_2025_winter_night_HEAT_SYS_2020_HEAT)_: +-1 v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) ++0.25 v_flow_out_annual(A_2025_ELC_HEAT_SYS_2020_HEAT) += 0 + +c_e_demand_activity_constraint(B_2025_summer_day_HEAT_SYS_2020_HEAT)_: +-1 v_flow_out(B_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) ++0.25 v_flow_out_annual(B_2025_ELC_HEAT_SYS_2020_HEAT) += 0 + +c_e_demand_activity_constraint(A_2025_winter_day_HEAT_SYS_2025_HEAT)_: +-1 v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) ++0.25 v_flow_out_annual(A_2025_ELC_HEAT_SYS_2025_HEAT) += 0 + +c_e_demand_activity_constraint(B_2025_winter_day_HEAT_SYS_2025_HEAT)_: +-1 v_flow_out(B_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) ++0.25 v_flow_out_annual(B_2025_ELC_HEAT_SYS_2025_HEAT) += 0 + +c_e_demand_activity_constraint(B_2025_winter_day_HEAT_SYS_2020_HEAT)_: +-1 v_flow_out(B_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) ++0.25 v_flow_out_annual(B_2025_ELC_HEAT_SYS_2020_HEAT) += 0 + +c_e_demand_activity_constraint(A_2025_summer_night_HEAT_SYS_2025_HEAT)_: +-1 v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) ++0.25 v_flow_out_annual(A_2025_ELC_HEAT_SYS_2025_HEAT) += 0 + +c_e_demand_activity_constraint(A_2025_summer_night_HEAT_SYS_2020_HEAT)_: +-1 v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) ++0.25 v_flow_out_annual(A_2025_ELC_HEAT_SYS_2020_HEAT) += 0 + +c_e_demand_activity_constraint(A_2025_summer_day_HEAT_SYS_2025_HEAT)_: +-1 v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) ++0.25 v_flow_out_annual(A_2025_ELC_HEAT_SYS_2025_HEAT) += 0 + +c_e_demand_activity_constraint(A_2025_summer_day_HEAT_SYS_2020_HEAT)_: +-1 v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) ++0.25 v_flow_out_annual(A_2025_ELC_HEAT_SYS_2020_HEAT) += 0 + +c_e_commodity_balance_constraint(B_2020_winter_day_NG)_: +-1.8181818181818181 v_flow_out(B_2020_winter_day_NG_NGCC_2020_ELC) ++1 v_flow_out(B_2020_winter_day_ethos_IMP_NG_2020_NG) +-1.8181818181818181 v_flow_out(B_2020_winter_day_NG_NGCC_2015_ELC) += 0 + +c_e_commodity_balance_constraint(B_2025_summer_night_ELC)_: ++1 v_flow_out(B_2025_summer_night_NG_NGCC_2015_ELC) +-1.0526315789473684 v_flow_out(B_C_2025_summer_night_ELC_E_TRANS_2015_ELC) +-1 v_flow_out(B_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) +-1.0526315789473684 v_flow_out(B_A_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_2025_summer_night_NG_NGCC_2020_ELC) +-1 v_flow_out(B_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) ++1 v_flow_out(C_B_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_B_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_2025_summer_night_NG_NGCC_2025_ELC) += 0 + +c_e_commodity_balance_constraint(C_2020_winter_night_ELC)_: +-1.0526315789473684 v_flow_out(C_B_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_C_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_C_2020_winter_night_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_A_2020_winter_night_ELC_E_TRANS_2015_ELC) += 0 + +c_e_commodity_balance_constraint(A_2025_winter_night_NG)_: +-1.8181818181818181 v_flow_out(A_2025_winter_night_NG_NGCC_2015_ELC) +-1.8181818181818181 v_flow_out(A_2025_winter_night_NG_NGCC_2020_ELC) +-1.8181818181818181 v_flow_out(A_2025_winter_night_NG_NGCC_2025_ELC) ++1 v_flow_out(A_2025_winter_night_ethos_IMP_NG_2020_NG) += 0 + +c_e_commodity_balance_constraint(A_2025_summer_night_ELC)_: ++1 v_flow_out(A_2025_summer_night_ethos_SOLPV_2015_ELC) ++1 v_flow_out(C_A_2025_summer_night_ELC_E_TRANS_2015_ELC) +-1 v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) +-1.0526315789473684 v_flow_out(A_C_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_A_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2025_summer_night_NG_NGCC_2020_ELC) ++1 v_flow_out(A_2025_summer_night_ethos_SOLPV_2020_ELC) ++1 v_flow_out(A_2025_summer_night_ELC_E_BATT_2020_ELC) ++1 v_flow_out(A_2025_summer_night_ELC_E_BATT_2025_ELC) +-1 v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) +-1.0526315789473684 v_flow_out(A_B_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2025_summer_night_NG_NGCC_2025_ELC) ++1 v_flow_out(A_2025_summer_night_ethos_SOLPV_2025_ELC) ++1 v_flow_out(A_2025_summer_night_NG_NGCC_2015_ELC) +-1 v_flow_in(A_2025_summer_night_ELC_E_BATT_2020_ELC) +-1 v_flow_in(A_2025_summer_night_ELC_E_BATT_2025_ELC) += 0 + +c_e_commodity_balance_constraint(A_2025_winter_day_ELC)_: ++1 v_flow_out(B_A_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2025_winter_day_ELC_E_BATT_2020_ELC) +-1 v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) +-1.0526315789473684 v_flow_out(A_C_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(C_A_2025_winter_day_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(A_B_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2025_winter_day_NG_NGCC_2020_ELC) ++1 v_flow_out(A_2025_winter_day_ethos_SOLPV_2015_ELC) ++1 v_flow_out(A_2025_winter_day_ELC_E_BATT_2025_ELC) +-1 v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) ++1 v_flow_out(A_2025_winter_day_ethos_SOLPV_2020_ELC) ++1 v_flow_out(A_2025_winter_day_NG_NGCC_2025_ELC) ++1 v_flow_out(A_2025_winter_day_ethos_SOLPV_2025_ELC) ++1 v_flow_out(A_2025_winter_day_NG_NGCC_2015_ELC) +-1 v_flow_in(A_2025_winter_day_ELC_E_BATT_2020_ELC) +-1 v_flow_in(A_2025_winter_day_ELC_E_BATT_2025_ELC) += 0 + +c_e_commodity_balance_constraint(B_2025_winter_day_NG)_: +-1.8181818181818181 v_flow_out(B_2025_winter_day_NG_NGCC_2025_ELC) ++1 v_flow_out(B_2025_winter_day_ethos_IMP_NG_2020_NG) +-1.8181818181818181 v_flow_out(B_2025_winter_day_NG_NGCC_2015_ELC) +-1.8181818181818181 v_flow_out(B_2025_winter_day_NG_NGCC_2020_ELC) += 0 + +c_e_commodity_balance_constraint(C_2020_summer_night_ELC)_: +-1.0526315789473684 v_flow_out(C_A_2020_summer_night_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_B_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_C_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_C_2020_summer_night_ELC_E_TRANS_2015_ELC) += 0 + +c_e_commodity_balance_constraint(A_2020_summer_night_NG)_: +-1.8181818181818181 v_flow_out(A_2020_summer_night_NG_NGCC_2015_ELC) +-1.8181818181818181 v_flow_out(A_2020_summer_night_NG_NGCC_2020_ELC) ++1 v_flow_out(A_2020_summer_night_ethos_IMP_NG_2020_NG) += 0 + +c_e_commodity_balance_constraint(A_2020_winter_day_NG)_: +-1.8181818181818181 v_flow_out(A_2020_winter_day_NG_NGCC_2015_ELC) +-1.8181818181818181 v_flow_out(A_2020_winter_day_NG_NGCC_2020_ELC) ++1 v_flow_out(A_2020_winter_day_ethos_IMP_NG_2020_NG) += 0 + +c_e_commodity_balance_constraint(C_2025_summer_day_ELC)_: ++1 v_flow_out(B_C_2025_summer_day_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_B_2025_summer_day_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_A_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_C_2025_summer_day_ELC_E_TRANS_2015_ELC) += 0 + +c_e_commodity_balance_constraint(B_2025_winter_night_NG)_: ++1 v_flow_out(B_2025_winter_night_ethos_IMP_NG_2020_NG) +-1.8181818181818181 v_flow_out(B_2025_winter_night_NG_NGCC_2015_ELC) +-1.8181818181818181 v_flow_out(B_2025_winter_night_NG_NGCC_2020_ELC) +-1.8181818181818181 v_flow_out(B_2025_winter_night_NG_NGCC_2025_ELC) += 0 + +c_e_commodity_balance_constraint(A_2020_summer_day_ELC)_: +-1.0526315789473684 v_flow_out(A_C_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2020_summer_day_NG_NGCC_2020_ELC) ++1 v_flow_out(A_2020_summer_day_ethos_SOLPV_2020_ELC) ++1 v_flow_out(A_2020_summer_day_ELC_E_BATT_2020_ELC) ++1 v_flow_out(A_2020_summer_day_NG_NGCC_2015_ELC) ++1 v_flow_out(B_A_2020_summer_day_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(A_B_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(C_A_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2020_summer_day_ethos_SOLPV_2015_ELC) +-1 v_flow_in(A_2020_summer_day_ELC_E_BATT_2020_ELC) += 1.25 + +c_e_commodity_balance_constraint(B_2020_summer_day_ELC)_: ++1 v_flow_out(B_2020_summer_day_NG_NGCC_2020_ELC) ++1 v_flow_out(C_B_2020_summer_day_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(B_A_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_2020_summer_day_NG_NGCC_2015_ELC) +-1.0526315789473684 v_flow_out(B_C_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_B_2020_summer_day_ELC_E_TRANS_2015_ELC) += 0.75 + +c_e_commodity_balance_constraint(B_2025_summer_day_NG)_: +-1.8181818181818181 v_flow_out(B_2025_summer_day_NG_NGCC_2020_ELC) ++1 v_flow_out(B_2025_summer_day_ethos_IMP_NG_2020_NG) +-1.8181818181818181 v_flow_out(B_2025_summer_day_NG_NGCC_2025_ELC) +-1.8181818181818181 v_flow_out(B_2025_summer_day_NG_NGCC_2015_ELC) += 0 + +c_e_commodity_balance_constraint(B_2020_summer_night_NG)_: +-1.8181818181818181 v_flow_out(B_2020_summer_night_NG_NGCC_2015_ELC) +-1.8181818181818181 v_flow_out(B_2020_summer_night_NG_NGCC_2020_ELC) ++1 v_flow_out(B_2020_summer_night_ethos_IMP_NG_2020_NG) += 0 + +c_e_commodity_balance_constraint(B_2020_winter_day_ELC)_: +-1.0526315789473684 v_flow_out(B_A_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_2020_winter_day_NG_NGCC_2020_ELC) +-1.0526315789473684 v_flow_out(B_C_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_2020_winter_day_NG_NGCC_2015_ELC) ++1 v_flow_out(C_B_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_B_2020_winter_day_ELC_E_TRANS_2015_ELC) += 0.75 + +c_e_commodity_balance_constraint(B_2020_winter_night_NG)_: +-1.8181818181818181 v_flow_out(B_2020_winter_night_NG_NGCC_2015_ELC) +-1.8181818181818181 v_flow_out(B_2020_winter_night_NG_NGCC_2020_ELC) ++1 v_flow_out(B_2020_winter_night_ethos_IMP_NG_2020_NG) += 0 + +c_e_commodity_balance_constraint(A_2025_summer_day_NG)_: +-1.8181818181818181 v_flow_out(A_2025_summer_day_NG_NGCC_2020_ELC) ++1 v_flow_out(A_2025_summer_day_ethos_IMP_NG_2020_NG) +-1.8181818181818181 v_flow_out(A_2025_summer_day_NG_NGCC_2025_ELC) +-1.8181818181818181 v_flow_out(A_2025_summer_day_NG_NGCC_2015_ELC) += 0 + +c_e_commodity_balance_constraint(A_2025_winter_night_ELC)_: ++1 v_flow_out(A_2025_winter_night_NG_NGCC_2015_ELC) ++1 v_flow_out(C_A_2025_winter_night_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(A_B_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2025_winter_night_ethos_SOLPV_2015_ELC) ++1 v_flow_out(A_2025_winter_night_ELC_E_BATT_2020_ELC) +-1 v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) +-1.0526315789473684 v_flow_out(A_C_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2025_winter_night_NG_NGCC_2020_ELC) ++1 v_flow_out(A_2025_winter_night_ethos_SOLPV_2020_ELC) ++1 v_flow_out(B_A_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2025_winter_night_NG_NGCC_2025_ELC) ++1 v_flow_out(A_2025_winter_night_ELC_E_BATT_2025_ELC) +-1 v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) ++1 v_flow_out(A_2025_winter_night_ethos_SOLPV_2025_ELC) +-1 v_flow_in(A_2025_winter_night_ELC_E_BATT_2025_ELC) +-1 v_flow_in(A_2025_winter_night_ELC_E_BATT_2020_ELC) += 0 + +c_e_commodity_balance_constraint(C_2025_winter_night_ELC)_: ++1 v_flow_out(B_C_2025_winter_night_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_A_2025_winter_night_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_B_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_C_2025_winter_night_ELC_E_TRANS_2015_ELC) += 0 + +c_e_commodity_balance_constraint(B_2025_winter_day_ELC)_: +-1.0526315789473684 v_flow_out(B_A_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_2025_winter_day_NG_NGCC_2025_ELC) +-1.0526315789473684 v_flow_out(B_C_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_B_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(C_B_2025_winter_day_ELC_E_TRANS_2015_ELC) +-1 v_flow_out(B_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) ++1 v_flow_out(B_2025_winter_day_NG_NGCC_2015_ELC) +-1 v_flow_out(B_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) ++1 v_flow_out(B_2025_winter_day_NG_NGCC_2020_ELC) += 0 + +c_e_commodity_balance_constraint(C_2020_winter_day_ELC)_: ++1 v_flow_out(A_C_2020_winter_day_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_A_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_C_2020_winter_day_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_B_2020_winter_day_ELC_E_TRANS_2015_ELC) += 0 + +c_e_commodity_balance_constraint(A_2020_summer_night_ELC)_: ++1 v_flow_out(A_2020_summer_night_ELC_E_BATT_2020_ELC) ++1 v_flow_out(C_A_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2020_summer_night_NG_NGCC_2015_ELC) ++1 v_flow_out(A_2020_summer_night_ethos_SOLPV_2015_ELC) ++1 v_flow_out(A_2020_summer_night_NG_NGCC_2020_ELC) +-1.0526315789473684 v_flow_out(A_C_2020_summer_night_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(A_B_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2020_summer_night_ethos_SOLPV_2020_ELC) ++1 v_flow_out(B_A_2020_summer_night_ELC_E_TRANS_2015_ELC) +-1 v_flow_in(A_2020_summer_night_ELC_E_BATT_2020_ELC) += 1.25 + +c_e_commodity_balance_constraint(A_2020_winter_day_ELC)_: ++1 v_flow_out(B_A_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2020_winter_day_ELC_E_BATT_2020_ELC) +-1.0526315789473684 v_flow_out(A_C_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2020_winter_day_NG_NGCC_2015_ELC) ++1 v_flow_out(A_2020_winter_day_ethos_SOLPV_2015_ELC) ++1 v_flow_out(C_A_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2020_winter_day_NG_NGCC_2020_ELC) +-1.0526315789473684 v_flow_out(A_B_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2020_winter_day_ethos_SOLPV_2020_ELC) +-1 v_flow_in(A_2020_winter_day_ELC_E_BATT_2020_ELC) += 1.25 + +c_e_commodity_balance_constraint(A_2020_winter_night_NG)_: +-1.8181818181818181 v_flow_out(A_2020_winter_night_NG_NGCC_2020_ELC) ++1 v_flow_out(A_2020_winter_night_ethos_IMP_NG_2020_NG) +-1.8181818181818181 v_flow_out(A_2020_winter_night_NG_NGCC_2015_ELC) += 0 + +c_e_commodity_balance_constraint(A_2025_summer_night_NG)_: +-1.8181818181818181 v_flow_out(A_2025_summer_night_NG_NGCC_2020_ELC) ++1 v_flow_out(A_2025_summer_night_ethos_IMP_NG_2020_NG) +-1.8181818181818181 v_flow_out(A_2025_summer_night_NG_NGCC_2025_ELC) +-1.8181818181818181 v_flow_out(A_2025_summer_night_NG_NGCC_2015_ELC) += 0 + +c_e_commodity_balance_constraint(B_2025_summer_night_NG)_: +-1.8181818181818181 v_flow_out(B_2025_summer_night_NG_NGCC_2015_ELC) +-1.8181818181818181 v_flow_out(B_2025_summer_night_NG_NGCC_2020_ELC) ++1 v_flow_out(B_2025_summer_night_ethos_IMP_NG_2020_NG) +-1.8181818181818181 v_flow_out(B_2025_summer_night_NG_NGCC_2025_ELC) += 0 + +c_e_commodity_balance_constraint(A_2025_winter_day_NG)_: +-1.8181818181818181 v_flow_out(A_2025_winter_day_NG_NGCC_2020_ELC) +-1.8181818181818181 v_flow_out(A_2025_winter_day_NG_NGCC_2025_ELC) ++1 v_flow_out(A_2025_winter_day_ethos_IMP_NG_2020_NG) +-1.8181818181818181 v_flow_out(A_2025_winter_day_NG_NGCC_2015_ELC) += 0 + +c_e_commodity_balance_constraint(C_2025_summer_night_ELC)_: +-1.0526315789473684 v_flow_out(C_A_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_C_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_C_2025_summer_night_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_B_2025_summer_night_ELC_E_TRANS_2015_ELC) += 0 + +c_e_commodity_balance_constraint(B_2025_winter_night_ELC)_: +-1.0526315789473684 v_flow_out(B_C_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_2025_winter_night_NG_NGCC_2015_ELC) ++1 v_flow_out(A_B_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(C_B_2025_winter_night_ELC_E_TRANS_2015_ELC) +-1 v_flow_out(B_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) +-1.0526315789473684 v_flow_out(B_A_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_2025_winter_night_NG_NGCC_2020_ELC) +-1 v_flow_out(B_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) ++1 v_flow_out(B_2025_winter_night_NG_NGCC_2025_ELC) += 0 + +c_e_commodity_balance_constraint(B_2020_summer_night_ELC)_: ++1 v_flow_out(B_2020_summer_night_NG_NGCC_2015_ELC) ++1 v_flow_out(C_B_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_2020_summer_night_NG_NGCC_2020_ELC) +-1.0526315789473684 v_flow_out(B_C_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_B_2020_summer_night_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(B_A_2020_summer_night_ELC_E_TRANS_2015_ELC) += 0.75 + +c_e_commodity_balance_constraint(B_2020_winter_night_ELC)_: ++1 v_flow_out(B_2020_winter_night_NG_NGCC_2015_ELC) ++1 v_flow_out(C_B_2020_winter_night_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(B_A_2020_winter_night_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(B_C_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_2020_winter_night_NG_NGCC_2020_ELC) ++1 v_flow_out(A_B_2020_winter_night_ELC_E_TRANS_2015_ELC) += 0.75 + +c_e_commodity_balance_constraint(A_2025_summer_day_ELC)_: +-1.0526315789473684 v_flow_out(A_B_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2025_summer_day_NG_NGCC_2020_ELC) ++1 v_flow_out(A_2025_summer_day_ethos_SOLPV_2020_ELC) ++1 v_flow_out(B_A_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2025_summer_day_ELC_E_BATT_2025_ELC) +-1 v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) ++1 v_flow_out(A_2025_summer_day_NG_NGCC_2025_ELC) ++1 v_flow_out(A_2025_summer_day_ethos_SOLPV_2025_ELC) ++1 v_flow_out(C_A_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2025_summer_day_NG_NGCC_2015_ELC) ++1 v_flow_out(A_2025_summer_day_ethos_SOLPV_2015_ELC) ++1 v_flow_out(A_2025_summer_day_ELC_E_BATT_2020_ELC) +-1 v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) +-1.0526315789473684 v_flow_out(A_C_2025_summer_day_ELC_E_TRANS_2015_ELC) +-1 v_flow_in(A_2025_summer_day_ELC_E_BATT_2025_ELC) +-1 v_flow_in(A_2025_summer_day_ELC_E_BATT_2020_ELC) += 0 + +c_e_commodity_balance_constraint(B_2025_summer_day_ELC)_: ++1 v_flow_out(A_B_2025_summer_day_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(B_A_2025_summer_day_ELC_E_TRANS_2015_ELC) +-1 v_flow_out(B_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) ++1 v_flow_out(B_2025_summer_day_NG_NGCC_2020_ELC) +-1.0526315789473684 v_flow_out(B_C_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(C_B_2025_summer_day_ELC_E_TRANS_2015_ELC) +-1 v_flow_out(B_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) ++1 v_flow_out(B_2025_summer_day_NG_NGCC_2025_ELC) ++1 v_flow_out(B_2025_summer_day_NG_NGCC_2015_ELC) += 0 + +c_e_commodity_balance_constraint(C_2020_summer_day_ELC)_: ++1 v_flow_out(A_C_2020_summer_day_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_B_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_C_2020_summer_day_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_A_2020_summer_day_ELC_E_TRANS_2015_ELC) += 0 + +c_e_commodity_balance_constraint(A_2020_summer_day_NG)_: +-1.8181818181818181 v_flow_out(A_2020_summer_day_NG_NGCC_2020_ELC) ++1 v_flow_out(A_2020_summer_day_ethos_IMP_NG_2020_NG) +-1.8181818181818181 v_flow_out(A_2020_summer_day_NG_NGCC_2015_ELC) += 0 + +c_e_commodity_balance_constraint(C_2025_winter_day_ELC)_: ++1 v_flow_out(A_C_2025_winter_day_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_A_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(B_C_2025_winter_day_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(C_B_2025_winter_day_ELC_E_TRANS_2015_ELC) += 0 + +c_e_commodity_balance_constraint(A_2020_winter_night_ELC)_: ++1 v_flow_out(B_A_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2020_winter_night_NG_NGCC_2020_ELC) ++1 v_flow_out(A_2020_winter_night_ethos_SOLPV_2015_ELC) ++1 v_flow_out(A_2020_winter_night_ELC_E_BATT_2020_ELC) +-1.0526315789473684 v_flow_out(A_C_2020_winter_night_ELC_E_TRANS_2015_ELC) +-1.0526315789473684 v_flow_out(A_B_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2020_winter_night_NG_NGCC_2015_ELC) ++1 v_flow_out(C_A_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1 v_flow_out(A_2020_winter_night_ethos_SOLPV_2020_ELC) +-1 v_flow_in(A_2020_winter_night_ELC_E_BATT_2020_ELC) += 1.25 + +c_e_commodity_balance_constraint(B_2020_summer_day_NG)_: +-1.8181818181818181 v_flow_out(B_2020_summer_day_NG_NGCC_2020_ELC) ++1 v_flow_out(B_2020_summer_day_ethos_IMP_NG_2020_NG) +-1.8181818181818181 v_flow_out(B_2020_summer_day_NG_NGCC_2015_ELC) += 0 + +c_e_annual_commodity_balance_constraint(C_2020_ELC_C)_: ++1 v_flow_out_annual(C_2020_ethos_ANN_IMP_2020_ELC_C) += 1.0 + +c_e_annual_commodity_balance_constraint(C_2025_ELC_C)_: ++1 v_flow_out_annual(C_2025_ethos_ANN_IMP_2020_ELC_C) += 1.1 + +c_e_regional_exchange_capacity_constraint(A_C_2020_E_TRANS_2015)_: ++1 v_capacity(A_C_2020_E_TRANS_2015) +-1 v_capacity(C_A_2020_E_TRANS_2015) += 0 + +c_e_regional_exchange_capacity_constraint(B_A_2020_E_TRANS_2015)_: +-1 v_capacity(A_B_2020_E_TRANS_2015) ++1 v_capacity(B_A_2020_E_TRANS_2015) += 0 + +c_e_regional_exchange_capacity_constraint(C_B_2020_E_TRANS_2015)_: +-1 v_capacity(B_C_2020_E_TRANS_2015) ++1 v_capacity(C_B_2020_E_TRANS_2015) += 0 + +c_e_regional_exchange_capacity_constraint(A_B_2025_E_TRANS_2015)_: ++1 v_capacity(A_B_2025_E_TRANS_2015) +-1 v_capacity(B_A_2025_E_TRANS_2015) += 0 + +c_e_regional_exchange_capacity_constraint(C_A_2025_E_TRANS_2015)_: ++1 v_capacity(C_A_2025_E_TRANS_2015) +-1 v_capacity(A_C_2025_E_TRANS_2015) += 0 + +c_e_regional_exchange_capacity_constraint(C_A_2020_E_TRANS_2015)_: +-1 v_capacity(A_C_2020_E_TRANS_2015) ++1 v_capacity(C_A_2020_E_TRANS_2015) += 0 + +c_e_regional_exchange_capacity_constraint(A_B_2020_E_TRANS_2015)_: ++1 v_capacity(A_B_2020_E_TRANS_2015) +-1 v_capacity(B_A_2020_E_TRANS_2015) += 0 + +c_e_regional_exchange_capacity_constraint(B_C_2025_E_TRANS_2015)_: +-1 v_capacity(C_B_2025_E_TRANS_2015) ++1 v_capacity(B_C_2025_E_TRANS_2015) += 0 + +c_e_regional_exchange_capacity_constraint(A_C_2025_E_TRANS_2015)_: +-1 v_capacity(C_A_2025_E_TRANS_2015) ++1 v_capacity(A_C_2025_E_TRANS_2015) += 0 + +c_e_regional_exchange_capacity_constraint(B_A_2025_E_TRANS_2015)_: +-1 v_capacity(A_B_2025_E_TRANS_2015) ++1 v_capacity(B_A_2025_E_TRANS_2015) += 0 + +c_e_regional_exchange_capacity_constraint(C_B_2025_E_TRANS_2015)_: ++1 v_capacity(C_B_2025_E_TRANS_2015) +-1 v_capacity(B_C_2025_E_TRANS_2015) += 0 + +c_e_regional_exchange_capacity_constraint(B_C_2020_E_TRANS_2015)_: ++1 v_capacity(B_C_2020_E_TRANS_2015) +-1 v_capacity(C_B_2020_E_TRANS_2015) += 0 + +c_e_storage_energy_constraint(A_2020_winter_night_E_BATT_2020)_: +-1 v_flow_out(A_2020_winter_night_ELC_E_BATT_2020_ELC) ++0.85 v_flow_in(A_2020_winter_night_ELC_E_BATT_2020_ELC) ++1 v_storage_init(A_2020_winter_E_BATT_2020) +-1 v_storage_level(A_2020_winter_day_E_BATT_2020) += 0 + +c_e_storage_energy_constraint(A_2020_summer_night_E_BATT_2020)_: +-1 v_flow_out(A_2020_summer_night_ELC_E_BATT_2020_ELC) ++0.85 v_flow_in(A_2020_summer_night_ELC_E_BATT_2020_ELC) ++1 v_storage_init(A_2020_summer_E_BATT_2020) +-1 v_storage_level(A_2020_summer_day_E_BATT_2020) += 0 + +c_e_storage_energy_constraint(A_2025_winter_day_E_BATT_2020)_: +-1 v_flow_out(A_2025_winter_day_ELC_E_BATT_2020_ELC) ++0.85 v_flow_in(A_2025_winter_day_ELC_E_BATT_2020_ELC) ++1 v_storage_level(A_2025_winter_day_E_BATT_2020) +-1 v_storage_level(A_2025_winter_night_E_BATT_2020) += 0 + +c_e_storage_energy_constraint(A_2025_summer_night_E_BATT_2025)_: +-1 v_flow_out(A_2025_summer_night_ELC_E_BATT_2025_ELC) ++0.85 v_flow_in(A_2025_summer_night_ELC_E_BATT_2025_ELC) ++1 v_storage_init(A_2025_summer_E_BATT_2025) +-1 v_storage_level(A_2025_summer_day_E_BATT_2025) += 0 + +c_e_storage_energy_constraint(A_2025_summer_day_E_BATT_2025)_: +-1 v_flow_out(A_2025_summer_day_ELC_E_BATT_2025_ELC) ++0.85 v_flow_in(A_2025_summer_day_ELC_E_BATT_2025_ELC) +-1 v_storage_level(A_2025_summer_night_E_BATT_2025) ++1 v_storage_level(A_2025_summer_day_E_BATT_2025) += 0 + +c_e_storage_energy_constraint(A_2025_winter_night_E_BATT_2020)_: +-1 v_flow_out(A_2025_winter_night_ELC_E_BATT_2020_ELC) ++0.85 v_flow_in(A_2025_winter_night_ELC_E_BATT_2020_ELC) ++1 v_storage_init(A_2025_winter_E_BATT_2020) +-1 v_storage_level(A_2025_winter_day_E_BATT_2020) += 0 + +c_e_storage_energy_constraint(A_2025_winter_day_E_BATT_2025)_: +-1 v_flow_out(A_2025_winter_day_ELC_E_BATT_2025_ELC) ++0.85 v_flow_in(A_2025_winter_day_ELC_E_BATT_2025_ELC) ++1 v_storage_level(A_2025_winter_day_E_BATT_2025) +-1 v_storage_level(A_2025_winter_night_E_BATT_2025) += 0 + +c_e_storage_energy_constraint(A_2020_winter_day_E_BATT_2020)_: +-1 v_flow_out(A_2020_winter_day_ELC_E_BATT_2020_ELC) ++0.85 v_flow_in(A_2020_winter_day_ELC_E_BATT_2020_ELC) +-1 v_storage_level(A_2020_winter_night_E_BATT_2020) ++1 v_storage_level(A_2020_winter_day_E_BATT_2020) += 0 + +c_e_storage_energy_constraint(A_2025_summer_night_E_BATT_2020)_: +-1 v_flow_out(A_2025_summer_night_ELC_E_BATT_2020_ELC) ++0.85 v_flow_in(A_2025_summer_night_ELC_E_BATT_2020_ELC) ++1 v_storage_init(A_2025_summer_E_BATT_2020) +-1 v_storage_level(A_2025_summer_day_E_BATT_2020) += 0 + +c_e_storage_energy_constraint(A_2025_summer_day_E_BATT_2020)_: +-1 v_flow_out(A_2025_summer_day_ELC_E_BATT_2020_ELC) ++0.85 v_flow_in(A_2025_summer_day_ELC_E_BATT_2020_ELC) +-1 v_storage_level(A_2025_summer_night_E_BATT_2020) ++1 v_storage_level(A_2025_summer_day_E_BATT_2020) += 0 + +c_e_storage_energy_constraint(A_2020_summer_day_E_BATT_2020)_: +-1 v_flow_out(A_2020_summer_day_ELC_E_BATT_2020_ELC) ++0.85 v_flow_in(A_2020_summer_day_ELC_E_BATT_2020_ELC) +-1 v_storage_level(A_2020_summer_night_E_BATT_2020) ++1 v_storage_level(A_2020_summer_day_E_BATT_2020) += 0 + +c_e_storage_energy_constraint(A_2025_winter_night_E_BATT_2025)_: +-1 v_flow_out(A_2025_winter_night_ELC_E_BATT_2025_ELC) ++0.85 v_flow_in(A_2025_winter_night_ELC_E_BATT_2025_ELC) ++1 v_storage_init(A_2025_winter_E_BATT_2025) +-1 v_storage_level(A_2025_winter_day_E_BATT_2025) += 0 + +c_e_storage_level_last_tod_constraint(A_2025_summer_E_BATT_2020)_: +-1 v_storage_init(A_2025_summer_E_BATT_2020) ++1 v_storage_level(A_2025_summer_night_E_BATT_2020) += 0 + +c_e_storage_level_last_tod_constraint(A_2025_winter_E_BATT_2020)_: +-1 v_storage_init(A_2025_winter_E_BATT_2020) ++1 v_storage_level(A_2025_winter_night_E_BATT_2020) += 0 + +c_e_storage_level_last_tod_constraint(A_2020_summer_E_BATT_2020)_: +-1 v_storage_init(A_2020_summer_E_BATT_2020) ++1 v_storage_level(A_2020_summer_night_E_BATT_2020) += 0 + +c_e_storage_level_last_tod_constraint(A_2020_winter_E_BATT_2020)_: +-1 v_storage_init(A_2020_winter_E_BATT_2020) ++1 v_storage_level(A_2020_winter_night_E_BATT_2020) += 0 + +c_e_storage_level_last_tod_constraint(A_2025_summer_E_BATT_2025)_: +-1 v_storage_init(A_2025_summer_E_BATT_2025) ++1 v_storage_level(A_2025_summer_night_E_BATT_2025) += 0 + +c_e_storage_level_last_tod_constraint(A_2025_winter_E_BATT_2025)_: +-1 v_storage_init(A_2025_winter_E_BATT_2025) ++1 v_storage_level(A_2025_winter_night_E_BATT_2025) += 0 + +c_u_storage_energy_upper_bound_constraint(A_2020_winter_night_E_BATT_2020)_: +-0.73 v_capacity(A_2020_E_BATT_2020) ++1 v_storage_level(A_2020_winter_night_E_BATT_2020) +<= 0 + +c_u_storage_energy_upper_bound_constraint(A_2020_summer_night_E_BATT_2020)_: +-0.73 v_capacity(A_2020_E_BATT_2020) ++1 v_storage_level(A_2020_summer_night_E_BATT_2020) +<= 0 + +c_u_storage_energy_upper_bound_constraint(A_2025_winter_day_E_BATT_2020)_: +-0.73 v_capacity(A_2025_E_BATT_2020) ++1 v_storage_level(A_2025_winter_day_E_BATT_2020) +<= 0 + +c_u_storage_energy_upper_bound_constraint(A_2025_summer_night_E_BATT_2025)_: +-0.73 v_capacity(A_2025_E_BATT_2025) ++1 v_storage_level(A_2025_summer_night_E_BATT_2025) +<= 0 + +c_u_storage_energy_upper_bound_constraint(A_2025_summer_day_E_BATT_2025)_: +-0.73 v_capacity(A_2025_E_BATT_2025) ++1 v_storage_level(A_2025_summer_day_E_BATT_2025) +<= 0 + +c_u_storage_energy_upper_bound_constraint(A_2025_winter_night_E_BATT_2020)_: +-0.73 v_capacity(A_2025_E_BATT_2020) ++1 v_storage_level(A_2025_winter_night_E_BATT_2020) +<= 0 + +c_u_storage_energy_upper_bound_constraint(A_2025_winter_day_E_BATT_2025)_: +-0.73 v_capacity(A_2025_E_BATT_2025) ++1 v_storage_level(A_2025_winter_day_E_BATT_2025) +<= 0 + +c_u_storage_energy_upper_bound_constraint(A_2020_winter_day_E_BATT_2020)_: +-0.73 v_capacity(A_2020_E_BATT_2020) ++1 v_storage_level(A_2020_winter_day_E_BATT_2020) +<= 0 + +c_u_storage_energy_upper_bound_constraint(A_2025_summer_night_E_BATT_2020)_: +-0.73 v_capacity(A_2025_E_BATT_2020) ++1 v_storage_level(A_2025_summer_night_E_BATT_2020) +<= 0 + +c_u_storage_energy_upper_bound_constraint(A_2025_summer_day_E_BATT_2020)_: +-0.73 v_capacity(A_2025_E_BATT_2020) ++1 v_storage_level(A_2025_summer_day_E_BATT_2020) +<= 0 + +c_u_storage_energy_upper_bound_constraint(A_2020_summer_day_E_BATT_2020)_: +-0.73 v_capacity(A_2020_E_BATT_2020) ++1 v_storage_level(A_2020_summer_day_E_BATT_2020) +<= 0 + +c_u_storage_energy_upper_bound_constraint(A_2025_winter_night_E_BATT_2025)_: +-0.73 v_capacity(A_2025_E_BATT_2025) ++1 v_storage_level(A_2025_winter_night_E_BATT_2025) +<= 0 + +c_u_storage_charge_rate_constraint(A_2020_winter_night_E_BATT_2020)_: +-2.19 v_capacity(A_2020_E_BATT_2020) ++0.85 v_flow_in(A_2020_winter_night_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_charge_rate_constraint(A_2020_summer_night_E_BATT_2020)_: +-2.19 v_capacity(A_2020_E_BATT_2020) ++0.85 v_flow_in(A_2020_summer_night_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_charge_rate_constraint(A_2025_winter_day_E_BATT_2020)_: +-2.19 v_capacity(A_2025_E_BATT_2020) ++0.85 v_flow_in(A_2025_winter_day_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_charge_rate_constraint(A_2025_summer_night_E_BATT_2025)_: +-2.19 v_capacity(A_2025_E_BATT_2025) ++0.85 v_flow_in(A_2025_summer_night_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_storage_charge_rate_constraint(A_2025_summer_day_E_BATT_2025)_: +-2.19 v_capacity(A_2025_E_BATT_2025) ++0.85 v_flow_in(A_2025_summer_day_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_storage_charge_rate_constraint(A_2025_winter_night_E_BATT_2020)_: +-2.19 v_capacity(A_2025_E_BATT_2020) ++0.85 v_flow_in(A_2025_winter_night_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_charge_rate_constraint(A_2025_winter_day_E_BATT_2025)_: +-2.19 v_capacity(A_2025_E_BATT_2025) ++0.85 v_flow_in(A_2025_winter_day_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_storage_charge_rate_constraint(A_2020_winter_day_E_BATT_2020)_: +-2.19 v_capacity(A_2020_E_BATT_2020) ++0.85 v_flow_in(A_2020_winter_day_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_charge_rate_constraint(A_2025_summer_night_E_BATT_2020)_: +-2.19 v_capacity(A_2025_E_BATT_2020) ++0.85 v_flow_in(A_2025_summer_night_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_charge_rate_constraint(A_2025_summer_day_E_BATT_2020)_: +-2.19 v_capacity(A_2025_E_BATT_2020) ++0.85 v_flow_in(A_2025_summer_day_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_charge_rate_constraint(A_2020_summer_day_E_BATT_2020)_: +-2.19 v_capacity(A_2020_E_BATT_2020) ++0.85 v_flow_in(A_2020_summer_day_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_charge_rate_constraint(A_2025_winter_night_E_BATT_2025)_: +-2.19 v_capacity(A_2025_E_BATT_2025) ++0.85 v_flow_in(A_2025_winter_night_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_storage_discharge_rate_constraint(A_2020_winter_night_E_BATT_2020)_: ++1 v_flow_out(A_2020_winter_night_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2020_E_BATT_2020) +<= 0 + +c_u_storage_discharge_rate_constraint(A_2020_summer_night_E_BATT_2020)_: ++1 v_flow_out(A_2020_summer_night_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2020_E_BATT_2020) +<= 0 + +c_u_storage_discharge_rate_constraint(A_2025_winter_day_E_BATT_2020)_: ++1 v_flow_out(A_2025_winter_day_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2025_E_BATT_2020) +<= 0 + +c_u_storage_discharge_rate_constraint(A_2025_summer_night_E_BATT_2025)_: ++1 v_flow_out(A_2025_summer_night_ELC_E_BATT_2025_ELC) +-2.19 v_capacity(A_2025_E_BATT_2025) +<= 0 + +c_u_storage_discharge_rate_constraint(A_2025_summer_day_E_BATT_2025)_: ++1 v_flow_out(A_2025_summer_day_ELC_E_BATT_2025_ELC) +-2.19 v_capacity(A_2025_E_BATT_2025) +<= 0 + +c_u_storage_discharge_rate_constraint(A_2025_winter_night_E_BATT_2020)_: ++1 v_flow_out(A_2025_winter_night_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2025_E_BATT_2020) +<= 0 + +c_u_storage_discharge_rate_constraint(A_2025_winter_day_E_BATT_2025)_: ++1 v_flow_out(A_2025_winter_day_ELC_E_BATT_2025_ELC) +-2.19 v_capacity(A_2025_E_BATT_2025) +<= 0 + +c_u_storage_discharge_rate_constraint(A_2020_winter_day_E_BATT_2020)_: ++1 v_flow_out(A_2020_winter_day_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2020_E_BATT_2020) +<= 0 + +c_u_storage_discharge_rate_constraint(A_2025_summer_night_E_BATT_2020)_: ++1 v_flow_out(A_2025_summer_night_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2025_E_BATT_2020) +<= 0 + +c_u_storage_discharge_rate_constraint(A_2025_summer_day_E_BATT_2020)_: ++1 v_flow_out(A_2025_summer_day_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2025_E_BATT_2020) +<= 0 + +c_u_storage_discharge_rate_constraint(A_2020_summer_day_E_BATT_2020)_: ++1 v_flow_out(A_2020_summer_day_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2020_E_BATT_2020) +<= 0 + +c_u_storage_discharge_rate_constraint(A_2025_winter_night_E_BATT_2025)_: ++1 v_flow_out(A_2025_winter_night_ELC_E_BATT_2025_ELC) +-2.19 v_capacity(A_2025_E_BATT_2025) +<= 0 + +c_u_storage_throughput_constraint(A_2020_winter_night_E_BATT_2020)_: ++1 v_flow_out(A_2020_winter_night_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2020_E_BATT_2020) ++0.85 v_flow_in(A_2020_winter_night_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_throughput_constraint(A_2020_summer_night_E_BATT_2020)_: ++1 v_flow_out(A_2020_summer_night_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2020_E_BATT_2020) ++0.85 v_flow_in(A_2020_summer_night_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_throughput_constraint(A_2025_winter_day_E_BATT_2020)_: ++1 v_flow_out(A_2025_winter_day_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2025_E_BATT_2020) ++0.85 v_flow_in(A_2025_winter_day_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_throughput_constraint(A_2025_summer_night_E_BATT_2025)_: ++1 v_flow_out(A_2025_summer_night_ELC_E_BATT_2025_ELC) +-2.19 v_capacity(A_2025_E_BATT_2025) ++0.85 v_flow_in(A_2025_summer_night_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_storage_throughput_constraint(A_2025_summer_day_E_BATT_2025)_: ++1 v_flow_out(A_2025_summer_day_ELC_E_BATT_2025_ELC) +-2.19 v_capacity(A_2025_E_BATT_2025) ++0.85 v_flow_in(A_2025_summer_day_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_storage_throughput_constraint(A_2025_winter_night_E_BATT_2020)_: ++1 v_flow_out(A_2025_winter_night_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2025_E_BATT_2020) ++0.85 v_flow_in(A_2025_winter_night_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_throughput_constraint(A_2025_winter_day_E_BATT_2025)_: ++1 v_flow_out(A_2025_winter_day_ELC_E_BATT_2025_ELC) +-2.19 v_capacity(A_2025_E_BATT_2025) ++0.85 v_flow_in(A_2025_winter_day_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_storage_throughput_constraint(A_2020_winter_day_E_BATT_2020)_: ++1 v_flow_out(A_2020_winter_day_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2020_E_BATT_2020) ++0.85 v_flow_in(A_2020_winter_day_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_throughput_constraint(A_2025_summer_night_E_BATT_2020)_: ++1 v_flow_out(A_2025_summer_night_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2025_E_BATT_2020) ++0.85 v_flow_in(A_2025_summer_night_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_throughput_constraint(A_2025_summer_day_E_BATT_2020)_: ++1 v_flow_out(A_2025_summer_day_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2025_E_BATT_2020) ++0.85 v_flow_in(A_2025_summer_day_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_throughput_constraint(A_2020_summer_day_E_BATT_2020)_: ++1 v_flow_out(A_2020_summer_day_ELC_E_BATT_2020_ELC) +-2.19 v_capacity(A_2020_E_BATT_2020) ++0.85 v_flow_in(A_2020_summer_day_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_storage_throughput_constraint(A_2025_winter_night_E_BATT_2025)_: ++1 v_flow_out(A_2025_winter_night_ELC_E_BATT_2025_ELC) +-2.19 v_capacity(A_2025_E_BATT_2025) ++0.85 v_flow_in(A_2025_winter_night_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_operating_reserve_margin_constraint(A_B_2025_summer_night_elec_AB)_: ++1.08 v_flow_out(A_2025_summer_night_ethos_SOLPV_2015_ELC) ++1.08 v_flow_out(B_2025_summer_night_NG_NGCC_2015_ELC) ++1.08 v_flow_out(C_A_2025_summer_night_ELC_E_TRANS_2015_ELC) +-1.1368421052631579 v_flow_out(B_C_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) ++1.08 v_flow_out(B_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) +-1.1368421052631579 v_flow_out(A_C_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2025_summer_night_NG_NGCC_2020_ELC) ++1.08 v_flow_out(A_2025_summer_night_ethos_SOLPV_2020_ELC) ++1.08 v_flow_out(B_2025_summer_night_NG_NGCC_2020_ELC) ++0.18000000000000005 v_flow_out(A_2025_summer_night_ELC_E_BATT_2020_ELC) ++1.08 v_flow_out(B_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) ++0.18000000000000005 v_flow_out(A_2025_summer_night_ELC_E_BATT_2025_ELC) ++1.08 v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) ++1.08 v_flow_out(C_B_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2025_summer_night_NG_NGCC_2025_ELC) ++1.08 v_flow_out(A_2025_summer_night_ethos_SOLPV_2025_ELC) ++1.08 v_flow_out(A_2025_summer_night_NG_NGCC_2015_ELC) ++1.08 v_flow_out(B_2025_summer_night_NG_NGCC_2025_ELC) +-2.0805 v_capacity(A_2025_NGCC_2015) +-2.0805 v_capacity(B_2025_NGCC_2025) +-2.19 v_capacity(C_B_2025_E_TRANS_2015) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +-2.0805 v_capacity(A_2025_NGCC_2020) +-0.25 v_capacity(A_2025_HEAT_SYS_2020) +-2.0805 v_capacity(B_2025_NGCC_2015) +-0.25 v_capacity(B_2025_HEAT_SYS_2020) ++2.19 v_capacity(A_C_2025_E_TRANS_2015) ++2.19 v_capacity(B_C_2025_E_TRANS_2015) +-2.0805 v_capacity(A_2025_NGCC_2025) +-0.25 v_capacity(A_2025_HEAT_SYS_2025) +-2.0805 v_capacity(B_2025_NGCC_2020) +-0.25 v_capacity(B_2025_HEAT_SYS_2025) +-0.18000000000000005 v_flow_in(A_2025_summer_night_ELC_E_BATT_2020_ELC) +-0.18000000000000005 v_flow_in(A_2025_summer_night_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_operating_reserve_margin_constraint(A_2025_summer_day_elec_A)_: +-1.1578947368421053 v_flow_out(A_B_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_summer_day_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2025_summer_day_ethos_SOLPV_2020_ELC) ++1.1 v_flow_out(B_A_2025_summer_day_ELC_E_TRANS_2015_ELC) ++0.20000000000000007 v_flow_out(A_2025_summer_day_ELC_E_BATT_2025_ELC) ++1.1 v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) ++1.1 v_flow_out(A_2025_summer_day_NG_NGCC_2025_ELC) ++1.1 v_flow_out(A_2025_summer_day_ethos_SOLPV_2025_ELC) ++1.1 v_flow_out(C_A_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_summer_day_NG_NGCC_2015_ELC) ++1.1 v_flow_out(A_2025_summer_day_ethos_SOLPV_2015_ELC) ++0.20000000000000007 v_flow_out(A_2025_summer_day_ELC_E_BATT_2020_ELC) ++1.1 v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) +-1.1578947368421053 v_flow_out(A_C_2025_summer_day_ELC_E_TRANS_2015_ELC) +-1.0512 v_capacity(A_2025_SOLPV_2015) +-2.0805 v_capacity(A_2025_NGCC_2015) ++2.19 v_capacity(A_B_2025_E_TRANS_2015) +-1.0512 v_capacity(A_2025_SOLPV_2020) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +-2.0805 v_capacity(A_2025_NGCC_2020) +-0.25 v_capacity(A_2025_HEAT_SYS_2020) +-2.19 v_capacity(B_A_2025_E_TRANS_2015) ++2.19 v_capacity(A_C_2025_E_TRANS_2015) +-1.0512 v_capacity(A_2025_SOLPV_2025) +-2.0805 v_capacity(A_2025_NGCC_2025) +-0.25 v_capacity(A_2025_HEAT_SYS_2025) +-0.20000000000000007 v_flow_in(A_2025_summer_day_ELC_E_BATT_2025_ELC) +-0.20000000000000007 v_flow_in(A_2025_summer_day_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_operating_reserve_margin_constraint(B_2020_summer_day_NGCC)_: ++1.1 v_flow_out(B_2020_summer_day_NG_NGCC_2020_ELC) ++1.1 v_flow_out(B_2020_summer_day_NG_NGCC_2015_ELC) +-2.0805 v_capacity(B_2020_NGCC_2015) +-2.0805 v_capacity(B_2020_NGCC_2020) +<= 0 + +c_u_operating_reserve_margin_constraint(A_2020_winter_night_elec_A)_: ++1.1 v_flow_out(B_A_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_winter_night_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2020_winter_night_ethos_SOLPV_2015_ELC) ++0.20000000000000007 v_flow_out(A_2020_winter_night_ELC_E_BATT_2020_ELC) +-1.1578947368421053 v_flow_out(A_C_2020_winter_night_ELC_E_TRANS_2015_ELC) +-1.1578947368421053 v_flow_out(A_B_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_winter_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(C_A_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_winter_night_ethos_SOLPV_2020_ELC) +-0.25 v_capacity(A_2020_HEAT_SYS_2020) +-1.971 v_capacity(A_2020_NGCC_2020) ++2.19 v_capacity(A_B_2020_E_TRANS_2015) ++2.19 v_capacity(A_C_2020_E_TRANS_2015) +-1.971 v_capacity(A_2020_NGCC_2015) +-2.19 v_capacity(B_A_2020_E_TRANS_2015) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +-0.20000000000000007 v_flow_in(A_2020_winter_night_ELC_E_BATT_2020_ELC) +<= -1.375 + +c_u_operating_reserve_margin_constraint(B_2020_winter_day_NGCC)_: ++1.1 v_flow_out(B_2020_winter_day_NG_NGCC_2020_ELC) ++1.1 v_flow_out(B_2020_winter_day_NG_NGCC_2015_ELC) +-1.971 v_capacity(B_2020_NGCC_2015) +-1.971 v_capacity(B_2020_NGCC_2020) +<= 0 + +c_u_operating_reserve_margin_constraint(A_B_2025_winter_day_elec_AB)_: ++0.18000000000000005 v_flow_out(A_2025_winter_day_ELC_E_BATT_2020_ELC) ++1.08 v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) ++1.08 v_flow_out(B_2025_winter_day_NG_NGCC_2025_ELC) +-1.1368421052631579 v_flow_out(A_C_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(C_A_2025_winter_day_ELC_E_TRANS_2015_ELC) +-1.1368421052631579 v_flow_out(B_C_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2025_winter_day_NG_NGCC_2020_ELC) ++1.08 v_flow_out(A_2025_winter_day_ethos_SOLPV_2015_ELC) ++1.08 v_flow_out(C_B_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(B_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) ++0.18000000000000005 v_flow_out(A_2025_winter_day_ELC_E_BATT_2025_ELC) ++1.08 v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) ++1.08 v_flow_out(B_2025_winter_day_NG_NGCC_2015_ELC) ++1.08 v_flow_out(A_2025_winter_day_ethos_SOLPV_2020_ELC) ++1.08 v_flow_out(A_2025_winter_day_NG_NGCC_2025_ELC) ++1.08 v_flow_out(A_2025_winter_day_ethos_SOLPV_2025_ELC) ++1.08 v_flow_out(B_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) ++1.08 v_flow_out(A_2025_winter_day_NG_NGCC_2015_ELC) ++1.08 v_flow_out(B_2025_winter_day_NG_NGCC_2020_ELC) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2015) +-1.971 v_capacity(A_2025_NGCC_2015) +-1.971 v_capacity(B_2025_NGCC_2025) +-2.19 v_capacity(C_B_2025_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2020) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +-1.971 v_capacity(A_2025_NGCC_2020) +-0.25 v_capacity(A_2025_HEAT_SYS_2020) +-1.971 v_capacity(B_2025_NGCC_2015) +-0.25 v_capacity(B_2025_HEAT_SYS_2020) ++2.19 v_capacity(A_C_2025_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2025) ++2.19 v_capacity(B_C_2025_E_TRANS_2015) +-1.971 v_capacity(A_2025_NGCC_2025) +-0.25 v_capacity(A_2025_HEAT_SYS_2025) +-1.971 v_capacity(B_2025_NGCC_2020) +-0.25 v_capacity(B_2025_HEAT_SYS_2025) +-0.18000000000000005 v_flow_in(A_2025_winter_day_ELC_E_BATT_2020_ELC) +-0.18000000000000005 v_flow_in(A_2025_winter_day_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_operating_reserve_margin_constraint(A_2025_summer_night_elec_A)_: ++1.1 v_flow_out(A_2025_summer_night_ethos_SOLPV_2015_ELC) ++1.1 v_flow_out(C_A_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) +-1.1578947368421053 v_flow_out(A_C_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(B_A_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_summer_night_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2025_summer_night_ethos_SOLPV_2020_ELC) ++0.20000000000000007 v_flow_out(A_2025_summer_night_ELC_E_BATT_2020_ELC) ++0.20000000000000007 v_flow_out(A_2025_summer_night_ELC_E_BATT_2025_ELC) ++1.1 v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) +-1.1578947368421053 v_flow_out(A_B_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_summer_night_NG_NGCC_2025_ELC) ++1.1 v_flow_out(A_2025_summer_night_ethos_SOLPV_2025_ELC) ++1.1 v_flow_out(A_2025_summer_night_NG_NGCC_2015_ELC) +-2.0805 v_capacity(A_2025_NGCC_2015) ++2.19 v_capacity(A_B_2025_E_TRANS_2015) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +-2.0805 v_capacity(A_2025_NGCC_2020) +-0.25 v_capacity(A_2025_HEAT_SYS_2020) +-2.19 v_capacity(B_A_2025_E_TRANS_2015) ++2.19 v_capacity(A_C_2025_E_TRANS_2015) +-2.0805 v_capacity(A_2025_NGCC_2025) +-0.25 v_capacity(A_2025_HEAT_SYS_2025) +-0.20000000000000007 v_flow_in(A_2025_summer_night_ELC_E_BATT_2020_ELC) +-0.20000000000000007 v_flow_in(A_2025_summer_night_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_operating_reserve_margin_constraint(A_2025_winter_day_elec_A)_: ++1.1 v_flow_out(B_A_2025_winter_day_ELC_E_TRANS_2015_ELC) ++0.20000000000000007 v_flow_out(A_2025_winter_day_ELC_E_BATT_2020_ELC) ++1.1 v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) +-1.1578947368421053 v_flow_out(A_C_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(C_A_2025_winter_day_ELC_E_TRANS_2015_ELC) +-1.1578947368421053 v_flow_out(A_B_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_winter_day_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2025_winter_day_ethos_SOLPV_2015_ELC) ++0.20000000000000007 v_flow_out(A_2025_winter_day_ELC_E_BATT_2025_ELC) ++1.1 v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) ++1.1 v_flow_out(A_2025_winter_day_ethos_SOLPV_2020_ELC) ++1.1 v_flow_out(A_2025_winter_day_NG_NGCC_2025_ELC) ++1.1 v_flow_out(A_2025_winter_day_ethos_SOLPV_2025_ELC) ++1.1 v_flow_out(A_2025_winter_day_NG_NGCC_2015_ELC) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2015) +-1.971 v_capacity(A_2025_NGCC_2015) ++2.19 v_capacity(A_B_2025_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2020) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +-1.971 v_capacity(A_2025_NGCC_2020) +-0.25 v_capacity(A_2025_HEAT_SYS_2020) +-2.19 v_capacity(B_A_2025_E_TRANS_2015) ++2.19 v_capacity(A_C_2025_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2025) +-1.971 v_capacity(A_2025_NGCC_2025) +-0.25 v_capacity(A_2025_HEAT_SYS_2025) +-0.20000000000000007 v_flow_in(A_2025_winter_day_ELC_E_BATT_2020_ELC) +-0.20000000000000007 v_flow_in(A_2025_winter_day_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_operating_reserve_margin_constraint(B_2025_winter_day_NGCC)_: ++1.1 v_flow_out(B_2025_winter_day_NG_NGCC_2025_ELC) ++1.1 v_flow_out(B_2025_winter_day_NG_NGCC_2015_ELC) ++1.1 v_flow_out(B_2025_winter_day_NG_NGCC_2020_ELC) +-1.971 v_capacity(B_2025_NGCC_2025) +-1.971 v_capacity(B_2025_NGCC_2015) +-1.971 v_capacity(B_2025_NGCC_2020) +<= 0 + +c_u_operating_reserve_margin_constraint(A_B_2020_winter_day_elec_AB)_: ++0.18000000000000005 v_flow_out(A_2020_winter_day_ELC_E_BATT_2020_ELC) ++1.08 v_flow_out(B_2020_winter_day_NG_NGCC_2020_ELC) +-1.1368421052631579 v_flow_out(A_C_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2020_winter_day_NG_NGCC_2015_ELC) ++1.08 v_flow_out(A_2020_winter_day_ethos_SOLPV_2015_ELC) ++1.08 v_flow_out(C_A_2020_winter_day_ELC_E_TRANS_2015_ELC) +-1.1368421052631579 v_flow_out(B_C_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(B_2020_winter_day_NG_NGCC_2015_ELC) ++1.08 v_flow_out(C_B_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2020_winter_day_NG_NGCC_2020_ELC) ++1.08 v_flow_out(A_2020_winter_day_ethos_SOLPV_2020_ELC) +-0.25 v_capacity(A_2020_HEAT_SYS_2020) +-1.971 v_capacity(A_2020_NGCC_2020) ++2.19 v_capacity(B_C_2020_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2020_SOLPV_2015) +-1.971 v_capacity(B_2020_NGCC_2015) ++2.19 v_capacity(A_C_2020_E_TRANS_2015) +-0.25 v_capacity(B_2020_HEAT_SYS_2020) +-0.32849999999999996 v_capacity(A_2020_SOLPV_2020) +-1.971 v_capacity(A_2020_NGCC_2015) +-1.971 v_capacity(B_2020_NGCC_2020) +-2.19 v_capacity(C_B_2020_E_TRANS_2015) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +-0.18000000000000005 v_flow_in(A_2020_winter_day_ELC_E_BATT_2020_ELC) +<= -2.16 + +c_u_operating_reserve_margin_constraint(A_B_2025_summer_day_elec_AB)_: ++1.08 v_flow_out(A_2025_summer_day_NG_NGCC_2020_ELC) ++1.08 v_flow_out(A_2025_summer_day_ethos_SOLPV_2020_ELC) ++1.08 v_flow_out(B_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) ++0.18000000000000005 v_flow_out(A_2025_summer_day_ELC_E_BATT_2025_ELC) ++1.08 v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) ++1.08 v_flow_out(B_2025_summer_day_NG_NGCC_2020_ELC) +-1.1368421052631579 v_flow_out(B_C_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2025_summer_day_NG_NGCC_2025_ELC) ++1.08 v_flow_out(A_2025_summer_day_ethos_SOLPV_2025_ELC) ++1.08 v_flow_out(C_B_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(C_A_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(B_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) ++1.08 v_flow_out(A_2025_summer_day_NG_NGCC_2015_ELC) ++1.08 v_flow_out(A_2025_summer_day_ethos_SOLPV_2015_ELC) ++1.08 v_flow_out(B_2025_summer_day_NG_NGCC_2025_ELC) ++0.18000000000000005 v_flow_out(A_2025_summer_day_ELC_E_BATT_2020_ELC) ++1.08 v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) +-1.1368421052631579 v_flow_out(A_C_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(B_2025_summer_day_NG_NGCC_2015_ELC) +-1.0512 v_capacity(A_2025_SOLPV_2015) +-2.0805 v_capacity(A_2025_NGCC_2015) +-2.0805 v_capacity(B_2025_NGCC_2025) +-2.19 v_capacity(C_B_2025_E_TRANS_2015) +-1.0512 v_capacity(A_2025_SOLPV_2020) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +-2.0805 v_capacity(A_2025_NGCC_2020) +-0.25 v_capacity(A_2025_HEAT_SYS_2020) +-2.0805 v_capacity(B_2025_NGCC_2015) +-0.25 v_capacity(B_2025_HEAT_SYS_2020) ++2.19 v_capacity(A_C_2025_E_TRANS_2015) +-1.0512 v_capacity(A_2025_SOLPV_2025) ++2.19 v_capacity(B_C_2025_E_TRANS_2015) +-2.0805 v_capacity(A_2025_NGCC_2025) +-0.25 v_capacity(A_2025_HEAT_SYS_2025) +-2.0805 v_capacity(B_2025_NGCC_2020) +-0.25 v_capacity(B_2025_HEAT_SYS_2025) +-0.18000000000000005 v_flow_in(A_2025_summer_day_ELC_E_BATT_2025_ELC) +-0.18000000000000005 v_flow_in(A_2025_summer_day_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_operating_reserve_margin_constraint(B_2025_winter_night_NGCC)_: ++1.1 v_flow_out(B_2025_winter_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(B_2025_winter_night_NG_NGCC_2020_ELC) ++1.1 v_flow_out(B_2025_winter_night_NG_NGCC_2025_ELC) +-1.971 v_capacity(B_2025_NGCC_2025) +-1.971 v_capacity(B_2025_NGCC_2015) +-1.971 v_capacity(B_2025_NGCC_2020) +<= 0 + +c_u_operating_reserve_margin_constraint(B_2020_summer_night_NGCC)_: ++1.1 v_flow_out(B_2020_summer_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(B_2020_summer_night_NG_NGCC_2020_ELC) +-2.0805 v_capacity(B_2020_NGCC_2015) +-2.0805 v_capacity(B_2020_NGCC_2020) +<= 0 + +c_u_operating_reserve_margin_constraint(B_2020_winter_night_NGCC)_: ++1.1 v_flow_out(B_2020_winter_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(B_2020_winter_night_NG_NGCC_2020_ELC) +-1.971 v_capacity(B_2020_NGCC_2015) +-1.971 v_capacity(B_2020_NGCC_2020) +<= 0 + +c_u_operating_reserve_margin_constraint(A_B_2020_summer_night_elec_AB)_: ++0.18000000000000005 v_flow_out(A_2020_summer_night_ELC_E_BATT_2020_ELC) ++1.08 v_flow_out(C_A_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(B_2020_summer_night_NG_NGCC_2015_ELC) ++1.08 v_flow_out(C_B_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2020_summer_night_NG_NGCC_2015_ELC) ++1.08 v_flow_out(A_2020_summer_night_ethos_SOLPV_2015_ELC) ++1.08 v_flow_out(A_2020_summer_night_NG_NGCC_2020_ELC) ++1.08 v_flow_out(B_2020_summer_night_NG_NGCC_2020_ELC) +-1.1368421052631579 v_flow_out(A_C_2020_summer_night_ELC_E_TRANS_2015_ELC) +-1.1368421052631579 v_flow_out(B_C_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2020_summer_night_ethos_SOLPV_2020_ELC) +-0.25 v_capacity(A_2020_HEAT_SYS_2020) +-2.0805 v_capacity(A_2020_NGCC_2020) ++2.19 v_capacity(B_C_2020_E_TRANS_2015) +-2.0805 v_capacity(B_2020_NGCC_2015) ++2.19 v_capacity(A_C_2020_E_TRANS_2015) +-0.25 v_capacity(B_2020_HEAT_SYS_2020) +-2.0805 v_capacity(A_2020_NGCC_2015) +-2.0805 v_capacity(B_2020_NGCC_2020) +-2.19 v_capacity(C_B_2020_E_TRANS_2015) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +-0.18000000000000005 v_flow_in(A_2020_summer_night_ELC_E_BATT_2020_ELC) +<= -2.16 + +c_u_operating_reserve_margin_constraint(B_2025_summer_day_NGCC)_: ++1.1 v_flow_out(B_2025_summer_day_NG_NGCC_2020_ELC) ++1.1 v_flow_out(B_2025_summer_day_NG_NGCC_2025_ELC) ++1.1 v_flow_out(B_2025_summer_day_NG_NGCC_2015_ELC) +-2.0805 v_capacity(B_2025_NGCC_2025) +-2.0805 v_capacity(B_2025_NGCC_2015) +-2.0805 v_capacity(B_2025_NGCC_2020) +<= 0 + +c_u_operating_reserve_margin_constraint(A_2020_summer_day_elec_A)_: +-1.1578947368421053 v_flow_out(A_C_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_summer_day_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2020_summer_day_ethos_SOLPV_2020_ELC) ++0.20000000000000007 v_flow_out(A_2020_summer_day_ELC_E_BATT_2020_ELC) ++1.1 v_flow_out(A_2020_summer_day_NG_NGCC_2015_ELC) ++1.1 v_flow_out(B_A_2020_summer_day_ELC_E_TRANS_2015_ELC) +-1.1578947368421053 v_flow_out(A_B_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(C_A_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_summer_day_ethos_SOLPV_2015_ELC) +-0.25 v_capacity(A_2020_HEAT_SYS_2020) +-2.0805 v_capacity(A_2020_NGCC_2020) +-1.0512 v_capacity(A_2020_SOLPV_2015) ++2.19 v_capacity(A_B_2020_E_TRANS_2015) ++2.19 v_capacity(A_C_2020_E_TRANS_2015) +-1.0512 v_capacity(A_2020_SOLPV_2020) +-2.0805 v_capacity(A_2020_NGCC_2015) +-2.19 v_capacity(B_A_2020_E_TRANS_2015) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +-0.20000000000000007 v_flow_in(A_2020_summer_day_ELC_E_BATT_2020_ELC) +<= -1.375 + +c_u_operating_reserve_margin_constraint(A_B_2020_winter_night_elec_AB)_: ++1.08 v_flow_out(B_2020_winter_night_NG_NGCC_2015_ELC) ++1.08 v_flow_out(C_B_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2020_winter_night_NG_NGCC_2020_ELC) +-1.1368421052631579 v_flow_out(B_C_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2020_winter_night_ethos_SOLPV_2015_ELC) ++0.18000000000000005 v_flow_out(A_2020_winter_night_ELC_E_BATT_2020_ELC) +-1.1368421052631579 v_flow_out(A_C_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(B_2020_winter_night_NG_NGCC_2020_ELC) ++1.08 v_flow_out(A_2020_winter_night_NG_NGCC_2015_ELC) ++1.08 v_flow_out(C_A_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2020_winter_night_ethos_SOLPV_2020_ELC) +-0.25 v_capacity(A_2020_HEAT_SYS_2020) +-1.971 v_capacity(A_2020_NGCC_2020) ++2.19 v_capacity(B_C_2020_E_TRANS_2015) +-1.971 v_capacity(B_2020_NGCC_2015) ++2.19 v_capacity(A_C_2020_E_TRANS_2015) +-0.25 v_capacity(B_2020_HEAT_SYS_2020) +-1.971 v_capacity(A_2020_NGCC_2015) +-1.971 v_capacity(B_2020_NGCC_2020) +-2.19 v_capacity(C_B_2020_E_TRANS_2015) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +-0.18000000000000005 v_flow_in(A_2020_winter_night_ELC_E_BATT_2020_ELC) +<= -2.16 + +c_u_operating_reserve_margin_constraint(A_B_2025_winter_night_elec_AB)_: ++1.08 v_flow_out(A_2025_winter_night_NG_NGCC_2015_ELC) +-1.1368421052631579 v_flow_out(B_C_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(C_A_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(B_2025_winter_night_NG_NGCC_2015_ELC) ++1.08 v_flow_out(A_2025_winter_night_ethos_SOLPV_2015_ELC) ++1.08 v_flow_out(C_B_2025_winter_night_ELC_E_TRANS_2015_ELC) ++0.18000000000000005 v_flow_out(A_2025_winter_night_ELC_E_BATT_2020_ELC) ++1.08 v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) +-1.1368421052631579 v_flow_out(A_C_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(B_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) ++1.08 v_flow_out(A_2025_winter_night_NG_NGCC_2020_ELC) ++1.08 v_flow_out(A_2025_winter_night_ethos_SOLPV_2020_ELC) ++1.08 v_flow_out(A_2025_winter_night_NG_NGCC_2025_ELC) ++1.08 v_flow_out(B_2025_winter_night_NG_NGCC_2020_ELC) ++1.08 v_flow_out(B_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) ++0.18000000000000005 v_flow_out(A_2025_winter_night_ELC_E_BATT_2025_ELC) ++1.08 v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) ++1.08 v_flow_out(B_2025_winter_night_NG_NGCC_2025_ELC) ++1.08 v_flow_out(A_2025_winter_night_ethos_SOLPV_2025_ELC) +-1.971 v_capacity(A_2025_NGCC_2015) +-1.971 v_capacity(B_2025_NGCC_2025) +-2.19 v_capacity(C_B_2025_E_TRANS_2015) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +-1.971 v_capacity(A_2025_NGCC_2020) +-0.25 v_capacity(A_2025_HEAT_SYS_2020) +-1.971 v_capacity(B_2025_NGCC_2015) +-0.25 v_capacity(B_2025_HEAT_SYS_2020) ++2.19 v_capacity(A_C_2025_E_TRANS_2015) ++2.19 v_capacity(B_C_2025_E_TRANS_2015) +-1.971 v_capacity(A_2025_NGCC_2025) +-0.25 v_capacity(A_2025_HEAT_SYS_2025) +-1.971 v_capacity(B_2025_NGCC_2020) +-0.25 v_capacity(B_2025_HEAT_SYS_2025) +-0.18000000000000005 v_flow_in(A_2025_winter_night_ELC_E_BATT_2025_ELC) +-0.18000000000000005 v_flow_in(A_2025_winter_night_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_operating_reserve_margin_constraint(A_2025_winter_night_elec_A)_: ++1.1 v_flow_out(A_2025_winter_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(C_A_2025_winter_night_ELC_E_TRANS_2015_ELC) +-1.1578947368421053 v_flow_out(A_B_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_winter_night_ethos_SOLPV_2015_ELC) ++0.20000000000000007 v_flow_out(A_2025_winter_night_ELC_E_BATT_2020_ELC) ++1.1 v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) +-1.1578947368421053 v_flow_out(A_C_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_winter_night_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2025_winter_night_ethos_SOLPV_2020_ELC) ++1.1 v_flow_out(B_A_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_winter_night_NG_NGCC_2025_ELC) ++0.20000000000000007 v_flow_out(A_2025_winter_night_ELC_E_BATT_2025_ELC) ++1.1 v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) ++1.1 v_flow_out(A_2025_winter_night_ethos_SOLPV_2025_ELC) +-1.971 v_capacity(A_2025_NGCC_2015) ++2.19 v_capacity(A_B_2025_E_TRANS_2015) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +-1.971 v_capacity(A_2025_NGCC_2020) +-0.25 v_capacity(A_2025_HEAT_SYS_2020) +-2.19 v_capacity(B_A_2025_E_TRANS_2015) ++2.19 v_capacity(A_C_2025_E_TRANS_2015) +-1.971 v_capacity(A_2025_NGCC_2025) +-0.25 v_capacity(A_2025_HEAT_SYS_2025) +-0.20000000000000007 v_flow_in(A_2025_winter_night_ELC_E_BATT_2025_ELC) +-0.20000000000000007 v_flow_in(A_2025_winter_night_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_operating_reserve_margin_constraint(A_2020_winter_day_elec_A)_: ++1.1 v_flow_out(B_A_2020_winter_day_ELC_E_TRANS_2015_ELC) ++0.20000000000000007 v_flow_out(A_2020_winter_day_ELC_E_BATT_2020_ELC) +-1.1578947368421053 v_flow_out(A_C_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_winter_day_NG_NGCC_2015_ELC) ++1.1 v_flow_out(A_2020_winter_day_ethos_SOLPV_2015_ELC) ++1.1 v_flow_out(C_A_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_winter_day_NG_NGCC_2020_ELC) +-1.1578947368421053 v_flow_out(A_B_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_winter_day_ethos_SOLPV_2020_ELC) +-0.25 v_capacity(A_2020_HEAT_SYS_2020) +-1.971 v_capacity(A_2020_NGCC_2020) +-0.32849999999999996 v_capacity(A_2020_SOLPV_2015) ++2.19 v_capacity(A_B_2020_E_TRANS_2015) ++2.19 v_capacity(A_C_2020_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2020_SOLPV_2020) +-1.971 v_capacity(A_2020_NGCC_2015) +-2.19 v_capacity(B_A_2020_E_TRANS_2015) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +-0.20000000000000007 v_flow_in(A_2020_winter_day_ELC_E_BATT_2020_ELC) +<= -1.375 + +c_u_operating_reserve_margin_constraint(B_2025_summer_night_NGCC)_: ++1.1 v_flow_out(B_2025_summer_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(B_2025_summer_night_NG_NGCC_2020_ELC) ++1.1 v_flow_out(B_2025_summer_night_NG_NGCC_2025_ELC) +-2.0805 v_capacity(B_2025_NGCC_2025) +-2.0805 v_capacity(B_2025_NGCC_2015) +-2.0805 v_capacity(B_2025_NGCC_2020) +<= 0 + +c_u_operating_reserve_margin_constraint(A_2020_summer_night_elec_A)_: ++0.20000000000000007 v_flow_out(A_2020_summer_night_ELC_E_BATT_2020_ELC) ++1.1 v_flow_out(C_A_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_summer_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(A_2020_summer_night_ethos_SOLPV_2015_ELC) ++1.1 v_flow_out(A_2020_summer_night_NG_NGCC_2020_ELC) +-1.1578947368421053 v_flow_out(A_C_2020_summer_night_ELC_E_TRANS_2015_ELC) +-1.1578947368421053 v_flow_out(A_B_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_summer_night_ethos_SOLPV_2020_ELC) ++1.1 v_flow_out(B_A_2020_summer_night_ELC_E_TRANS_2015_ELC) +-0.25 v_capacity(A_2020_HEAT_SYS_2020) +-2.0805 v_capacity(A_2020_NGCC_2020) ++2.19 v_capacity(A_B_2020_E_TRANS_2015) ++2.19 v_capacity(A_C_2020_E_TRANS_2015) +-2.0805 v_capacity(A_2020_NGCC_2015) +-2.19 v_capacity(B_A_2020_E_TRANS_2015) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +-0.20000000000000007 v_flow_in(A_2020_summer_night_ELC_E_BATT_2020_ELC) +<= -1.375 + +c_u_operating_reserve_margin_constraint(A_B_2020_summer_day_elec_AB)_: +-1.1368421052631579 v_flow_out(A_C_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2020_summer_day_NG_NGCC_2020_ELC) ++1.08 v_flow_out(A_2020_summer_day_ethos_SOLPV_2020_ELC) ++1.08 v_flow_out(B_2020_summer_day_NG_NGCC_2020_ELC) ++0.18000000000000005 v_flow_out(A_2020_summer_day_ELC_E_BATT_2020_ELC) ++1.08 v_flow_out(C_B_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2020_summer_day_NG_NGCC_2015_ELC) ++1.08 v_flow_out(B_2020_summer_day_NG_NGCC_2015_ELC) +-1.1368421052631579 v_flow_out(B_C_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(C_A_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1.08 v_flow_out(A_2020_summer_day_ethos_SOLPV_2015_ELC) +-0.25 v_capacity(A_2020_HEAT_SYS_2020) +-2.0805 v_capacity(A_2020_NGCC_2020) ++2.19 v_capacity(B_C_2020_E_TRANS_2015) +-1.0512 v_capacity(A_2020_SOLPV_2015) +-2.0805 v_capacity(B_2020_NGCC_2015) ++2.19 v_capacity(A_C_2020_E_TRANS_2015) +-0.25 v_capacity(B_2020_HEAT_SYS_2020) +-1.0512 v_capacity(A_2020_SOLPV_2020) +-2.0805 v_capacity(A_2020_NGCC_2015) +-2.0805 v_capacity(B_2020_NGCC_2020) +-2.19 v_capacity(C_B_2020_E_TRANS_2015) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +-0.18000000000000005 v_flow_in(A_2020_summer_day_ELC_E_BATT_2020_ELC) +<= -2.16 + +c_u_planning_reserve_margin_constraint(A_B_2025_summer_night_elec_AB)_: ++1.1 v_flow_out(A_2025_summer_night_ethos_SOLPV_2015_ELC) ++1.1 v_flow_out(B_2025_summer_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(C_A_2025_summer_night_ELC_E_TRANS_2015_ELC) +-1.1578947368421053 v_flow_out(B_C_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) ++1.1 v_flow_out(B_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) +-1.1578947368421053 v_flow_out(A_C_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_summer_night_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2025_summer_night_ethos_SOLPV_2020_ELC) ++1.1 v_flow_out(B_2025_summer_night_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2025_summer_night_ELC_E_BATT_2020_ELC) ++1.1 v_flow_out(B_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) ++1.1 v_flow_out(A_2025_summer_night_ELC_E_BATT_2025_ELC) ++1.1 v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) ++1.1 v_flow_out(C_B_2025_summer_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_summer_night_NG_NGCC_2025_ELC) ++1.1 v_flow_out(A_2025_summer_night_ethos_SOLPV_2025_ELC) ++1.1 v_flow_out(A_2025_summer_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(B_2025_summer_night_NG_NGCC_2025_ELC) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2015) +-1.971 v_capacity(A_2025_E_BATT_2020) +-2.19 v_capacity(A_2025_NGCC_2015) +-2.19 v_capacity(B_2025_NGCC_2025) +-2.19 v_capacity(C_B_2025_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2020) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +-2.19 v_capacity(A_2025_NGCC_2020) +-1.971 v_capacity(A_2025_E_BATT_2025) +-2.19 v_capacity(B_2025_NGCC_2015) ++2.19 v_capacity(A_C_2025_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2025) ++2.19 v_capacity(B_C_2025_E_TRANS_2015) +-2.19 v_capacity(A_2025_NGCC_2025) +-2.19 v_capacity(B_2025_NGCC_2020) +-1.1 v_flow_in(A_2025_summer_night_ELC_E_BATT_2020_ELC) +-1.1 v_flow_in(A_2025_summer_night_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_planning_reserve_margin_constraint(A_2020_summer_day_NGCC)_: ++1.15 v_flow_out(A_2020_summer_day_NG_NGCC_2020_ELC) ++1.15 v_flow_out(A_2020_summer_day_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2020_NGCC_2020) +-2.19 v_capacity(A_2020_NGCC_2015) +<= 0 + +c_u_planning_reserve_margin_constraint(C_2020_summer_day_elec_C_ann)_: +-0.25 v_capacity(C_2020_ANN_IMP_2020) ++0.3 v_flow_out_annual(C_2020_ethos_ANN_IMP_2020_ELC_C) +<= -0.3 + +c_u_planning_reserve_margin_constraint(A_B_2025_winter_day_elec_AB)_: ++1.1 v_flow_out(A_2025_winter_day_ELC_E_BATT_2020_ELC) ++1.1 v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) ++1.1 v_flow_out(B_2025_winter_day_NG_NGCC_2025_ELC) +-1.1578947368421053 v_flow_out(A_C_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(C_A_2025_winter_day_ELC_E_TRANS_2015_ELC) +-1.1578947368421053 v_flow_out(B_C_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_winter_day_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2025_winter_day_ethos_SOLPV_2015_ELC) ++1.1 v_flow_out(C_B_2025_winter_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(B_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) ++1.1 v_flow_out(A_2025_winter_day_ELC_E_BATT_2025_ELC) ++1.1 v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) ++1.1 v_flow_out(B_2025_winter_day_NG_NGCC_2015_ELC) ++1.1 v_flow_out(A_2025_winter_day_ethos_SOLPV_2020_ELC) ++1.1 v_flow_out(A_2025_winter_day_NG_NGCC_2025_ELC) ++1.1 v_flow_out(A_2025_winter_day_ethos_SOLPV_2025_ELC) ++1.1 v_flow_out(B_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) ++1.1 v_flow_out(A_2025_winter_day_NG_NGCC_2015_ELC) ++1.1 v_flow_out(B_2025_winter_day_NG_NGCC_2020_ELC) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2015) +-1.971 v_capacity(A_2025_E_BATT_2020) +-2.19 v_capacity(A_2025_NGCC_2015) +-2.19 v_capacity(B_2025_NGCC_2025) +-2.19 v_capacity(C_B_2025_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2020) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +-2.19 v_capacity(A_2025_NGCC_2020) +-1.971 v_capacity(A_2025_E_BATT_2025) +-2.19 v_capacity(B_2025_NGCC_2015) ++2.19 v_capacity(A_C_2025_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2025) ++2.19 v_capacity(B_C_2025_E_TRANS_2015) +-2.19 v_capacity(A_2025_NGCC_2025) +-2.19 v_capacity(B_2025_NGCC_2020) +-1.1 v_flow_in(A_2025_winter_day_ELC_E_BATT_2020_ELC) +-1.1 v_flow_in(A_2025_winter_day_ELC_E_BATT_2025_ELC) +<= 0 + +c_u_planning_reserve_margin_constraint(A_2025_winter_night_NGCC)_: ++1.15 v_flow_out(A_2025_winter_night_NG_NGCC_2015_ELC) ++1.15 v_flow_out(A_2025_winter_night_NG_NGCC_2020_ELC) ++1.15 v_flow_out(A_2025_winter_night_NG_NGCC_2025_ELC) +-2.19 v_capacity(A_2025_NGCC_2015) +-2.19 v_capacity(A_2025_NGCC_2020) +-2.19 v_capacity(A_2025_NGCC_2025) +<= 0 + +c_u_planning_reserve_margin_constraint(C_2025_winter_day_elec_C_ann)_: +-0.25 v_capacity(C_2025_ANN_IMP_2020) ++0.3 v_flow_out_annual(C_2025_ethos_ANN_IMP_2020_ELC_C) +<= -0.33 + +c_u_planning_reserve_margin_constraint(A_2020_summer_night_NGCC)_: ++1.15 v_flow_out(A_2020_summer_night_NG_NGCC_2015_ELC) ++1.15 v_flow_out(A_2020_summer_night_NG_NGCC_2020_ELC) +-2.19 v_capacity(A_2020_NGCC_2020) +-2.19 v_capacity(A_2020_NGCC_2015) +<= 0 + +c_u_planning_reserve_margin_constraint(A_B_2020_winter_day_elec_AB)_: ++1.1 v_flow_out(A_2020_winter_day_ELC_E_BATT_2020_ELC) ++1.1 v_flow_out(B_2020_winter_day_NG_NGCC_2020_ELC) +-1.1578947368421053 v_flow_out(A_C_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_winter_day_NG_NGCC_2015_ELC) ++1.1 v_flow_out(A_2020_winter_day_ethos_SOLPV_2015_ELC) ++1.1 v_flow_out(C_A_2020_winter_day_ELC_E_TRANS_2015_ELC) +-1.1578947368421053 v_flow_out(B_C_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(B_2020_winter_day_NG_NGCC_2015_ELC) ++1.1 v_flow_out(C_B_2020_winter_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_winter_day_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2020_winter_day_ethos_SOLPV_2020_ELC) +-2.19 v_capacity(A_2020_NGCC_2020) ++2.19 v_capacity(B_C_2020_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2020_SOLPV_2015) +-2.19 v_capacity(B_2020_NGCC_2015) ++2.19 v_capacity(A_C_2020_E_TRANS_2015) +-1.971 v_capacity(A_2020_E_BATT_2020) +-0.32849999999999996 v_capacity(A_2020_SOLPV_2020) +-2.19 v_capacity(A_2020_NGCC_2015) +-2.19 v_capacity(B_2020_NGCC_2020) +-2.19 v_capacity(C_B_2020_E_TRANS_2015) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +-1.1 v_flow_in(A_2020_winter_day_ELC_E_BATT_2020_ELC) +<= -2.2 + +c_u_planning_reserve_margin_constraint(A_2020_winter_day_NGCC)_: ++1.15 v_flow_out(A_2020_winter_day_NG_NGCC_2015_ELC) ++1.15 v_flow_out(A_2020_winter_day_NG_NGCC_2020_ELC) +-2.19 v_capacity(A_2020_NGCC_2020) +-2.19 v_capacity(A_2020_NGCC_2015) +<= 0 + +c_u_planning_reserve_margin_constraint(C_2020_winter_night_elec_C_ann)_: +-0.25 v_capacity(C_2020_ANN_IMP_2020) ++0.3 v_flow_out_annual(C_2020_ethos_ANN_IMP_2020_ELC_C) +<= -0.3 + +c_u_planning_reserve_margin_constraint(A_B_2025_summer_day_elec_AB)_: ++1.1 v_flow_out(A_2025_summer_day_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2025_summer_day_ethos_SOLPV_2020_ELC) ++1.1 v_flow_out(B_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) ++1.1 v_flow_out(A_2025_summer_day_ELC_E_BATT_2025_ELC) ++1.1 v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) ++1.1 v_flow_out(B_2025_summer_day_NG_NGCC_2020_ELC) +-1.1578947368421053 v_flow_out(B_C_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_summer_day_NG_NGCC_2025_ELC) ++1.1 v_flow_out(A_2025_summer_day_ethos_SOLPV_2025_ELC) ++1.1 v_flow_out(C_B_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(C_A_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(B_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) ++1.1 v_flow_out(A_2025_summer_day_NG_NGCC_2015_ELC) ++1.1 v_flow_out(A_2025_summer_day_ethos_SOLPV_2015_ELC) ++1.1 v_flow_out(B_2025_summer_day_NG_NGCC_2025_ELC) ++1.1 v_flow_out(A_2025_summer_day_ELC_E_BATT_2020_ELC) ++1.1 v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) +-1.1578947368421053 v_flow_out(A_C_2025_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(B_2025_summer_day_NG_NGCC_2015_ELC) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2015) +-1.971 v_capacity(A_2025_E_BATT_2020) +-2.19 v_capacity(A_2025_NGCC_2015) +-2.19 v_capacity(B_2025_NGCC_2025) +-2.19 v_capacity(C_B_2025_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2020) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +-2.19 v_capacity(A_2025_NGCC_2020) +-1.971 v_capacity(A_2025_E_BATT_2025) +-2.19 v_capacity(B_2025_NGCC_2015) ++2.19 v_capacity(A_C_2025_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2025) ++2.19 v_capacity(B_C_2025_E_TRANS_2015) +-2.19 v_capacity(A_2025_NGCC_2025) +-2.19 v_capacity(B_2025_NGCC_2020) +-1.1 v_flow_in(A_2025_summer_day_ELC_E_BATT_2025_ELC) +-1.1 v_flow_in(A_2025_summer_day_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_planning_reserve_margin_constraint(A_B_2020_summer_night_elec_AB)_: ++1.1 v_flow_out(A_2020_summer_night_ELC_E_BATT_2020_ELC) ++1.1 v_flow_out(C_A_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(B_2020_summer_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(C_B_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_summer_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(A_2020_summer_night_ethos_SOLPV_2015_ELC) ++1.1 v_flow_out(A_2020_summer_night_NG_NGCC_2020_ELC) ++1.1 v_flow_out(B_2020_summer_night_NG_NGCC_2020_ELC) +-1.1578947368421053 v_flow_out(A_C_2020_summer_night_ELC_E_TRANS_2015_ELC) +-1.1578947368421053 v_flow_out(B_C_2020_summer_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_summer_night_ethos_SOLPV_2020_ELC) +-2.19 v_capacity(A_2020_NGCC_2020) ++2.19 v_capacity(B_C_2020_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2020_SOLPV_2015) +-2.19 v_capacity(B_2020_NGCC_2015) ++2.19 v_capacity(A_C_2020_E_TRANS_2015) +-1.971 v_capacity(A_2020_E_BATT_2020) +-0.32849999999999996 v_capacity(A_2020_SOLPV_2020) +-2.19 v_capacity(A_2020_NGCC_2015) +-2.19 v_capacity(B_2020_NGCC_2020) +-2.19 v_capacity(C_B_2020_E_TRANS_2015) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +-1.1 v_flow_in(A_2020_summer_night_ELC_E_BATT_2020_ELC) +<= -2.2 + +c_u_planning_reserve_margin_constraint(C_2020_summer_night_elec_C_ann)_: +-0.25 v_capacity(C_2020_ANN_IMP_2020) ++0.3 v_flow_out_annual(C_2020_ethos_ANN_IMP_2020_ELC_C) +<= -0.3 + +c_u_planning_reserve_margin_constraint(A_B_2020_winter_night_elec_AB)_: ++1.1 v_flow_out(B_2020_winter_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(C_B_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_winter_night_NG_NGCC_2020_ELC) +-1.1578947368421053 v_flow_out(B_C_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_winter_night_ethos_SOLPV_2015_ELC) ++1.1 v_flow_out(A_2020_winter_night_ELC_E_BATT_2020_ELC) +-1.1578947368421053 v_flow_out(A_C_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(B_2020_winter_night_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2020_winter_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(C_A_2020_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_winter_night_ethos_SOLPV_2020_ELC) +-2.19 v_capacity(A_2020_NGCC_2020) ++2.19 v_capacity(B_C_2020_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2020_SOLPV_2015) +-2.19 v_capacity(B_2020_NGCC_2015) ++2.19 v_capacity(A_C_2020_E_TRANS_2015) +-1.971 v_capacity(A_2020_E_BATT_2020) +-0.32849999999999996 v_capacity(A_2020_SOLPV_2020) +-2.19 v_capacity(A_2020_NGCC_2015) +-2.19 v_capacity(B_2020_NGCC_2020) +-2.19 v_capacity(C_B_2020_E_TRANS_2015) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +-1.1 v_flow_in(A_2020_winter_night_ELC_E_BATT_2020_ELC) +<= -2.2 + +c_u_planning_reserve_margin_constraint(C_2025_summer_day_elec_C_ann)_: +-0.25 v_capacity(C_2025_ANN_IMP_2020) ++0.3 v_flow_out_annual(C_2025_ethos_ANN_IMP_2020_ELC_C) +<= -0.33 + +c_u_planning_reserve_margin_constraint(A_B_2025_winter_night_elec_AB)_: ++1.1 v_flow_out(A_2025_winter_night_NG_NGCC_2015_ELC) +-1.1578947368421053 v_flow_out(B_C_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(C_A_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(B_2025_winter_night_NG_NGCC_2015_ELC) ++1.1 v_flow_out(A_2025_winter_night_ethos_SOLPV_2015_ELC) ++1.1 v_flow_out(C_B_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2025_winter_night_ELC_E_BATT_2020_ELC) ++1.1 v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) +-1.1578947368421053 v_flow_out(A_C_2025_winter_night_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(B_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) ++1.1 v_flow_out(A_2025_winter_night_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2025_winter_night_ethos_SOLPV_2020_ELC) ++1.1 v_flow_out(A_2025_winter_night_NG_NGCC_2025_ELC) ++1.1 v_flow_out(B_2025_winter_night_NG_NGCC_2020_ELC) ++1.1 v_flow_out(B_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) ++1.1 v_flow_out(A_2025_winter_night_ELC_E_BATT_2025_ELC) ++1.1 v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) ++1.1 v_flow_out(B_2025_winter_night_NG_NGCC_2025_ELC) ++1.1 v_flow_out(A_2025_winter_night_ethos_SOLPV_2025_ELC) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2015) +-1.971 v_capacity(A_2025_E_BATT_2020) +-2.19 v_capacity(A_2025_NGCC_2015) +-2.19 v_capacity(B_2025_NGCC_2025) +-2.19 v_capacity(C_B_2025_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2020) +-2.19 v_capacity(C_A_2025_E_TRANS_2015) +-2.19 v_capacity(A_2025_NGCC_2020) +-1.971 v_capacity(A_2025_E_BATT_2025) +-2.19 v_capacity(B_2025_NGCC_2015) ++2.19 v_capacity(A_C_2025_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2025_SOLPV_2025) ++2.19 v_capacity(B_C_2025_E_TRANS_2015) +-2.19 v_capacity(A_2025_NGCC_2025) +-2.19 v_capacity(B_2025_NGCC_2020) +-1.1 v_flow_in(A_2025_winter_night_ELC_E_BATT_2025_ELC) +-1.1 v_flow_in(A_2025_winter_night_ELC_E_BATT_2020_ELC) +<= 0 + +c_u_planning_reserve_margin_constraint(A_2025_summer_day_NGCC)_: ++1.15 v_flow_out(A_2025_summer_day_NG_NGCC_2020_ELC) ++1.15 v_flow_out(A_2025_summer_day_NG_NGCC_2025_ELC) ++1.15 v_flow_out(A_2025_summer_day_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2025_NGCC_2015) +-2.19 v_capacity(A_2025_NGCC_2020) +-2.19 v_capacity(A_2025_NGCC_2025) +<= 0 + +c_u_planning_reserve_margin_constraint(A_2020_winter_night_NGCC)_: ++1.15 v_flow_out(A_2020_winter_night_NG_NGCC_2020_ELC) ++1.15 v_flow_out(A_2020_winter_night_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2020_NGCC_2020) +-2.19 v_capacity(A_2020_NGCC_2015) +<= 0 + +c_u_planning_reserve_margin_constraint(A_2025_summer_night_NGCC)_: ++1.15 v_flow_out(A_2025_summer_night_NG_NGCC_2020_ELC) ++1.15 v_flow_out(A_2025_summer_night_NG_NGCC_2025_ELC) ++1.15 v_flow_out(A_2025_summer_night_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2025_NGCC_2015) +-2.19 v_capacity(A_2025_NGCC_2020) +-2.19 v_capacity(A_2025_NGCC_2025) +<= 0 + +c_u_planning_reserve_margin_constraint(A_2025_winter_day_NGCC)_: ++1.15 v_flow_out(A_2025_winter_day_NG_NGCC_2020_ELC) ++1.15 v_flow_out(A_2025_winter_day_NG_NGCC_2025_ELC) ++1.15 v_flow_out(A_2025_winter_day_NG_NGCC_2015_ELC) +-2.19 v_capacity(A_2025_NGCC_2015) +-2.19 v_capacity(A_2025_NGCC_2020) +-2.19 v_capacity(A_2025_NGCC_2025) +<= 0 + +c_u_planning_reserve_margin_constraint(C_2025_winter_night_elec_C_ann)_: +-0.25 v_capacity(C_2025_ANN_IMP_2020) ++0.3 v_flow_out_annual(C_2025_ethos_ANN_IMP_2020_ELC_C) +<= -0.33 + +c_u_planning_reserve_margin_constraint(C_2020_winter_day_elec_C_ann)_: +-0.25 v_capacity(C_2020_ANN_IMP_2020) ++0.3 v_flow_out_annual(C_2020_ethos_ANN_IMP_2020_ELC_C) +<= -0.3 + +c_u_planning_reserve_margin_constraint(C_2025_summer_night_elec_C_ann)_: +-0.25 v_capacity(C_2025_ANN_IMP_2020) ++0.3 v_flow_out_annual(C_2025_ethos_ANN_IMP_2020_ELC_C) +<= -0.33 + +c_u_planning_reserve_margin_constraint(A_B_2020_summer_day_elec_AB)_: +-1.1578947368421053 v_flow_out(A_C_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_summer_day_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2020_summer_day_ethos_SOLPV_2020_ELC) ++1.1 v_flow_out(B_2020_summer_day_NG_NGCC_2020_ELC) ++1.1 v_flow_out(A_2020_summer_day_ELC_E_BATT_2020_ELC) ++1.1 v_flow_out(C_B_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_summer_day_NG_NGCC_2015_ELC) ++1.1 v_flow_out(B_2020_summer_day_NG_NGCC_2015_ELC) +-1.1578947368421053 v_flow_out(B_C_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(C_A_2020_summer_day_ELC_E_TRANS_2015_ELC) ++1.1 v_flow_out(A_2020_summer_day_ethos_SOLPV_2015_ELC) +-2.19 v_capacity(A_2020_NGCC_2020) ++2.19 v_capacity(B_C_2020_E_TRANS_2015) +-0.32849999999999996 v_capacity(A_2020_SOLPV_2015) +-2.19 v_capacity(B_2020_NGCC_2015) ++2.19 v_capacity(A_C_2020_E_TRANS_2015) +-1.971 v_capacity(A_2020_E_BATT_2020) +-0.32849999999999996 v_capacity(A_2020_SOLPV_2020) +-2.19 v_capacity(A_2020_NGCC_2015) +-2.19 v_capacity(B_2020_NGCC_2020) +-2.19 v_capacity(C_B_2020_E_TRANS_2015) +-2.19 v_capacity(C_A_2020_E_TRANS_2015) +-1.1 v_flow_in(A_2020_summer_day_ELC_E_BATT_2020_ELC) +<= -2.2 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= v_flow_out(B_A_2025_winter_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_B_2025_summer_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_night_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_day_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_night_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_day_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_out(B_C_2025_winter_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2020_summer_night_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_out(B_A_2020_winter_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_C_2020_summer_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_day_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_flow_out(B_2020_winter_night_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) <= +inf + 0 <= v_flow_out(B_A_2025_summer_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(C_A_2025_winter_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_winter_day_NG_NGCC_2025_ELC) <= +inf + 0 <= v_flow_out(B_2025_summer_night_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) <= +inf + 0 <= v_flow_out(A_C_2025_winter_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(C_B_2020_winter_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2020_summer_day_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(C_A_2025_summer_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_winter_night_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(C_A_2020_summer_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_A_2020_winter_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2020_winter_night_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(A_2020_winter_day_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_out(A_2020_summer_day_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_flow_out(B_2025_winter_night_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(B_2020_summer_day_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(B_2025_winter_day_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(B_C_2020_winter_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2020_summer_day_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_out(B_C_2025_summer_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_B_2025_winter_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2020_winter_day_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_day_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(A_2025_winter_night_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_flow_out(C_B_2025_winter_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2020_summer_night_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(C_A_2025_winter_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_C_2025_winter_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_B_2025_winter_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_night_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) <= +inf + 0 <= v_flow_out(A_2025_summer_day_ELC_E_BATT_2025_ELC) <= +inf + 0 <= v_flow_out(B_2020_winter_day_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(B_2025_summer_night_ELC_HEAT_SYS_2020_HEAT) <= +inf + 0 <= v_flow_out(A_C_2025_summer_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) <= +inf + 0 <= v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) <= +inf + 0 <= v_flow_out(A_2025_winter_day_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(B_A_2025_summer_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(C_B_2020_summer_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_summer_day_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(A_2020_summer_night_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_summer_day_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(A_2020_summer_day_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(A_C_2025_winter_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_night_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(A_2020_winter_night_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_flow_out(A_2020_summer_night_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_flow_out(A_2020_winter_night_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_out(B_C_2025_summer_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_winter_night_ELC_HEAT_SYS_2020_HEAT) <= +inf + 0 <= v_flow_out(A_2025_winter_night_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(A_C_2020_winter_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_day_NG_NGCC_2025_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_day_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_flow_out(C_B_2025_winter_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_winter_day_ELC_HEAT_SYS_2020_HEAT) <= +inf + 0 <= v_flow_out(A_C_2020_winter_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_night_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_day_ELC_E_BATT_2025_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_night_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_flow_out(C_B_2020_summer_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_A_2025_winter_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2020_winter_day_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_day_ethos_SOLPV_2025_ELC) <= +inf + 0 <= v_flow_out(B_2020_winter_night_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(C_B_2025_summer_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) <= +inf + 0 <= v_flow_out(A_2020_summer_night_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(B_2025_winter_day_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(B_2020_winter_night_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(C_A_2025_summer_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_summer_night_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(B_2025_summer_day_ELC_HEAT_SYS_2025_HEAT) <= +inf + 0 <= v_flow_out(A_2020_winter_night_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(A_2020_winter_day_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_day_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_night_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_night_NG_NGCC_2025_ELC) <= +inf + 0 <= v_flow_out(C_A_2020_winter_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_day_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_winter_night_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(B_2020_summer_day_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(A_B_2020_winter_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) <= +inf + 0 <= v_flow_out(A_2025_winter_day_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_flow_out(B_2020_summer_night_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(A_C_2020_summer_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_night_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(A_2020_summer_day_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_night_ELC_E_BATT_2025_ELC) <= +inf + 0 <= v_flow_out(B_A_2020_summer_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) <= +inf + 0 <= v_flow_out(A_2020_winter_night_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_night_ELC_E_BATT_2025_ELC) <= +inf + 0 <= v_flow_out(B_C_2020_winter_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2020_summer_night_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(A_2025_winter_night_ELC_HEAT_SYS_2025_HEAT) <= +inf + 0 <= v_flow_out(A_2025_winter_day_NG_NGCC_2025_ELC) <= +inf + 0 <= v_flow_out(A_2020_summer_night_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(B_2020_summer_day_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_night_ELC_HEAT_SYS_2025_HEAT) <= +inf + 0 <= v_flow_out(B_2025_winter_night_NG_NGCC_2025_ELC) <= +inf + 0 <= v_flow_out(B_C_2020_summer_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(C_B_2025_summer_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_summer_day_NG_NGCC_2025_ELC) <= +inf + 0 <= v_flow_out(B_C_2020_summer_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_B_2025_summer_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_day_ethos_SOLPV_2025_ELC) <= +inf + 0 <= v_flow_out(B_2020_winter_day_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(A_B_2020_summer_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(C_A_2020_winter_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_day_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(A_2020_winter_night_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_flow_out(B_2025_summer_night_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(A_2020_summer_night_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_day_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_out(A_B_2020_summer_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(C_A_2020_summer_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_winter_day_ELC_HEAT_SYS_2025_HEAT) <= +inf + 0 <= v_flow_out(A_2025_summer_day_ELC_HEAT_SYS_2020_HEAT) <= +inf + 0 <= v_flow_out(A_2025_summer_night_NG_NGCC_2025_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_day_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_summer_night_ethos_SOLPV_2025_ELC) <= +inf + 0 <= v_flow_out(A_C_2025_summer_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(C_B_2020_winter_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_summer_day_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_night_ethos_SOLPV_2025_ELC) <= +inf + 0 <= v_flow_out(A_2020_winter_day_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(B_2025_winter_day_NG_NGCC_2020_ELC) <= +inf + 0 <= v_flow_out(A_2025_winter_night_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(B_A_2020_summer_night_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_B_2020_winter_day_ELC_E_TRANS_2015_ELC) <= +inf + 0 <= v_flow_out(A_2020_winter_day_ethos_IMP_NG_2020_NG) <= +inf + 0 <= v_flow_out(A_2025_summer_night_NG_NGCC_2015_ELC) <= +inf + 0 <= v_flow_out(B_2025_summer_night_NG_NGCC_2025_ELC) <= +inf + 0 <= v_flow_out(A_2020_summer_day_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_flow_out(A_2020_winter_day_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_capacity(C_2025_HEAT_ANN_2020) <= +inf + 0 <= v_capacity(A_2020_HEAT_SYS_2020) <= +inf + 0 <= v_capacity(A_2025_SOLPV_2015) <= +inf + 0 <= v_capacity(A_2025_E_BATT_2020) <= +inf + 0 <= v_capacity(A_2025_NGCC_2015) <= +inf + 0 <= v_capacity(A_B_2025_E_TRANS_2015) <= +inf + 0 <= v_capacity(A_2020_NGCC_2020) <= +inf + 0 <= v_capacity(B_2025_NGCC_2025) <= +inf + 0 <= v_capacity(B_C_2020_E_TRANS_2015) <= +inf + 0 <= v_capacity(C_B_2025_E_TRANS_2015) <= +inf + 0 <= v_capacity(A_2025_SOLPV_2020) <= +inf + 0 <= v_capacity(C_A_2025_E_TRANS_2015) <= +inf + 0 <= v_capacity(A_2025_NGCC_2020) <= +inf + 0 <= v_capacity(A_2025_E_BATT_2025) <= +inf + 0 <= v_capacity(A_2020_SOLPV_2015) <= +inf + 0 <= v_capacity(B_2020_NGCC_2015) <= +inf + 0 <= v_capacity(A_2025_HEAT_SYS_2020) <= +inf + 0 <= v_capacity(B_2025_NGCC_2015) <= +inf + 0 <= v_capacity(B_2025_HEAT_SYS_2020) <= +inf + 0 <= v_capacity(B_A_2025_E_TRANS_2015) <= +inf + 0 <= v_capacity(C_2020_ANN_IMP_2020) <= +inf + 0 <= v_capacity(C_2020_HEAT_ANN_2020) <= +inf + 0 <= v_capacity(A_B_2020_E_TRANS_2015) <= +inf + 0 <= v_capacity(A_C_2020_E_TRANS_2015) <= +inf + 0 <= v_capacity(A_2020_E_BATT_2020) <= +inf + 0 <= v_capacity(A_C_2025_E_TRANS_2015) <= +inf + 0 <= v_capacity(B_2020_HEAT_SYS_2020) <= +inf + 0 <= v_capacity(A_2025_SOLPV_2025) <= +inf + 0 <= v_capacity(B_C_2025_E_TRANS_2015) <= +inf + 0 <= v_capacity(A_2025_NGCC_2025) <= +inf + 0 <= v_capacity(A_2025_HEAT_SYS_2025) <= +inf + 0 <= v_capacity(A_2020_SOLPV_2020) <= +inf + 0 <= v_capacity(A_2020_NGCC_2015) <= +inf + 0 <= v_capacity(B_2020_NGCC_2020) <= +inf + 0 <= v_capacity(B_2025_NGCC_2020) <= +inf + 0 <= v_capacity(C_B_2020_E_TRANS_2015) <= +inf + 0 <= v_capacity(B_2025_HEAT_SYS_2025) <= +inf + 0 <= v_capacity(B_A_2020_E_TRANS_2015) <= +inf + 0 <= v_capacity(C_A_2020_E_TRANS_2015) <= +inf + 0 <= v_capacity(C_2025_ANN_IMP_2020) <= +inf + 0 <= v_curtailment(A_2025_summer_day_ethos_SOLPV_2025_ELC) <= +inf + 0 <= v_curtailment(A_2025_winter_night_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_curtailment(A_2025_summer_night_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_curtailment(A_2020_winter_day_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_curtailment(A_2025_summer_day_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_curtailment(A_2025_winter_day_ethos_SOLPV_2025_ELC) <= +inf + 0 <= v_curtailment(A_2020_winter_night_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_curtailment(A_2020_summer_night_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_curtailment(A_2025_summer_day_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_curtailment(A_2025_summer_night_ethos_SOLPV_2025_ELC) <= +inf + 0 <= v_curtailment(A_2020_winter_night_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_curtailment(A_2020_summer_night_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_curtailment(A_2025_winter_day_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_curtailment(A_2025_winter_night_ethos_SOLPV_2025_ELC) <= +inf + 0 <= v_curtailment(A_2025_winter_day_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_curtailment(A_2020_summer_day_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_curtailment(A_2025_summer_night_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_curtailment(A_2020_summer_day_ethos_SOLPV_2015_ELC) <= +inf + 0 <= v_curtailment(A_2020_winter_day_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_curtailment(A_2025_winter_night_ethos_SOLPV_2020_ELC) <= +inf + 0 <= v_flow_out_annual(A_2025_ELC_HEAT_SYS_2025_HEAT) <= +inf + 0 <= v_flow_out_annual(B_2025_ELC_HEAT_SYS_2020_HEAT) <= +inf + 0 <= v_flow_out_annual(A_2025_ELC_HEAT_SYS_2020_HEAT) <= +inf + 0 <= v_flow_out_annual(C_2025_ethos_ANN_IMP_2020_ELC_C) <= +inf + 0 <= v_flow_out_annual(B_2025_ELC_HEAT_SYS_2025_HEAT) <= +inf + 0 <= v_flow_out_annual(C_2020_ethos_ANN_IMP_2020_ELC_C) <= +inf + 0 <= v_capacity_available_by_period_and_tech(B_2025_NGCC) <= +inf + 0 <= v_capacity_available_by_period_and_tech(A_2020_E_BATT) <= +inf + 0 <= v_capacity_available_by_period_and_tech(B_2025_HEAT_SYS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(C_2025_HEAT_ANN) <= +inf + 0 <= v_capacity_available_by_period_and_tech(A_B_2025_E_TRANS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(B_C_2025_E_TRANS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(A_C_2020_E_TRANS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(C_2020_HEAT_ANN) <= +inf + 0 <= v_capacity_available_by_period_and_tech(C_B_2025_E_TRANS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(A_2025_SOLPV) <= +inf + 0 <= v_capacity_available_by_period_and_tech(B_2020_NGCC) <= +inf + 0 <= v_capacity_available_by_period_and_tech(A_B_2020_E_TRANS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(B_A_2025_E_TRANS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(B_C_2020_E_TRANS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(B_2020_HEAT_SYS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(A_2025_NGCC) <= +inf + 0 <= v_capacity_available_by_period_and_tech(C_2025_ANN_IMP) <= +inf + 0 <= v_capacity_available_by_period_and_tech(C_B_2020_E_TRANS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(A_2025_HEAT_SYS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(C_A_2025_E_TRANS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(A_2020_SOLPV) <= +inf + 0 <= v_capacity_available_by_period_and_tech(B_A_2020_E_TRANS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(A_2025_E_BATT) <= +inf + 0 <= v_capacity_available_by_period_and_tech(A_C_2025_E_TRANS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(A_2020_NGCC) <= +inf + 0 <= v_capacity_available_by_period_and_tech(C_2020_ANN_IMP) <= +inf + 0 <= v_capacity_available_by_period_and_tech(C_A_2020_E_TRANS) <= +inf + 0 <= v_capacity_available_by_period_and_tech(A_2020_HEAT_SYS) <= +inf + 0 <= v_new_capacity(A_SOLPV_2020) <= +inf + 0 <= v_new_capacity(B_NGCC_2020) <= +inf + 0 <= v_new_capacity(A_HEAT_SYS_2025) <= +inf + 0 <= v_new_capacity(A_NGCC_2020) <= +inf + 0 <= v_new_capacity(A_E_BATT_2020) <= +inf + 0 <= v_new_capacity(B_HEAT_SYS_2020) <= +inf + 0 <= v_new_capacity(A_SOLPV_2025) <= +inf + 0 <= v_new_capacity(B_NGCC_2025) <= +inf + 0 <= v_new_capacity(C_ANN_IMP_2020) <= +inf + 0 <= v_new_capacity(A_HEAT_SYS_2020) <= +inf + 0 <= v_new_capacity(A_NGCC_2025) <= +inf + 0 <= v_new_capacity(A_E_BATT_2025) <= +inf + 0 <= v_new_capacity(C_HEAT_ANN_2020) <= +inf + 0 <= v_new_capacity(B_HEAT_SYS_2025) <= +inf + 0 <= v_flow_in(A_2020_winter_day_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_in(A_2025_winter_day_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_in(A_2020_summer_night_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_in(A_2025_summer_night_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_in(A_2025_winter_night_ELC_E_BATT_2025_ELC) <= +inf + 0 <= v_flow_in(A_2025_winter_night_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_in(A_2025_winter_day_ELC_E_BATT_2025_ELC) <= +inf + 0 <= v_flow_in(A_2025_summer_day_ELC_E_BATT_2025_ELC) <= +inf + 0 <= v_flow_in(A_2025_summer_day_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_in(A_2020_winter_night_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_flow_in(A_2025_summer_night_ELC_E_BATT_2025_ELC) <= +inf + 0 <= v_flow_in(A_2020_summer_day_ELC_E_BATT_2020_ELC) <= +inf + 0 <= v_storage_init(A_2025_summer_E_BATT_2020) <= +inf + 0 <= v_storage_init(A_2025_winter_E_BATT_2020) <= +inf + 0 <= v_storage_init(A_2020_summer_E_BATT_2020) <= +inf + 0 <= v_storage_init(A_2020_winter_E_BATT_2020) <= +inf + 0 <= v_storage_init(A_2025_summer_E_BATT_2025) <= +inf + 0 <= v_storage_init(A_2025_winter_E_BATT_2025) <= +inf + 0 <= v_storage_level(A_2020_winter_night_E_BATT_2020) <= +inf + 0 <= v_storage_level(A_2020_summer_night_E_BATT_2020) <= +inf + 0 <= v_storage_level(A_2025_winter_day_E_BATT_2020) <= +inf + 0 <= v_storage_level(A_2025_summer_night_E_BATT_2025) <= +inf + 0 <= v_storage_level(A_2025_summer_day_E_BATT_2025) <= +inf + 0 <= v_storage_level(A_2025_winter_night_E_BATT_2020) <= +inf + 0 <= v_storage_level(A_2025_winter_day_E_BATT_2025) <= +inf + 0 <= v_storage_level(A_2020_winter_day_E_BATT_2020) <= +inf + 0 <= v_storage_level(A_2025_summer_night_E_BATT_2020) <= +inf + 0 <= v_storage_level(A_2025_summer_day_E_BATT_2020) <= +inf + 0 <= v_storage_level(A_2020_summer_day_E_BATT_2020) <= +inf + 0 <= v_storage_level(A_2025_winter_night_E_BATT_2025) <= +inf +end diff --git a/tests/testing_data/reserve_margins.sql b/tests/testing_data/reserve_margins.sql new file mode 100644 index 000000000..3e1a5606d --- /dev/null +++ b/tests/testing_data/reserve_margins.sql @@ -0,0 +1,162 @@ +REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',1,''); +REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,''); +REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,''); +REPLACE INTO "time_period" VALUES(0,2015,'e'); +REPLACE INTO "time_period" VALUES(1,2020,'f'); +REPLACE INTO "time_period" VALUES(2,2025,'f'); +REPLACE INTO "time_period" VALUES(3,2030,'f'); +REPLACE INTO "time_period_type" VALUES('e','existing'); +REPLACE INTO "time_period_type" VALUES('f','future'); +REPLACE INTO "time_season" VALUES(0,'summer',0.5,NULL); +REPLACE INTO "time_season" VALUES(1,'winter',0.5,NULL); +REPLACE INTO "time_of_day" VALUES(0,'day',12,NULL); +REPLACE INTO "time_of_day" VALUES(1,'night',12,NULL); +REPLACE INTO "region" VALUES('A',NULL); +REPLACE INTO "region" VALUES('B',NULL); +REPLACE INTO "region" VALUES('C',NULL); +REPLACE INTO "commodity_type" VALUES('s','source'); +REPLACE INTO "commodity_type" VALUES('a','annual'); +REPLACE INTO "commodity_type" VALUES('p','physical'); +REPLACE INTO "commodity_type" VALUES('d','demand'); +REPLACE INTO "commodity_type" VALUES('e','emissions'); +REPLACE INTO "commodity_type" VALUES('w','waste'); +REPLACE INTO "commodity_type" VALUES('wa','waste annual'); +REPLACE INTO "commodity_type" VALUES('wp','waste physical'); +REPLACE INTO "commodity" VALUES('ethos','s',NULL,NULL); +REPLACE INTO "commodity" VALUES('NG','p',NULL,NULL); +REPLACE INTO "commodity" VALUES('ELC','p',NULL,NULL); +REPLACE INTO "commodity" VALUES('ELC_C','a',NULL,NULL); -- annual electricity in C +REPLACE INTO "commodity" VALUES('HEAT','d',NULL,NULL); +REPLACE INTO "commodity" VALUES('HEAT_C','d',NULL,NULL); +REPLACE INTO "technology_type" VALUES('p','production'); +REPLACE INTO "technology_type" VALUES('pb','baseload production'); +REPLACE INTO "technology_type" VALUES('ps','storage production'); +REPLACE INTO "technology" VALUES('NGCC','p',NULL,NULL,NULL, 0,0,0,0,0,0,0, 'combined-cycle gas'); +REPLACE INTO "technology" VALUES('SOLPV','p',NULL,NULL,NULL, 0,0,1,0,0,0,0, 'solar photovoltaic'); +REPLACE INTO "technology" VALUES('E_BATT','ps',NULL,NULL,NULL,0,0,0,0,0,0,0, 'grid battery'); +REPLACE INTO "technology" VALUES('ANN_IMP','p',NULL,NULL,NULL,0,1,0,0,0,0,0, 'capacitated annual import'); +REPLACE INTO "technology" VALUES('HEAT_SYS','p',NULL,NULL,NULL,0,0,0,0,0,0,0,'electric heater'); +REPLACE INTO "technology" VALUES('HEAT_ANN','p',NULL,NULL,NULL,0,1,0,0,0,0,0,'annual electric heater'); +REPLACE INTO "technology" VALUES('IMP_NG','p',NULL,NULL,NULL, 1,0,0,0,0,0,0, 'unlimited NG import'); +REPLACE INTO "technology" VALUES('E_TRANS','p',NULL,NULL,NULL,0,0,0,0,0,1,0, 'interregional transmission'); +REPLACE INTO "tech_group" VALUES('elec_A', 'generators + storage + load in A'); +REPLACE INTO "tech_group" VALUES('elec_AB', 'generators + storage + exchange + load across A and B'); +REPLACE INTO "tech_group" VALUES('elec_C_ann', 'annual import + load in C'); +REPLACE INTO "tech_group_member" VALUES('elec_A', 'NGCC'); +REPLACE INTO "tech_group_member" VALUES('elec_A', 'SOLPV'); +REPLACE INTO "tech_group_member" VALUES('elec_A', 'E_BATT'); +REPLACE INTO "tech_group_member" VALUES('elec_A', 'HEAT_SYS'); +REPLACE INTO "tech_group_member" VALUES('elec_A', 'E_TRANS'); +REPLACE INTO "tech_group_member" VALUES('elec_AB', 'NGCC'); +REPLACE INTO "tech_group_member" VALUES('elec_AB', 'SOLPV'); +REPLACE INTO "tech_group_member" VALUES('elec_AB', 'E_BATT'); +REPLACE INTO "tech_group_member" VALUES('elec_AB', 'HEAT_SYS'); +REPLACE INTO "tech_group_member" VALUES('elec_AB', 'E_TRANS'); +REPLACE INTO "tech_group_member" VALUES('elec_C_ann', 'ANN_IMP'); +REPLACE INTO "tech_group_member" VALUES('elec_C_ann', 'HEAT_ANN'); +REPLACE INTO "efficiency" VALUES('A','ethos','IMP_NG', 2020,'NG', 1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('A','NG', 'NGCC', 2015,'ELC',0.55,NULL,NULL); +REPLACE INTO "efficiency" VALUES('A','NG', 'NGCC', 2020,'ELC',0.55,NULL,NULL); +REPLACE INTO "efficiency" VALUES('A','NG', 'NGCC', 2025,'ELC',0.55,NULL,NULL); +REPLACE INTO "efficiency" VALUES('A','ethos','SOLPV', 2015,'ELC',1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('A','ethos','SOLPV', 2020,'ELC',1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('A','ethos','SOLPV', 2025,'ELC',1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('A','ELC', 'E_BATT', 2020,'ELC',0.85,NULL,NULL); +REPLACE INTO "efficiency" VALUES('A','ELC', 'E_BATT', 2025,'ELC',0.85,NULL,NULL); +REPLACE INTO "efficiency" VALUES('A','ELC', 'HEAT_SYS',2020,'HEAT',1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('A','ELC', 'HEAT_SYS',2025,'HEAT',1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('B','ethos','IMP_NG', 2020,'NG', 1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('B','NG', 'NGCC', 2015,'ELC',0.55,NULL,NULL); +REPLACE INTO "efficiency" VALUES('B','NG', 'NGCC', 2020,'ELC',0.55,NULL,NULL); +REPLACE INTO "efficiency" VALUES('B','NG', 'NGCC', 2025,'ELC',0.55,NULL,NULL); +REPLACE INTO "efficiency" VALUES('B','ELC', 'HEAT_SYS',2020,'HEAT',1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('B','ELC', 'HEAT_SYS',2025,'HEAT',1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('C','ethos','ANN_IMP', 2020,'ELC_C',1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('C','ELC_C','HEAT_ANN', 2020,'HEAT_C',1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('A-B','ELC','E_TRANS',2015,'ELC',0.95,NULL,NULL); +REPLACE INTO "efficiency" VALUES('B-A','ELC','E_TRANS',2015,'ELC',0.95,NULL,NULL); +REPLACE INTO "efficiency" VALUES('A-C','ELC','E_TRANS',2015,'ELC',0.95,NULL,NULL); +REPLACE INTO "efficiency" VALUES('C-A','ELC','E_TRANS',2015,'ELC',0.95,NULL,NULL); +REPLACE INTO "efficiency" VALUES('B-C','ELC','E_TRANS',2015,'ELC',0.95,NULL,NULL); +REPLACE INTO "efficiency" VALUES('C-B','ELC','E_TRANS',2015,'ELC',0.95,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('A','IMP_NG', 1.0,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('A','NGCC', 8.76,NULL,NULL); -- GW → PJ/yr (8760 h) +REPLACE INTO "capacity_to_activity" VALUES('A','SOLPV', 8.76,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('A','E_BATT', 8.76,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('A','HEAT_SYS',1.0,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('B','IMP_NG', 1.0,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('B','NGCC', 8.76,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('B','HEAT_SYS',1.0,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('C','ANN_IMP',1.0,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('C','HEAT_ANN',1.0,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('A-B','E_TRANS',8.76,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('B-A','E_TRANS',8.76,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('A-C','E_TRANS',8.76,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('C-A','E_TRANS',8.76,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('B-C','E_TRANS',8.76,NULL,NULL); +REPLACE INTO "capacity_to_activity" VALUES('C-B','E_TRANS',8.76,NULL,NULL); +REPLACE INTO "storage_duration" VALUES('A','E_BATT',4.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('A','summer','day', 'SOLPV',0.6,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('A','summer','night','SOLPV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('A','winter','day', 'SOLPV',0.3,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('A','winter','night','SOLPV',0.0,NULL); +REPLACE INTO "existing_capacity" VALUES('A','NGCC', 2015,0.5,'GW',NULL); +REPLACE INTO "existing_capacity" VALUES('A','SOLPV', 2015,0.2,'GW',NULL); +REPLACE INTO "existing_capacity" VALUES('B','NGCC', 2015,0.3,'GW',NULL); +REPLACE INTO "existing_capacity" VALUES('A-B','E_TRANS',2015,1.0,'GW',NULL); +REPLACE INTO "existing_capacity" VALUES('B-A','E_TRANS',2015,1.0,'GW',NULL); +REPLACE INTO "existing_capacity" VALUES('A-C','E_TRANS',2015,1.0,'GW',NULL); +REPLACE INTO "existing_capacity" VALUES('C-A','E_TRANS',2015,1.0,'GW',NULL); +REPLACE INTO "existing_capacity" VALUES('B-C','E_TRANS',2015,1.0,'GW',NULL); +REPLACE INTO "existing_capacity" VALUES('C-B','E_TRANS',2015,1.0,'GW',NULL); +REPLACE INTO "lifetime_tech" VALUES('A','IMP_NG', 100.0,NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('A','NGCC', 30.0, NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('A','SOLPV', 25.0, NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('A','E_BATT', 15.0, NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('A','HEAT_SYS',20.0, NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('B','IMP_NG', 100.0,NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('B','NGCC', 30.0, NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('B','HEAT_SYS',20.0, NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('C','ANN_IMP', 100.0,NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('C','HEAT_ANN',20.0, NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('A-B','E_TRANS',50.0,NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('B-A','E_TRANS',50.0,NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('C-A','E_TRANS',50.0,NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('A-C','E_TRANS',50.0,NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('B-C','E_TRANS',50.0,NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('C-B','E_TRANS',50.0,NULL,NULL); +REPLACE INTO "demand" VALUES('A',2020,'HEAT',5.0,NULL,NULL); +REPLACE INTO "demand" VALUES('A',2025,'HEAT',5.5,NULL,NULL); +REPLACE INTO "demand" VALUES('B',2020,'HEAT',3.0,NULL,NULL); +REPLACE INTO "demand" VALUES('B',2025,'HEAT',3.3,NULL,NULL); +REPLACE INTO "demand" VALUES('C',2020,'HEAT_C',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('C',2025,'HEAT_C',1.1,NULL,NULL); +REPLACE INTO "planning_reserve_credit" VALUES('A', 'NGCC', 1.0, NULL); +REPLACE INTO "planning_reserve_credit" VALUES('A', 'SOLPV', 0.15,NULL); +REPLACE INTO "planning_reserve_credit" VALUES('A', 'E_BATT', 0.9, NULL); +REPLACE INTO "planning_reserve_credit" VALUES('A', 'HEAT_SYS',0.0,NULL); +REPLACE INTO "planning_reserve_credit" VALUES('B', 'NGCC', 1.0, NULL); +REPLACE INTO "planning_reserve_credit" VALUES('B', 'HEAT_SYS',0.0,NULL); +REPLACE INTO "planning_reserve_credit" VALUES('A-B', 'E_TRANS',1.0, NULL); +REPLACE INTO "planning_reserve_credit" VALUES('B-A', 'E_TRANS',1.0, NULL); +REPLACE INTO "planning_reserve_credit" VALUES('C-B', 'E_TRANS',1.0, NULL); +REPLACE INTO "planning_reserve_credit" VALUES('B-C', 'E_TRANS',1.0, NULL); +REPLACE INTO "planning_reserve_credit" VALUES('C-A', 'E_TRANS',1.0, NULL); +REPLACE INTO "planning_reserve_credit" VALUES('A-C', 'E_TRANS',1.0, NULL); +REPLACE INTO "planning_reserve_credit" VALUES('C', 'ANN_IMP',1.0, NULL); +REPLACE INTO "planning_reserve_credit" VALUES('C', 'HEAT_ANN',0.0,NULL); +REPLACE INTO "planning_reserve_margin" VALUES('A', 'NGCC', 0.15,NULL); +REPLACE INTO "planning_reserve_margin" VALUES('A+B', 'elec_AB', 0.10,NULL); +REPLACE INTO "planning_reserve_margin" VALUES('C', 'elec_C_ann',0.20,NULL); +REPLACE INTO "operating_reserve_derate" VALUES('A','summer','NGCC', 0.95,NULL); +REPLACE INTO "operating_reserve_derate" VALUES('A','winter','NGCC', 0.90,NULL); +REPLACE INTO "operating_reserve_derate" VALUES('A','summer','SOLPV', 0.80,NULL); +REPLACE INTO "operating_reserve_derate" VALUES('A','winter','SOLPV', 0.50,NULL); +REPLACE INTO "operating_reserve_derate" VALUES('A','summer','E_BATT',0.90,NULL); +REPLACE INTO "operating_reserve_derate" VALUES('A','winter','E_BATT',0.90,NULL); +REPLACE INTO "operating_reserve_derate" VALUES('B','summer','NGCC', 0.95,NULL); +REPLACE INTO "operating_reserve_derate" VALUES('B','winter','NGCC', 0.90,NULL); +REPLACE INTO "operating_reserve_margin" VALUES('A', 'elec_A', 0.10,NULL); +REPLACE INTO "operating_reserve_margin" VALUES('B', 'NGCC', 0.10,NULL); +REPLACE INTO "operating_reserve_margin" VALUES('A+B','elec_AB', 0.08,NULL); diff --git a/tests/testing_data/seasonal_storage.sql b/tests/testing_data/seasonal_storage.sql index 19b394b39..55ba285d2 100644 --- a/tests/testing_data/seasonal_storage.sql +++ b/tests/testing_data/seasonal_storage.sql @@ -43,7 +43,7 @@ REPLACE INTO "efficiency" VALUES('region', 'electricity', 'demand', 2000, 'deman REPLACE INTO "limit_storage_level_fraction" VALUES('region','winter','b','seas_stor','e',0.5,NULL); REPLACE INTO "limit_storage_level_fraction" VALUES('region','charge','b','dly_stor','e',0.5,NULL); REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); -REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',1,''); REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,'Discount Rate for future costs'); REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); REPLACE INTO "operator" VALUES('e','equal to'); @@ -53,10 +53,10 @@ REPLACE INTO "region" VALUES('region',NULL); REPLACE INTO "sector_label" VALUES('electricity',NULL); REPLACE INTO "storage_duration" VALUES('region','dly_stor',4.0,NULL); REPLACE INTO "storage_duration" VALUES('region','seas_stor',8760.0,NULL); -REPLACE INTO "technology" VALUES('generator','p','electricity',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('dly_stor','ps','electricity',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('seas_stor','ps','electricity',NULL,NULL,0,0,0,0,0,0,0,1,NULL); -REPLACE INTO "technology" VALUES('demand','p','electricity',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('generator','p','electricity',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('dly_stor','ps','electricity',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('seas_stor','ps','electricity',NULL,NULL,0,0,0,0,0,0,1,NULL); +REPLACE INTO "technology" VALUES('demand','p','electricity',NULL,NULL,0,0,0,0,0,0,0,NULL); REPLACE INTO "technology_type" VALUES('p','production technology'); REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); REPLACE INTO "technology_type" VALUES('ps','storage production technology'); diff --git a/tests/testing_data/simple_linked_tech.sql b/tests/testing_data/simple_linked_tech.sql index 25760ebc9..34e502b49 100644 --- a/tests/testing_data/simple_linked_tech.sql +++ b/tests/testing_data/simple_linked_tech.sql @@ -28,7 +28,7 @@ REPLACE INTO "lifetime_tech" VALUES('linkville', 'CCS', 100.0, NULL, ''); REPLACE INTO "lifetime_tech" VALUES('linkville', 'PLANT', 100.0, NULL, ''); REPLACE INTO "linked_tech" VALUES('linkville','PLANT','CO2','CCS',NULL); REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); -REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',1,''); REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,''); REPLACE INTO "operator" VALUES('e','equal to'); @@ -41,10 +41,10 @@ REPLACE INTO "sector_label" VALUES('transport',NULL); REPLACE INTO "sector_label" VALUES('commercial',NULL); REPLACE INTO "sector_label" VALUES('residential',NULL); REPLACE INTO "sector_label" VALUES('industrial',NULL); -REPLACE INTO "technology" VALUES('PLANT','p','supply',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('CCS','p','supply',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('MINE','p','supply',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('FAKE_SOURCE','p','supply',NULL,NULL,1,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('PLANT','p','supply',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('CCS','p','supply',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('MINE','p','supply',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('FAKE_SOURCE','p','supply',NULL,NULL,1,0,0,0,0,0,0,NULL); REPLACE INTO "technology_type" VALUES('p','production technology'); REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); REPLACE INTO "technology_type" VALUES('ps','storage production technology'); diff --git a/tests/testing_data/storageville.sql b/tests/testing_data/storageville.sql index e643378ba..3d33f461f 100644 --- a/tests/testing_data/storageville.sql +++ b/tests/testing_data/storageville.sql @@ -42,7 +42,7 @@ REPLACE INTO "limit_capacity" VALUES('electricville',2025,'EH','le',200.0,'','') REPLACE INTO "limit_capacity" VALUES('electricville',2025,'batt','le',100.0,'',''); REPLACE INTO "limit_storage_level_fraction" VALUES('electricville','s1','d1','batt','e',0.5,NULL); REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); -REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',1,''); REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,''); REPLACE INTO "operator" VALUES('e','equal to'); @@ -56,10 +56,10 @@ REPLACE INTO "sector_label" VALUES('commercial',NULL); REPLACE INTO "sector_label" VALUES('residential',NULL); REPLACE INTO "sector_label" VALUES('industrial',NULL); REPLACE INTO "storage_duration" VALUES('electricville','batt',10.0,NULL); -REPLACE INTO "technology" VALUES('well','p','supply','water','',0,0,0,0,0,0,0,0,'plain old water'); -REPLACE INTO "technology" VALUES('bulbs','p','residential','electric','',0,0,0,0,0,0,0,0,' residential lighting'); -REPLACE INTO "technology" VALUES('EH','pb','electric','hydro','',0,0,0,0,0,0,0,0,'hydro power electric plant'); -REPLACE INTO "technology" VALUES('batt','ps','electric','electric','',0,0,0,0,0,0,0,0,'big battery'); +REPLACE INTO "technology" VALUES('well','p','supply','water','',0,0,0,0,0,0,0,'plain old water'); +REPLACE INTO "technology" VALUES('bulbs','p','residential','electric','',0,0,0,0,0,0,0,' residential lighting'); +REPLACE INTO "technology" VALUES('EH','pb','electric','hydro','',0,0,0,0,0,0,0,'hydro power electric plant'); +REPLACE INTO "technology" VALUES('batt','ps','electric','electric','',0,0,0,0,0,0,0,'big battery'); REPLACE INTO "technology_type" VALUES('p','production technology'); REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); REPLACE INTO "technology_type" VALUES('ps','storage production technology'); diff --git a/tests/testing_data/survival_curve.sql b/tests/testing_data/survival_curve.sql index a0f32dc16..864d9de38 100644 --- a/tests/testing_data/survival_curve.sql +++ b/tests/testing_data/survival_curve.sql @@ -143,17 +143,17 @@ REPLACE INTO "lifetime_tech" VALUES('region', 'tech_old', 35.0, NULL, NULL); REPLACE INTO "lifetime_tech" VALUES('region', 'tech_current', 35.0, NULL, NULL); REPLACE INTO "lifetime_tech" VALUES('region', 'tech_future', 35.0, NULL, NULL); REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); -REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',1,''); REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,'Discount Rate for future costs'); REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); REPLACE INTO "operator" VALUES('e','equal to'); REPLACE INTO "operator" VALUES('le','less than or equal to'); REPLACE INTO "operator" VALUES('ge','greater than or equal to'); REPLACE INTO "region" VALUES('region',NULL); -REPLACE INTO "technology" VALUES('tech_ancient','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('tech_old','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('tech_current','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO "technology" VALUES('tech_future','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('tech_ancient','p','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('tech_old','p','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('tech_current','p','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('tech_future','p','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); REPLACE INTO "technology_type" VALUES('p','production technology'); REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); REPLACE INTO "technology_type" VALUES('ps','storage production technology'); diff --git a/tests/testing_data/test_system.sql b/tests/testing_data/test_system.sql index a50226c85..7bf6bbbd9 100644 --- a/tests/testing_data/test_system.sql +++ b/tests/testing_data/test_system.sql @@ -438,7 +438,7 @@ REPLACE INTO "limit_tech_output_split" VALUES('R2',2025,'S_OILREF','DSL','ge',0. REPLACE INTO "limit_tech_output_split" VALUES('R2',2030,'S_OILREF','GSL','ge',0.72,''); REPLACE INTO "limit_tech_output_split" VALUES('R2',2030,'S_OILREF','DSL','ge',0.08,''); REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); -REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',1,''); REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,''); REPLACE INTO "operator" VALUES('e','equal to'); @@ -454,22 +454,22 @@ REPLACE INTO "sector_label" VALUES('residential',NULL); REPLACE INTO "sector_label" VALUES('industrial',NULL); REPLACE INTO "storage_duration" VALUES('R1','E_BATT',8.0,'8-hour duration specified as fraction of a day'); REPLACE INTO "storage_duration" VALUES('R2','E_BATT',8.0,'8-hour duration specified as fraction of a day'); -REPLACE INTO "technology" VALUES('S_IMPETH','p','supply','','',1,0,0,0,0,0,0,0,' imported ethanol'); -REPLACE INTO "technology" VALUES('S_IMPOIL','p','supply','','',1,0,0,0,0,0,0,0,' imported crude oil'); -REPLACE INTO "technology" VALUES('S_IMPNG','p','supply','','',1,0,0,0,0,0,0,0,' imported natural gas'); -REPLACE INTO "technology" VALUES('S_IMPURN','p','supply','','',1,0,0,0,0,0,0,0,' imported uranium'); -REPLACE INTO "technology" VALUES('S_OILREF','p','supply','','',0,0,0,1,0,0,0,0,' crude oil refinery'); -REPLACE INTO "technology" VALUES('E_NGCC','p','electric','','',0,0,0,0,0,0,0,0,' natural gas combined-cycle'); -REPLACE INTO "technology" VALUES('E_SOLPV','p','electric','','',0,0,0,0,0,0,0,0,' solar photovoltaic'); -REPLACE INTO "technology" VALUES('E_BATT','ps','electric','','',0,0,0,0,0,0,0,0,' lithium-ion battery'); -REPLACE INTO "technology" VALUES('E_NUCLEAR','pb','electric','','',0,0,0,0,0,0,0,0,' nuclear power plant'); -REPLACE INTO "technology" VALUES('T_BLND','p','transport','','',0,0,0,0,0,0,0,0,'ethanol - gasoline blending process'); -REPLACE INTO "technology" VALUES('T_DSL','p','transport','','',0,0,0,0,0,0,0,0,'diesel vehicle'); -REPLACE INTO "technology" VALUES('T_GSL','p','transport','','',0,0,0,0,0,0,0,0,'gasoline vehicle'); -REPLACE INTO "technology" VALUES('T_EV','p','transport','','',0,0,0,0,0,0,0,0,'electric vehicle'); -REPLACE INTO "technology" VALUES('R_EH','p','residential','','',0,0,0,0,0,0,0,0,' electric residential heating'); -REPLACE INTO "technology" VALUES('R_NGH','p','residential','','',0,0,0,0,0,0,0,0,' natural gas residential heating'); -REPLACE INTO "technology" VALUES('E_TRANS','p','electric','','',0,0,0,0,0,0,1,0,'electric transmission'); +REPLACE INTO "technology" VALUES('S_IMPETH','p','supply','','',1,0,0,0,0,0,0,' imported ethanol'); +REPLACE INTO "technology" VALUES('S_IMPOIL','p','supply','','',1,0,0,0,0,0,0,' imported crude oil'); +REPLACE INTO "technology" VALUES('S_IMPNG','p','supply','','',1,0,0,0,0,0,0,' imported natural gas'); +REPLACE INTO "technology" VALUES('S_IMPURN','p','supply','','',1,0,0,0,0,0,0,' imported uranium'); +REPLACE INTO "technology" VALUES('S_OILREF','p','supply','','',0,0,1,0,0,0,0,' crude oil refinery'); +REPLACE INTO "technology" VALUES('E_NGCC','p','electric','','',0,0,0,0,0,0,0,' natural gas combined-cycle'); +REPLACE INTO "technology" VALUES('E_SOLPV','p','electric','','',0,0,0,0,0,0,0,' solar photovoltaic'); +REPLACE INTO "technology" VALUES('E_BATT','ps','electric','','',0,0,0,0,0,0,0,' lithium-ion battery'); +REPLACE INTO "technology" VALUES('E_NUCLEAR','pb','electric','','',0,0,0,0,0,0,0,' nuclear power plant'); +REPLACE INTO "technology" VALUES('T_BLND','p','transport','','',0,0,0,0,0,0,0,'ethanol - gasoline blending process'); +REPLACE INTO "technology" VALUES('T_DSL','p','transport','','',0,0,0,0,0,0,0,'diesel vehicle'); +REPLACE INTO "technology" VALUES('T_GSL','p','transport','','',0,0,0,0,0,0,0,'gasoline vehicle'); +REPLACE INTO "technology" VALUES('T_EV','p','transport','','',0,0,0,0,0,0,0,'electric vehicle'); +REPLACE INTO "technology" VALUES('R_EH','p','residential','','',0,0,0,0,0,0,0,' electric residential heating'); +REPLACE INTO "technology" VALUES('R_NGH','p','residential','','',0,0,0,0,0,0,0,' natural gas residential heating'); +REPLACE INTO "technology" VALUES('E_TRANS','p','electric','','',0,0,0,0,0,1,0,'electric transmission'); REPLACE INTO "technology_type" VALUES('p','production technology'); REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); REPLACE INTO "technology_type" VALUES('ps','storage production technology'); diff --git a/tests/testing_data/test_system_sets.json b/tests/testing_data/test_system_sets.json index 40e5b7ab4..4b6d3bbaa 100644 --- a/tests/testing_data/test_system_sets.json +++ b/tests/testing_data/test_system_sets.json @@ -56,8 +56,10 @@ "linked_emissions_tech_constraint_rpsdtve": "(empty)", "loan_lifetime_process_rtv": "5033502364848a3a3f295f1b3d051531ecd5c1e5f8bbaecd61afcd18181225ab", "new_capacity_var_rtv": "bc0ec9e7f812410cb2af924cbc99a31c6e99cd95fc2de26193daad38f33cc132", + "operating_reserve_rpsdt": "(empty)", "operator": "74d830836f1399fb336a0432dde7d7bd36cffa3ff76b1c42d7945350cfb9bf91", "ordered_season_sequential": "(empty)", + "planning_reserve_rpsdt": "(empty)", "process_life_frac_rptv": "d1b75ede9f90899b1c1cbc56489bf9d12c52abc6c0466eb5fe4dccaf6f3be800", "ramp_down_constraint_rpsdtv": "(empty)", "ramp_up_constraint_rpsdtv": "(empty)", @@ -65,9 +67,6 @@ "regional_global_indices": "92fa6c5d5745d765d6e16ad1bca7e1fc72f4377273be7cfbfde626ca1967d81b", "regional_indices": "f74187f92c4fdb3c12d5610304c7ac9696001433150bdaa9ff20793fb6365b32", "regions": "0ddd05d695b255ac719dfa85de1e900a3036d547ffc7261c9c9ca2c81bfda029", - "renewable_portfolio_standard_constraint_rpg": "(empty)", - "reserve_margin_method": "7869283c0d14273f720716309207a8f0c24606d03c679d6b68e656ed8d86241d", - "reserve_margin_rpsd": "(empty)", "retired_capacity_var_rptv": "(empty)", "seasonal_storage_constraints_rpsdtv": "(empty)", "seasonal_storage_level_rpstv": "(empty)", @@ -87,7 +86,6 @@ "tech_group_names": "(empty)", "tech_or_group": "85a3645929dbeaf6b7eb17e8085c8923ef86949eaa3fb4fd81724dcdcf38fd30", "tech_production": "85a3645929dbeaf6b7eb17e8085c8923ef86949eaa3fb4fd81724dcdcf38fd30", - "tech_reserve": "(empty)", "tech_retirement": "(empty)", "tech_seasonal_storage": "(empty)", "tech_storage": "7109b89425e6707adc8a5e571bf70fc64475d2d76471e5afe93110fd86bbcec8", diff --git a/tests/testing_data/test_week.sql b/tests/testing_data/test_week.sql index a0384dc26..d22900ef6 100644 --- a/tests/testing_data/test_week.sql +++ b/tests/testing_data/test_week.sql @@ -1,5 +1,5 @@ REPLACE INTO metadata VALUES('DB_MAJOR',4,'DB major version number'); -REPLACE INTO metadata VALUES('DB_MINOR',0,'DB minor version number'); +REPLACE INTO metadata VALUES('DB_MINOR',1,'DB minor version number'); REPLACE INTO metadata_real VALUES('global_discount_rate',0.05000000000000000277,'Discount Rate for future costs'); REPLACE INTO metadata_real VALUES('default_loan_rate',0.05000000000000000277,'Default Loan Rate if not specified in LoanRate table'); REPLACE INTO sector_label VALUES('energy',NULL); @@ -90,11 +90,12 @@ REPLACE INTO time_of_day VALUES(24,'h24',1.0,NULL); REPLACE INTO time_period VALUES(0,0,'f'); REPLACE INTO time_period VALUES(1,1,'f'); REPLACE INTO time_period_type VALUES('e','existing vintages'); -REPLACE INTO time_period_type VALUES('f','future');REPLACE INTO technology VALUES('uc_cheap','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO technology VALUES('uc_expensive','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO technology VALUES('demand_sink','p','energy',NULL,NULL,1,0,0,0,0,0,0,0,NULL); -REPLACE INTO technology VALUES('daily_storage','ps','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -REPLACE INTO technology VALUES('seasonal_storage','ps','energy',NULL,NULL,0,0,0,0,0,0,0,1,NULL); +REPLACE INTO time_period_type VALUES('f','future'); +REPLACE INTO technology VALUES('uc_cheap','p','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO technology VALUES('uc_expensive','p','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO technology VALUES('demand_sink','p','energy',NULL,NULL,1,0,0,0,0,0,0,NULL); +REPLACE INTO technology VALUES('daily_storage','ps','energy',NULL,NULL,0,0,0,0,0,0,0,NULL); +REPLACE INTO technology VALUES('seasonal_storage','ps','energy',NULL,NULL,0,0,0,0,0,0,1,NULL); REPLACE INTO time_season VALUES(1,'d01',0.1428571428571428493,'1/7'); REPLACE INTO time_season VALUES(2,'d02',0.1428571428571428493,'1/7'); REPLACE INTO time_season VALUES(3,'d03',0.1428571428571428493,'1/7'); diff --git a/tests/testing_data/utopia_data.sql b/tests/testing_data/utopia_data.sql index d0f131cb3..66afeaefe 100644 --- a/tests/testing_data/utopia_data.sql +++ b/tests/testing_data/utopia_data.sql @@ -414,7 +414,7 @@ REPLACE INTO "limit_tech_output_split" VALUES('utopia',1990,'SRE','GSL','ge',0.3 REPLACE INTO "limit_tech_output_split" VALUES('utopia',2000,'SRE','GSL','ge',0.3,''); REPLACE INTO "limit_tech_output_split" VALUES('utopia',2010,'SRE','GSL','ge',0.3,''); REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); -REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',1,''); REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,''); REPLACE INTO "operator" VALUES('e','equal to'); @@ -427,25 +427,25 @@ REPLACE INTO "sector_label" VALUES('transport',NULL); REPLACE INTO "sector_label" VALUES('commercial',NULL); REPLACE INTO "sector_label" VALUES('residential',NULL); REPLACE INTO "sector_label" VALUES('industrial',NULL); -REPLACE INTO "technology" VALUES('IMPDSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported diesel'); -REPLACE INTO "technology" VALUES('IMPGSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported gasoline'); -REPLACE INTO "technology" VALUES('IMPHCO1','p','supply','coal','',1,0,0,0,0,0,0,0,' imported coal'); -REPLACE INTO "technology" VALUES('IMPOIL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported crude oil'); -REPLACE INTO "technology" VALUES('IMPURN1','p','supply','nuclear','',1,0,0,0,0,0,0,0,' imported uranium'); -REPLACE INTO "technology" VALUES('IMPFEQ','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported fossil equivalent'); -REPLACE INTO "technology" VALUES('IMPHYD','p','supply','hydro','',1,0,0,0,0,0,0,0,' imported water -- doesnt exist in Utopia'); -REPLACE INTO "technology" VALUES('E01','pb','electric','coal','',0,0,0,0,0,0,0,0,' coal power plant'); -REPLACE INTO "technology" VALUES('E21','pb','electric','nuclear','',0,0,0,0,0,0,0,0,' nuclear power plant'); -REPLACE INTO "technology" VALUES('E31','pb','electric','hydro','',0,0,0,0,0,0,0,0,' hydro power'); -REPLACE INTO "technology" VALUES('E51','ps','electric','electric','',0,0,0,0,0,0,0,0,' electric storage'); -REPLACE INTO "technology" VALUES('E70','p','electric','petroleum','',0,0,0,0,0,0,0,0,' diesel power plant'); -REPLACE INTO "technology" VALUES('RHE','p','residential','electric','',0,0,0,0,0,0,0,0,' electric residential heating'); -REPLACE INTO "technology" VALUES('RHO','p','residential','petroleum','',0,0,0,0,0,0,0,0,' diesel residential heating'); -REPLACE INTO "technology" VALUES('RL1','p','residential','electric','',0,0,0,0,0,0,0,0,' residential lighting'); -REPLACE INTO "technology" VALUES('SRE','p','supply','petroleum','',0,0,0,0,0,0,0,0,' crude oil processor'); -REPLACE INTO "technology" VALUES('TXD','p','transport','petroleum','',0,0,0,0,0,0,0,0,' diesel powered vehicles'); -REPLACE INTO "technology" VALUES('TXE','p','transport','electric','',0,0,0,0,0,0,0,0,' electric powered vehicles'); -REPLACE INTO "technology" VALUES('TXG','p','transport','petroleum','',0,0,0,0,0,0,0,0,' gasoline powered vehicles'); +REPLACE INTO "technology" VALUES('IMPDSL1','p','supply','petroleum','',1,0,0,0,0,0,0,' imported diesel'); +REPLACE INTO "technology" VALUES('IMPGSL1','p','supply','petroleum','',1,0,0,0,0,0,0,' imported gasoline'); +REPLACE INTO "technology" VALUES('IMPHCO1','p','supply','coal','',1,0,0,0,0,0,0,' imported coal'); +REPLACE INTO "technology" VALUES('IMPOIL1','p','supply','petroleum','',1,0,0,0,0,0,0,' imported crude oil'); +REPLACE INTO "technology" VALUES('IMPURN1','p','supply','nuclear','',1,0,0,0,0,0,0,' imported uranium'); +REPLACE INTO "technology" VALUES('IMPFEQ','p','supply','petroleum','',1,0,0,0,0,0,0,' imported fossil equivalent'); +REPLACE INTO "technology" VALUES('IMPHYD','p','supply','hydro','',1,0,0,0,0,0,0,' imported water -- doesnt exist in Utopia'); +REPLACE INTO "technology" VALUES('E01','pb','electric','coal','',0,0,0,0,0,0,0,' coal power plant'); +REPLACE INTO "technology" VALUES('E21','pb','electric','nuclear','',0,0,0,0,0,0,0,' nuclear power plant'); +REPLACE INTO "technology" VALUES('E31','pb','electric','hydro','',0,0,0,0,0,0,0,' hydro power'); +REPLACE INTO "technology" VALUES('E51','ps','electric','electric','',0,0,0,0,0,0,0,' electric storage'); +REPLACE INTO "technology" VALUES('E70','p','electric','petroleum','',0,0,0,0,0,0,0,' diesel power plant'); +REPLACE INTO "technology" VALUES('RHE','p','residential','electric','',0,0,0,0,0,0,0,' electric residential heating'); +REPLACE INTO "technology" VALUES('RHO','p','residential','petroleum','',0,0,0,0,0,0,0,' diesel residential heating'); +REPLACE INTO "technology" VALUES('RL1','p','residential','electric','',0,0,0,0,0,0,0,' residential lighting'); +REPLACE INTO "technology" VALUES('SRE','p','supply','petroleum','',0,0,0,0,0,0,0,' crude oil processor'); +REPLACE INTO "technology" VALUES('TXD','p','transport','petroleum','',0,0,0,0,0,0,0,' diesel powered vehicles'); +REPLACE INTO "technology" VALUES('TXE','p','transport','electric','',0,0,0,0,0,0,0,' electric powered vehicles'); +REPLACE INTO "technology" VALUES('TXG','p','transport','petroleum','',0,0,0,0,0,0,0,' gasoline powered vehicles'); REPLACE INTO "technology_type" VALUES('p','production technology'); REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); REPLACE INTO "technology_type" VALUES('ps','storage production technology'); diff --git a/tests/testing_data/utopia_sets.json b/tests/testing_data/utopia_sets.json index dded63032..99a816e2f 100644 --- a/tests/testing_data/utopia_sets.json +++ b/tests/testing_data/utopia_sets.json @@ -56,8 +56,10 @@ "linked_emissions_tech_constraint_rpsdtve": "(empty)", "loan_lifetime_process_rtv": "2cfc288b15f25957dfc70f6396d97ad655ecbed91c5a11582329749f1fb3dbd7", "new_capacity_var_rtv": "d4f1cc8b432075001befddab648d54d1f82a29099236948845393a193b6add5b", + "operating_reserve_rpsdt": "(empty)", "operator": "74d830836f1399fb336a0432dde7d7bd36cffa3ff76b1c42d7945350cfb9bf91", "ordered_season_sequential": "(empty)", + "planning_reserve_rpsdt": "(empty)", "process_life_frac_rptv": "63d17957ee2bebf664ffa6a39cea5e16ec65ad07db1df24dcea6b15bb9ebf589", "ramp_down_constraint_rpsdtv": "(empty)", "ramp_up_constraint_rpsdtv": "(empty)", @@ -65,9 +67,6 @@ "regional_global_indices": "ce00905893c23bc59c15dcf61d7e261c2e97ab1fc298ed0e0ccb1344f1cace37", "regional_indices": "ce00905893c23bc59c15dcf61d7e261c2e97ab1fc298ed0e0ccb1344f1cace37", "regions": "ce00905893c23bc59c15dcf61d7e261c2e97ab1fc298ed0e0ccb1344f1cace37", - "renewable_portfolio_standard_constraint_rpg": "(empty)", - "reserve_margin_method": "7869283c0d14273f720716309207a8f0c24606d03c679d6b68e656ed8d86241d", - "reserve_margin_rpsd": "(empty)", "retired_capacity_var_rptv": "(empty)", "seasonal_storage_constraints_rpsdtv": "(empty)", "seasonal_storage_level_rpstv": "(empty)", @@ -87,7 +86,6 @@ "tech_group_names": "(empty)", "tech_or_group": "5c321f60e5d16e60c5063b83d59ac9e184ab78e7fc469e41b21fd8aea83f600c", "tech_production": "5c321f60e5d16e60c5063b83d59ac9e184ab78e7fc469e41b21fd8aea83f600c", - "tech_reserve": "(empty)", "tech_retirement": "(empty)", "tech_seasonal_storage": "(empty)", "tech_storage": "245737c06f3e838e63da08a47a7d2a8605340ea5cad86397e1bb66461a574358", diff --git a/tests/utilities/compare_lp.py b/tests/utilities/compare_lp.py new file mode 100644 index 000000000..5090a7f57 --- /dev/null +++ b/tests/utilities/compare_lp.py @@ -0,0 +1,276 @@ +""" +Lightweight LP comparison helper, extracted from compare_lp.py. + +Parses two LP files and returns a structured diff, without writing reports +to disk. Intended for use by the reserve margin regression test. +""" + +from __future__ import annotations + +import re +from collections import defaultdict +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from collections.abc import Generator + from pathlib import Path + +_CAMEL_RE = re.compile(r'([a-z0-9])([A-Z])') +_TERM_RE = re.compile(r'^([+\-]?\s*\d*\.?\d+(?:[eE][+\-]?\d+)?)\s+(\S+)$') +_RELATION_RE = re.compile(r'^([<>=]=?)\s*([\d.eE+\-]+)$') + + +def _camel_to_snake(s: str) -> str: + return _CAMEL_RE.sub(r'\1_\2', s).lower() + + +def _norm(token: str) -> str: + paren = token.find('(') + if paren == -1: + return _camel_to_snake(token) + return _camel_to_snake(token[:paren]) + token[paren:] + + +def _needs_normalization(path: Path, sample: int = 2000) -> bool: + checked = 0 + with path.open(encoding='utf-8', errors='replace') as fh: + for raw in fh: + s = raw.strip() + if not s or s.startswith('\\') or s.startswith('*'): + continue + line = re.sub(r'^([+\-])\s+', r'\1', s) + m = _TERM_RE.match(line) + token = None + if m: + token = m.group(2) + elif s.endswith(':') and not s.startswith(('+', '-')): + token = s.rstrip(':').rstrip('_') + if token: + paren = token.find('(') + name = token[:paren] if paren != -1 else token + if _CAMEL_RE.search(name): + return True + checked += 1 + if checked >= sample: + break + return False + + +def _stream(path: Path, normalize: bool) -> Generator[tuple[Any, ...]]: + tok = _norm if normalize else (lambda t: t) + section = 'preamble' + obj_name = None + obj_terms: list[tuple[float, str]] = [] + con_label: str | None = None + con_terms: list[tuple[float, str]] = [] + con_rel: str | None = None + con_rhs: float | None = None + + def _flush() -> tuple[str, str, list[tuple[float, str]], str | None, float | None] | None: + nonlocal con_label, con_terms, con_rel, con_rhs + result: tuple[str, str, list[tuple[float, str]], str | None, float | None] | None = None + if con_label is not None and con_rel is not None: + result = ('con', con_label, list(con_terms), con_rel, con_rhs) + con_label = None + con_terms = [] + con_rel = None + con_rhs = None + return result + + with path.open(encoding='utf-8', errors='replace') as fh: + for raw in fh: + s = raw.strip() + if not s: + continue + lower = s.lower() + + if lower in ('s.t.', 'subject to', 'st'): + if section == 'objective' and obj_terms: + yield ('obj', obj_name, obj_terms) + section = 'constraints' + continue + + if lower in ('bounds', 'generals', 'general', 'binary', 'binaries', 'end'): + r = _flush() + if r: + yield r + if section == 'objective' and obj_terms: + yield ('obj', obj_name, obj_terms) + yield ('bounds_start',) + section = 'done' + continue + + if section == 'done' or s.startswith('\\') or s.startswith('*'): + continue + + if section == 'preamble': + if lower in ('min', 'max'): + section = 'objective' + continue + + if section == 'objective': + if s.endswith(':') and not s.startswith(('+', '-')): + obj_name = tok(s.rstrip(':')) + continue + line = re.sub(r'^([+\-])\s+', r'\1', s) + m = _TERM_RE.match(line) + if m: + obj_terms.append((float(m.group(1).replace(' ', '')), tok(m.group(2)))) + continue + + if section == 'constraints': + if s.endswith(':') and not s.startswith(('+', '-')): + r = _flush() + if r: + yield r + con_label = tok(s.rstrip(':').rstrip('_')) + continue + m = _RELATION_RE.match(s) + if m: + con_rel, con_rhs = m.group(1), float(m.group(2)) + continue + line = re.sub(r'^([+\-])\s+', r'\1', s) + m = _TERM_RE.match(line) + if m: + con_terms.append((float(m.group(1).replace(' ', '')), tok(m.group(2)))) + + r = _flush() + if r: + yield r + if section == 'objective' and obj_terms: + yield ('obj', obj_name, obj_terms) + + +def _to_dict(terms: list[tuple[float, str]]) -> dict[str, float]: + d: dict[str, float] = defaultdict(float) + for c, v in terms: + d[v] += c + return dict(d) + + +@dataclass +class ConstraintDiff: + label: str + relation_changed: tuple[str, str] | None = None # (rel1, rel2) + rhs_changed: tuple[float, float] | None = None # (rhs1, rhs2) + terms_added: dict[str, float] = field(default_factory=dict) + terms_removed: dict[str, float] = field(default_factory=dict) + terms_changed: dict[str, tuple[float, float]] = field(default_factory=dict) + + def __bool__(self) -> bool: + return bool( + self.relation_changed + or self.rhs_changed + or self.terms_added + or self.terms_removed + or self.terms_changed + ) + + +@dataclass +class LpDiff: + only_in_a: list[str] = field(default_factory=list) + only_in_b: list[str] = field(default_factory=list) + changed: list[ConstraintDiff] = field(default_factory=list) + obj_diff: list[str] = field(default_factory=list) # human-readable lines + + @property + def is_identical(self) -> bool: + return not (self.only_in_a or self.only_in_b or self.changed or self.obj_diff) + + def summary(self) -> str: + lines = [ + f'only in A : {len(self.only_in_a)}', + f'only in B : {len(self.only_in_b)}', + f'changed : {len(self.changed)}', + f'obj diffs : {len(self.obj_diff)}', + ] + if self.only_in_a: + lines.append(' A-only (first 10): ' + ', '.join(self.only_in_a[:10])) + if self.only_in_b: + lines.append(' B-only (first 10): ' + ', '.join(self.only_in_b[:10])) + for cd in self.changed[:5]: + lines.append(f' changed: {cd.label}') + if cd.relation_changed: + lines.append(f' relation: {cd.relation_changed[0]} → {cd.relation_changed[1]}') + if cd.rhs_changed: + lines.append(f' rhs: {cd.rhs_changed[0]:+g} → {cd.rhs_changed[1]:+g}') + for v, (c1, c2) in list(cd.terms_changed.items())[:3]: + lines.append(f' coeff {v}: {c1:+g} → {c2:+g}') + return '\n'.join(lines) + + +def compare_lp_files(path_a: Path, path_b: Path, rtol: float = 1e-6) -> LpDiff: + """ + Compare two LP files and return a structured diff. + + Auto-detects CamelCase vs snake_case naming and normalises both sides + when either file uses CamelCase, so mixed-format comparisons work correctly. + """ + apply_norm = _needs_normalization(path_a) or _needs_normalization(path_b) + + # Load file A into memory + cons_a: dict[str, tuple[dict[str, float], str, float]] = {} + obj_a: tuple[str | None, dict[str, float]] | None = None + for block in _stream(path_a, apply_norm): + if block[0] == 'obj': + obj_a = (block[1], _to_dict(block[2])) + elif block[0] == 'con': + _, label, terms, rel, rhs = block + cons_a[label] = (_to_dict(terms), rel, rhs) + elif block[0] == 'bounds_start': + break + + diff = LpDiff() + seen: set[str] = set() + obj_b: tuple[str | None, dict[str, float]] | None = None + + for block in _stream(path_b, apply_norm): + if block[0] == 'obj': + obj_b = (block[1], _to_dict(block[2])) + elif block[0] == 'con': + _, label, terms, rel, rhs = block + td = _to_dict(terms) + if label not in cons_a: + diff.only_in_b.append(label) + else: + seen.add(label) + td_a, rel_a, rhs_a = cons_a[label] + cd = ConstraintDiff(label=label) + if rel_a != rel: + cd.relation_changed = (rel_a, rel) + if abs(rhs_a - rhs) > rtol * max(abs(rhs_a), abs(rhs), 1e-15): + cd.rhs_changed = (rhs_a, rhs) + all_vars = sorted(set(td_a) | set(td)) + for v in all_vars: + c1, c2 = td_a.get(v), td.get(v) + if c1 is None: + cd.terms_added[v] = c2 # type: ignore[assignment] + elif c2 is None: + cd.terms_removed[v] = c1 + else: + tol = rtol * max(abs(c1), abs(c2), 1e-15) + if abs(c1 - c2) > tol: + cd.terms_changed[v] = (c1, c2) + if cd: + diff.changed.append(cd) + elif block[0] == 'bounds_start': + break + + diff.only_in_a = [lbl for lbl in cons_a if lbl not in seen] + + if obj_a and obj_b: + all_vars = sorted(set(obj_a[1]) | set(obj_b[1])) + for v in all_vars: + c1, c2 = obj_a[1].get(v), obj_b[1].get(v) + if c1 is None: + diff.obj_diff.append(f'ADDED {v} coeff={c2:+g}') + elif c2 is None: + diff.obj_diff.append(f'REMOVED {v} coeff={c1:+g}') + else: + tol = rtol * max(abs(c1), abs(c2), 1e-15) + if abs(c1 - c2) > tol: + diff.obj_diff.append(f'CHANGED {v} {c1:+g} → {c2:+g}') + + return diff