Skip to content

Constraints

CG_limit_supply(model, PD, H, ABA, CG, CGFT)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
H

The hour within the planning period for which the generation limit is relevant.

required
ABA

The balancing area as a string, indicating where the cogeneration plant is located.

required
CG

The identifier for the cogeneration plant type.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the electricity generation from cogeneration plants at the specified

location and time to not exceed the allowable output calculated from the total effective capacity of these

plants multiplied by a predefined cogeneration contribution rate defined here as the proportion of a unit's total supply that is exported to the transmission system.

Details
  • This constraint calculates the maximum generation limit for cogeneration plants by considering both the existing capacity at the start of the period and any capacity changes due to new installations or retirements.
  • The total effective capacity is then multiplied by a cogeneration contribution rate (e.g., 0.37 as the default COPPER value), which represents the proportion of a unit's total supply that is exported to the transmission system.
  • The cogeneration rate is typically configured externally (such as in a configuration file like config.toml), allowing for flexibility in scenario analysis.
Source code in COPPER.py
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
def CG_limit_supply(model, PD, H, ABA, CG, CGFT):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        H: The hour within the planning period for which the generation limit is relevant.
        ABA: The balancing area as a string, indicating where the cogeneration plant is located.
        CG: The identifier for the cogeneration plant type.

    Returns:
        A Pyomo Constraint expression: Limits the electricity generation from cogeneration plants at the specified
        location and time to not exceed the allowable output calculated from the total effective capacity of these
        plants multiplied by a predefined cogeneration contribution rate defined here as the proportion of a unit's
		total supply that is exported to the transmission system.

    Details:
        - This constraint calculates the maximum generation limit for cogeneration plants by considering both the
          existing capacity at the start of the period and any capacity changes due to new installations or retirements.
        - The total effective capacity is then multiplied by a cogeneration contribution rate (e.g., 0.37 as the default COPPER value),
          which represents the proportion of a unit's total supply that is exported to the transmission system.
        - The cogeneration rate is typically configured externally (such as in a configuration file like `config.toml`),
          allowing for flexibility in scenario analysis.
    """
    ind = pds.index(PD)
    for FT in CGFT.split('.'):
        return model.supply[PD, H, ABA, CG, FT] <= (quicksum(
            model.capacity_therm[PDD, ABA, CG] - model.retire_therm[PDD, ABA, CG] for PDD in pds[:ind + 1]) +
                                                    extant_thermal[pds[0] + '.' + ABA + '.' + CG]) * cogeneration_rate  # The COPPER default value is 0.37 this cogeneration rate is set in config.toml

aba_capacity_limit(model, PD, ABA, cap)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
ABA

The balancing area as a string, indicating where the generators are located.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the total net capacity of the specified generation type in the

specified balancing area (ABA) does not exceed a predefined maximum limit ((Alberta.a, biomass)[capacity_limit]).

Details
  • The function calculates the total net capacity of a specified power plants in the balancing area, taking into account the initial existing capacity and any subsequent capacity changes due to new installations or retirements.
  • The cumulative capacity from the beginning of the period up to and including the current planning period is considered, ensuring that the capacity does not surpass the maximum allowed as specified in max_development.
  • This constraint is important for managing the scale of specific power platn expansion in compliance with regional energy policies or sustainability goals, which may limit the expansion of specific facilities to control emissions or due to resource availability.
Source code in COPPER.py
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
def aba_capacity_limit(model, PD, ABA, cap):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        ABA: The balancing area as a string, indicating where the generators are located.

    Returns:
        A Pyomo Constraint expression: Ensures that the total net capacity of the specified generation type in the 
        specified balancing area (ABA) does not exceed a predefined maximum limit (`(Alberta.a, biomass)[capacity_limit]`).

    Details:
        - The function calculates the total net capacity of a specified power plants in the balancing area, taking into account 
        the initial existing capacity and any subsequent capacity changes due to new installations or retirements.
        - The cumulative capacity from the beginning of the period up to and including the current planning period is considered, 
        ensuring that the capacity does not surpass the maximum allowed as specified in `max_development`.
        - This constraint is important for managing the scale of specific power platn expansion in compliance with regional energy 
        policies or sustainability goals, which may limit the expansion of specific facilities to control emissions or due 
        to resource availability.
    """
    # Initialization
    ind = pds.index(PD)
    limited_cap = cap
    capacity, capacity_limit = 0, 999999999

    # Loop to establish capacity limits
    for gen in model.tplants:
        if gen.endswith("pre2010") or gen.endswith("2010to2024") or gen.endswith("post2025"):
            suffix = gen.split("_")[-1]
            g = gen.removesuffix("_" + suffix)

            if g.endswith("under25"):
                suffix = gen.split("_")[-1]
                g = gen.removesuffix("_" + suffix)
        else:
            g = gen

        if g == limited_cap:
            capacity += extant_thermal[pds[0] + '.' + ABA + '.' + gen] + quicksum(
                model.capacity_therm[PDD, ABA, gen] - model.retire_therm[PDD, ABA, gen] for PDD in
                pds[:ind + 1])
        else:
            capacity += 0

    try:
        capacity_limit = max_development.query('ABA == @ABA & gen_type == @limited_cap')['capacity_limit'].values[0]
    except IndexError:
        capacity_limit = 99999999

    if type(capacity) is int:
        return Constraint.Skip
    else:
        return capacity <= capacity_limit

aggregate_phaseout_rule(model, PHOT, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PHOT

The identifier for the type of thermal plant being phased out (e.g., 'coal', 'oil').

required
ABA

The balancing area as a string, indicating where the phase-out is taking place.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the total existing and installed capacity of thermal power plants

of type PHOT in all balancing areas over the relevant planning periods are <= the retirement value in the phaseout

year set in config.toml

phase-out year.

Source code in COPPER.py
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
def aggregate_phaseout_rule(model, PHOT, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PHOT: The identifier for the type of thermal plant being phased out (e.g., 'coal', 'oil').
        ABA: The balancing area as a string, indicating where the phase-out is taking place.

    Returns:
        A Pyomo Constraint expression: Ensures that the total existing and installed capacity of thermal power plants
        of type PHOT in all balancing areas over the relevant planning periods are <= the retirement value in the phaseout 
        year set in config.toml 
        phase-out year.
    """
    try:
        ind = pds.index(phase_out_type_year[PHOT])
    except KeyError:
        prov = ABA.split('.')[0]
        ind = pds.index(str(prov_ph_out_t.query('AP == @prov & gen_type == @PHOT')['phase_out_year'].values[0]))

    peak_existing_capacity = 0
    # Finds the peak existing capacity of a given generation type
    for PDD in pds[0:ind + 1]:
        try:
            if extant_thermal[PDD + '.' + ABA + '.' + PHOT] > peak_existing_capacity:
                peak_existing_capacity = extant_thermal[PDD + '.' + ABA + '.' + PHOT]
        except KeyError:
            pass
    # forces the sum of retirement values to be equal to existing and built capacity by the retirement year
    return peak_existing_capacity + quicksum(
        model.capacity_therm[PDD, ABA, PHOT] for PDD in pds[0:ind + 1]) <= quicksum(
        model.retire_therm[PDD, ABA, PHOT] for PDD in pds[0:ind + 1])

annual_usage_limit(model, PD, fuel)

Calculates the annual usage upper limit for different fuel types accross all technologies which utilize that fuel type. This calculation also grouos the supply regionally, therefore applying the limit to the collection of all provinces being represented (national scope)

Parameters:

Name Type Description Default
model

A Pyomo model instance, which includes all decision variables, parameters, and sets related to the model.

required
PD

The planning period as a string for which the O&M cost constraint is being calculated (e.g., '2025').

required

Returns:

Type Description

Total generation (i.e model.supply[PD, H, ABA, TP, FT]) <= annual capacity limit (Pyomo Constraint): expression that sets the supply for all facilities (TP)

which utilize the specific fuel type (FT) during the related period (PD).

Skips the constraint for the technologies which do not use the fuels specified in the annual_use_flags dictionary

Source code in COPPER.py
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
def annual_usage_limit(model, PD, fuel):
    """
    Calculates the annual usage upper limit for different fuel types accross all technologies
    which utilize that fuel type. This calculation also grouos the supply regionally, 
    therefore applying the limit to the collection of all provinces being represented (national scope)

    Parameters:
        model: A Pyomo model instance, which includes all decision variables, parameters, and sets related to the model.
        PD: The planning period as a string for which the O&M cost constraint is being calculated (e.g., '2025').

    Returns:
        Total generation (i.e model.supply[PD, H, ABA, TP, FT]) <= annual capacity limit (Pyomo Constraint): expression that sets the supply for all facilities (TP) 
        which utilize the specific fuel type (FT) during the related period (PD).
        Skips the constraint for the technologies which do not use the fuels specified in the annual_use_flags dictionary
    """
    # Initialization
    y = int(PD)
    limited_fuel = fuel
    generation, generation_limit = 0, 999999999

    # Loop to establish generation limits
    for FT in model.ftype:
        if FT.startswith("h2_"):
            f = "h2"
        elif FT.endswith("underlim") or FT.endswith("overlim"):
            suffix = FT.split("_")[-1]
            f = FT.removesuffix("_" + suffix)
        else:
            f = FT

        if f == limited_fuel:
            generation += quicksum(model.supply[PD, H, ABA, TP, FT]
                                for H in model.h for ABA in model.aba for TP in model.tplants
                                if FT in useful_fuels[TP].split('.')) / repday_scaling
        else:
            generation += 0

    try:
        generation_limit = max_usage.query('fuel_type == @limited_fuel & pd == @y')['generation_limit'].values[0]
    except IndexError:
        generation_limit = 99999999

    if type(generation) is int:
        return Constraint.Skip
    else:
        return generation <= generation_limit

bind_nuclear_binary(model, PD, ABA)

Binds model.nuclear_binary variable to abstracted nuclear capacity variable (model.capacity_nuclear). Only required for linearization of semi-continuous nuclear capacity varibale constraints.

Source code in COPPER.py
3031
3032
3033
3034
3035
3036
def bind_nuclear_binary(model, PD, ABA):
    """
    Binds model.nuclear_binary variable to abstracted nuclear capacity variable (model.capacity_nuclear).
    Only required for linearization of semi-continuous nuclear capacity varibale constraints.
    """
    return model.capacity_nuclear[PD, ABA] <= 99999 * model.nuclear_binary[PD, ABA]

calculate_carbon(pds, aba, gen_data, fuel_type_data, useful_fuels, CCS_capture_rate, OBPS, OBPS_intensity, carbon_credits_on, flag_cer)

Calculates the plant-specific carbon intensities, applying OBPS if/when applicable Reference for OBPS: https://laws-lois.justice.gc.ca/eng/regulations/SOR-2019-266/page-11.html#h-1185036

Returns a dataframe for carbon intensities of each plant (constant) and another for the policy-modified intensities for carbon tax calculations

Source code in phases/preprocessingtools.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def calculate_carbon(pds, aba, gen_data, fuel_type_data, useful_fuels, CCS_capture_rate, OBPS, OBPS_intensity, carbon_credits_on, flag_cer):
    """
    Calculates the plant-specific carbon intensities, applying OBPS if/when applicable
    Reference for OBPS: https://laws-lois.justice.gc.ca/eng/regulations/SOR-2019-266/page-11.html#h-1185036

    Returns a dataframe for carbon intensities of each plant (constant) and another for the policy-modified intensities
    for carbon tax calculations
    """

    carbondioxide = {}
    carbondioxide_for_ctax = {}
    carboncredits = {}

    for gen_type in useful_fuels.keys():
        for fuel in useful_fuels[gen_type].split("."):
            gen_efficiency = gen_data.loc[gen_type, 'efficiency']
            fuel_data = fuel_type_data.loc[fuel]
            fuel_co2 = fuel_data.fuel_co2 / gen_efficiency
            # Apply CCS capture rate
            if 'ccs' in gen_type:
                fuel_co2 = fuel_co2 * (1-CCS_capture_rate)

            fuel_co2 = np.nan_to_num(fuel_co2, nan=0.0)
            carbondioxide[f'{gen_type}.{fuel}'] = fuel_co2

            for ba in aba:
                for PD in pds:

                    if OBPS:
                        # check if OBPS applies to fuel
                        if not f'{PD}.{gen_type}.{fuel}.{ba}' in OBPS_intensity.keys():
                            carbondioxide_for_ctax[f'{PD}.{gen_type}.{fuel}.{ba}'] = 0

                        # cost of offset assumed to apply to full emissions and assumed to be priced at federal carbon price
                        elif 'overlim' in fuel and flag_cer:
                            carbondioxide_for_ctax[f'{PD}.{gen_type}.{fuel}.{ba}'] = fuel_co2

                        # apply carbon price to emissions over OBPS stanadrd at federal carbon price
                        else:
                            carbondioxide_for_ctax[f'{PD}.{gen_type}.{fuel}.{ba}'] = fuel_co2 - OBPS_intensity[
                                f'{PD}.{gen_type}.{fuel}.{ba}']

                    else:
                        if 'overlim' in fuel:
                            # full carbon tax charged for carbon offsets
                            carbondioxide_for_ctax[f'{PD}.{gen_type}.{fuel}.{ba}'] = fuel_co2

                        else:
                            # no carbon pricing if OBPS off and not using offsets
                            carbondioxide_for_ctax[f'{PD}.{gen_type}.{fuel}.{ba}'] = 0

                    # fill empty dict keys with zero
                    carbondioxide_for_ctax[f'{PD}.{gen_type}.{fuel}.{ba}'] = np.nan_to_num(
                        carbondioxide_for_ctax[f'{PD}.{gen_type}.{fuel}.{ba}'], nan=0.0)

                    if carbon_credits_on:
                        # create dataframe of carbon credit amount for each generator
                        carboncredits[f'{PD}.{gen_type}.{fuel}.{ba}'] = abs(min(carbondioxide_for_ctax[f'{PD}.{gen_type}.{fuel}.{ba}'], 0))
                    else:
                        # carbon credit value set to 0 if carbon credits not active
                        carboncredits[f'{PD}.{gen_type}.{fuel}.{ba}'] = 0

                    # not accounting for -ve ctax (carbon credits) in fuel costs
                    # instead considerd as a seperate variable
                    carbondioxide_for_ctax[f'{PD}.{gen_type}.{fuel}.{ba}'] = max(carbondioxide_for_ctax[f'{PD}.{gen_type}.{fuel}.{ba}'], 0)

    return carbondioxide, carbondioxide_for_ctax, carboncredits

calculate_carboncredits(pds, aba, carboncredits, useful_fuels, ctax)

Calculate carbon credit value when carbon credits are active.

Returns the carbon credit value (-ve cost) dictionary indexed by period, plant type, fuel type, and balancing area.

Source code in phases/preprocessingtools.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def calculate_carboncredits(pds, aba, carboncredits, useful_fuels, ctax):
    """
    Calculate carbon credit value when carbon credits are active.

    Returns the carbon credit value (-ve cost) dictionary indexed by period, plant type, fuel type, and balancing area.
    """

    carboncredit_value = {}

    for PD in pds:
        for ba in aba:
            for gen_type in useful_fuels.keys():
                for fuel in useful_fuels[gen_type].split("."):
                    carboncredit_value[f'{PD}.{gen_type}.{fuel}.{ba}'] = carboncredits[f'{PD}.{gen_type}.{fuel}.{ba}'] * ctax[PD]

    return carboncredit_value

calculate_fuelcost(pds, tplants, useful_fuels, ABA, gen_data, fuel_data, gasprice, local_gas_price, ctax, ctax_prov, carbondioxide_for_ctax, carbondioxide, price_evolution)

Calculates fuel costs, including local gas prices and carbon tax Inputs: * pds: periods of the model scope * tplants: thermal plants considered in the model * useful_fuels: a dictionary of all fuel types used by each generator * ABA: all geographic balancing areas in the model * gen_data: a Pandas dataframe with specific data for all generators in the model (example: capacity factors, efficiency, cost data) * fuel_data: Pandas dataframe with specific data for all fuels (example: base price, fuel type) * gasprice: price of a gas fuel type (can vary depending on policy measures being applied) * ctax: carbon tax rate applied to a fuel type * ctax_prov: provincial scaling factor for carbon tax rate * carbondioxide: emissions level of the plant in equivalence of Mt of CO2 * price_evolution: dataframe with the growth rate of the costs of each type for each generator over the timeline of the model

Source code in phases/preprocessingtools.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def calculate_fuelcost(pds, tplants, useful_fuels, ABA, gen_data, fuel_data, gasprice, local_gas_price, 
                       ctax, ctax_prov, carbondioxide_for_ctax, carbondioxide, price_evolution):
    """
    Calculates fuel costs, including local gas prices and carbon tax
    Inputs:
    * pds: periods of the model scope
    * tplants: thermal plants considered in the model
    * useful_fuels: a dictionary of all fuel types used by each generator
    * ABA: all geographic balancing areas in the model 
    * gen_data: a Pandas dataframe with specific data for all generators in the model (example: capacity factors, efficiency, cost data)
    * fuel_data: Pandas dataframe with specific data for all fuels (example: base price, fuel type)
    * gasprice: price of a gas fuel type (can vary depending on policy measures being applied)
    * ctax: carbon tax rate applied to a fuel type
    * ctax_prov: provincial scaling factor for carbon tax rate
    * carbondioxide: emissions level of the plant in equivalence of Mt of CO2
    * price_evolution: dataframe with the growth rate of the costs of each type for each generator over the timeline of the model
    """
    energy_cost = {}
    evolution = price_evolution[price_evolution['type'] == 'fuel']

    for PD in pds:
        for gen_type in tplants:
            for fuel in useful_fuels[gen_type].split("."):
                if fuel in evolution.index:
                    price_mod = evolution.loc[fuel, PD]
                else:
                    price_mod = 1
                gen_efficiency = gen_data.loc[gen_type].efficiency
                for ba in ABA:   
                    if (fuel == 'gas_underlim' or fuel == 'gas_overlim') and local_gas_price:
                        # applying local gas prices
                        energy_cost[f"{PD}.{gen_type}.{fuel}.{ba}"] = (gasprice[ba] * price_mod / gen_efficiency) * 3.6
                    else:
                        energy_cost[f"{PD}.{gen_type}.{fuel}.{ba}"] = (fuel_data.loc[fuel, "base_price"] * price_mod / gen_efficiency) * 3.6

                    if 'retire' in gen_type:
                        energy_cost[f"{PD}.{gen_type}.{fuel}.{ba}"] = 0

                    # adds in the carbon cost to the fuel cost
                    if f"{gen_type}.{fuel}" in carbondioxide:
                        prov = ba.split(".")[0]
                        if prov in ctax_prov.keys() and not 'overlim' in fuel:
                            prov_multiplier = ctax_prov[prov]
                        else:
                            prov_multiplier = 1
                        energy_cost[f"{PD}.{gen_type}.{fuel}.{ba}"] += carbondioxide_for_ctax[f"{PD}.{gen_type}.{fuel}.{ba}"] * ctax[PD] * prov_multiplier

    return energy_cost

cap(model, PD, H, ABA, TP)

Calculate and enforce the capacity constraint for a given planning district, hour, area balancing authority, and time period. This function ensures that the supply does not exceed the sum of the thermal capacities minus retirements up to and including the current planning district, adjusted by the extant thermal capacity.

Parameters:

Name Type Description Default
model Pyomo Object

The optimization model object, which includes parameters and sets used in the calculation.

required
PD str

the time period under consideration as a string.

required
H

The specific hour for which the capacity constraint is being calculated.

required
ABA

The area balancing authority identifier, which specifies the region under consideration. TP (str): Themal Plant Type

required

Returns: constraint : Constraint A constraint object that limits the supply for the specified inputs to not exceed the calculated capacity. Note: The capacity is calculated as the sum of the capacities from all the planning periods up to and including the current one, adjusted for any retirements and added to the initial extant thermal capacity for the area and time period.

Source code in COPPER.py
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
def cap(model, PD, H, ABA, TP):
    """
    Calculate and enforce the capacity constraint for a given planning district, hour, area balancing authority, and time period.
    This function ensures that the supply does not exceed the sum of the thermal capacities minus retirements up to and including 
    the current planning district, adjusted by the extant thermal capacity.

    Parameters:
        model (Pyomo Object): The optimization model object, which includes parameters and sets used in the calculation.
        PD (str): the time period under consideration as a string.
        H: The specific hour for which the capacity constraint is being calculated.
        ABA: The area balancing authority identifier, which specifies the region under consideration.
		TP (str): Themal Plant Type
    Returns:
    constraint : Constraint
        A constraint object that limits the supply for the specified inputs to not exceed the calculated capacity.
    Note:
        The capacity is calculated as the sum of the capacities from all the planning periods up to and including the current one, adjusted for any retirements and added to the initial extant thermal capacity for the area and time period.
    """
    ind = pds.index(PD)
    for FT in model.ftype:
        if FT in useful_fuels[TP].split('.'):
            return model.supply[PD, H, ABA, TP, FT] <= quicksum(
                    model.capacity_therm[PDD, ABA, TP] - model.retire_therm[PDD, ABA, TP] for PDD in pds[:ind + 1]) + \
                    extant_thermal[pds[0] + '.' + ABA + '.' + TP]

cap_cost_constraint(model, PD)

Constrains model.capcost for a given period to be greater than or equal to the sum of capacity[]capitalcosts[] for all generation types + transmission_capacitytranscost*distance for interprovincial transmission. If any ITC or government funds flags are set to true in config.toml then capacity of plants are multiplied by the corresponding subsidy factor.

Parameters:

Name Type Description Default
model Object

The optimization model.

required
PD str

The specific period.

required

Returns:

Name Type Description
Constraint Object

pyomo.core.expr.relational_expr.InequalityExpression object.

Source code in COPPER.py
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
def cap_cost_constraint(model, PD):
    """
    Constrains model.capcost for a given period to be greater than or equal to the sum of 
    capacity[]*capitalcosts[] for all generation types + transmission_capacity*transcost*distance for interprovincial transmission.
    If any ITC or government funds flags are set to true in config.toml then capacity of plants are multiplied by the corresponding 
    subsidy factor.

    Parameters:
        model (Object): The optimization model.
        PD (str): The specific period.

    Returns:
        Constraint (Object): pyomo.core.expr.relational_expr.InequalityExpression object.
    """

    ind = pds.index(PD)
    if flag_itc_ccs or flag_itc_clean_tech or flag_itc_clean_elec or cib_funds_flag:
        summed_capacity = quicksum(
            model.capacity_therm[PDD, ABA, TP] * capitalcost[f'{ABA}.{TP}.{PDD}'] * subsidy_factor[f'{ABA}.{TP}'] for PDD in
            pds[:ind + 1] for
            TP in tplants for ABA in aba if PDD in ['2025', '2030']) \
                          + quicksum(
            model.capacity_therm[PDD, ABA, TP] * capitalcost[f'{ABA}.{TP}.{PDD}'] for PDD in pds[:ind + 1] for TP in
            tplants for ABA in aba if PDD in ['2035', '2040', '2045', '2050']) \
                          + quicksum(model.capacity_wind_ons[PDD, GL] * subsidy_factor[f'{map_gl_to_ba[int(GL)]}.wind_ons'] * windonscost[PDD + '.' + GL]
                                     + model.capacity_wind_ofs[PDD, GL] * subsidy_factor[f'{map_gl_to_ba[int(GL)]}.wind_ofs'] * windofscost[PDD + '.' + GL]
                                     + model.capacity_solar[PDD, GL] * subsidy_factor[f'{map_gl_to_ba[int(GL)]}.solar'] * solarcost[PDD + '.' + GL]
                                     + model.capacity_wind_ons_recon[PDD, GL] * subsidy_factor[f'{map_gl_to_ba[int(GL)]}.wind_ons'] * windonscost_recon[PDD]
                                     + model.capacity_wind_ofs_recon[PDD, GL] * subsidy_factor[f'{map_gl_to_ba[int(GL)]}.wind_ofs'] * windofscost_recon[PDD]
                                     + model.capacity_solar_recon[PDD, GL] * subsidy_factor[f'{map_gl_to_ba[int(GL)]}.solar'] * solarcost_recon[PDD] for PDD
                                     in pds[:ind + 1] for GL in model.gl if PDD in ['2025', '2030']) \
                          + quicksum(model.capacity_wind_ons[PDD, GL] * windonscost[PDD + '.' + GL]
                                     + model.capacity_wind_ofs[PDD, GL] * windofscost[PDD + '.' + GL]
                                     + model.capacity_solar[PDD, GL] * solarcost[PDD + '.' + GL]
                                     + model.capacity_wind_ons_recon[PDD, GL] * windonscost_recon[PDD]
                                     + model.capacity_wind_ofs_recon[PDD, GL] * windofscost_recon[PDD]
                                     + model.capacity_solar_recon[PDD, GL] * solarcost_recon[PDD] for PDD
                                     in pds[:ind + 1] for GL in model.gl if PDD in ['2035', '2040', '2045', '2050'])
        if flag_itc_clean_elec:
            transmission_capcost = quicksum(
                    model.capacity_transmission[PDD, ABA, ABBA] * factor_itc_clean_elec * transcost * distance[ABA + '.' + ABBA]
                    for PDD in pds[:ind + 1] for ABA in aba for ABBA in aba if ABA + '.' + ABBA in transmap and PDD in ['2025', '2030']) \
                + quicksum(
                    model.capacity_transmission[PDD, ABA, ABBA] * transcost * distance[ABA + '.' + ABBA]
                    for PDD in pds[:ind + 1] for ABA in aba for ABBA in aba if ABA + '.' + ABBA in transmap and PDD in ['2035', '2040', '2045', '2050'])
        else:
            transmission_capcost = quicksum(
                model.capacity_transmission[PDD, ABA, ABBA] * transcost * distance[ABA + '.' + ABBA]
                for PDD in pds[:ind + 1] for ABA in aba for ABBA in aba if ABA + '.' + ABBA in transmap)

    else:
        summed_capacity = quicksum(
            model.capacity_therm[PDD, ABA, TP] * capitalcost[f'{ABA}.{TP}.{PDD}'] for PDD in pds[:ind + 1] for TP in
            tplants
            for ABA in aba) \
            + quicksum(model.capacity_wind_ons[PDD, GL] * windonscost[PDD + '.' + GL]
                       + model.capacity_wind_ofs[PDD, GL] * windofscost[PDD + '.' + GL]
                       + model.capacity_solar[PDD, GL] * solarcost[PDD + '.' + GL]
                       + model.capacity_wind_ons_recon[PDD, GL] * windonscost_recon[PDD]
                       + model.capacity_wind_ofs_recon[PDD, GL] * windofscost_recon[PDD]
                       + model.capacity_solar_recon[PDD, GL] * solarcost_recon[PDD] for PDD in pds[:ind + 1] for GL in
                       model.gl)

        transmission_capcost = quicksum(model.capacity_transmission[PDD, ABA, ABBA] * transcost * distance[ABA + '.' + ABBA] for PDD in
                       pds[:ind + 1] for ABA in aba for ABBA in aba if ABA + '.' + ABBA in transmap)

    return model.capcost[PD] >= summed_capacity + transmission_capcost

carboncredit_revenue_constraint(model, PD)

Defines a constraint on the carbon credit revenue for various types of power generation (thermal, wind, solar, and hydro) during each economic dispatch period.

This function calculates the total carbon credit revenye for all power plant types across all hours and locations, scaled to an annual cost by the representative day demand scaling factor. Representative days are set in config.toml. It then ensures that this total carbon credit revenue does not exceed the carbon credit value specified in the model for the given period (PD).

Parameters:

Name Type Description Default
model pyomo object

The Pyomo model instance containing all the decision variables, parameters, and sets used in the model.

required
PD string

The planning period as a string for which the constraint is being calculated eg. '2025'.

required

Returns:

Type Description

Constraint expression (pyomo constraint): enforces the total annualized VOM costs is to be less

than or equal to the model's variable O&M cost allowance for the period PD.

The function uses several model components: - model.supply[PD, H, ABA, TP]: Power supply from different plant types (TP) during each hour (H) and at each location (ABA). - model.windonsout, model.windofsout, model.solarout: Power output from wind (onshore and offshore) and solar units. - ror_hydroout, model.daystoragehydroout, model.monthstoragehydroout: Outputs from run-of-river, daily, and monthly storage hydro units. - model.load_shedding: Power load shedding amount, indicating power that must be cut due to insufficient supply or other issues. - carboncredit_value: Dictionary of carbon credit revenue for different types of energy sources.

Each term in the returned expression is scaled by the represenative day demand scaling factor, representing an annualization of the calculated costs.

Source code in COPPER.py
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
def carboncredit_revenue_constraint(model, PD):
    """
    Defines a constraint on the carbon credit revenue for various types of power generation 
    (thermal, wind, solar, and hydro) during each economic dispatch period.

    This function calculates the total carbon credit revenye for all power plant types across all hours and locations, scaled to an annual
    cost by the representative day demand scaling factor. Representative days are set in config.toml. It 
    then ensures that this total carbon credit revenue does not exceed the carbon credit value specified in 
    the model for the given period (`PD`).

    Parameters:
        model (pyomo object): The Pyomo model instance containing all the decision variables, parameters, and sets used in the model.
        PD (string): The planning period as a string for which the constraint is being calculated eg. '2025'.

    Returns:
        Constraint expression (pyomo constraint): enforces the total annualized VOM costs is to be less 
        than or equal to the model's variable O&M cost allowance for the period `PD`.

    The function uses several model components:
    - `model.supply[PD, H, ABA, TP]`: Power supply from different plant types (TP) during each hour (H) and at each 
      location (ABA).
    - `model.windonsout`, `model.windofsout`, `model.solarout`: Power output from wind (onshore and offshore) and solar 
      units.
    - `ror_hydroout`, `model.daystoragehydroout`, `model.monthstoragehydroout`: Outputs from run-of-river, daily, and 
      monthly storage hydro units.
    - `model.load_shedding`: Power load shedding amount, indicating power that must be cut due to insufficient supply 
      or other issues.
    - `carboncredit_value`: Dictionary of carbon credit revenue for different types of energy sources.

    Each term in the returned expression is scaled by the represenative day demand scaling factor, 
    representing an annualization of the calculated costs.
    """

    if carbon_credits_on:
        thermal_revenue = quicksum(model.supply[PD, H, ABA, TP, FT] * carboncredit_value[PD + '.' + TP + '.' + FT + '.' + ABA] 
                        for H in h for ABA in aba for TP in tplants for FT in model.ftype if FT in 
                        useful_fuels[TP].split('.')) / repday_scaling
        nonthermal_revenue = quicksum(model.windonsout[PD, H, ABA] * carboncredit_value[PD + '.wind_ons.wind.' + ABA] 
                        + model.windofsout[PD, H, ABA] * carboncredit_value[PD + '.wind_ofs.wind.' + ABA] 
                        + model.solarout[PD, H, ABA] * carboncredit_value[PD + '.solar.sunlight.' + ABA] 
                        + ror_hydroout[PD + '.' + str(H) + '.' + ABA] * carboncredit_value[PD + '.hydro_run.water.' + ABA] 
                        + model.daystoragehydroout[PD, H, ABA] * carboncredit_value[PD + '.hydro_daily.water.' + ABA] 
                        + model.monthstoragehydroout[PD, H, ABA] * carboncredit_value[PD + '.hydro_monthly.water.' + ABA]  for H in h for ABA in
                        aba) / repday_scaling

        return model.carboncredit_revenue[PD] <= (thermal_revenue + nonthermal_revenue)
    else:
        return model.carboncredit_revenue[PD] == 0

day_onetime(model, PD, HR_DAY)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
HR_DAY

The identifier for the day-storage hydro facility.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that once the day-storage hydro facility's renewal is activated

(represented by a binary variable being set to 1), it cannot be deactivated (set back to 0) in any subsequent

planning period.

Details
  • This constraint is applied to the binary variable (model.day_renewal_binary) associated with the renewal of day-storage hydro facilities.
  • The constraint ensures that the binary status in the current planning period (PD) must be greater than or equal to its status in the previous period, effectively mandating a non-decreasing behavior. This ensures that once a facility is marked for renewal (activated), the process cannot be reversed.
  • It is particularly important in energy models where commitments to renewals are irreversible due to regulatory or policy requirements.
Source code in COPPER.py
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
def day_onetime(model, PD, HR_DAY):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        HR_DAY: The identifier for the day-storage hydro facility.

    Returns:
        A Pyomo Constraint expression: Ensures that once the day-storage hydro facility's renewal is activated 
        (represented by a binary variable being set to 1), it cannot be deactivated (set back to 0) in any subsequent 
        planning period.

    Details:
        - This constraint is applied to the binary variable (`model.day_renewal_binary`) associated with the renewal 
        of day-storage hydro facilities.
        - The constraint ensures that the binary status in the current planning period (`PD`) must be greater than 
        or equal to its status in the previous period, effectively mandating a non-decreasing behavior. This ensures 
        that once a facility is marked for renewal (activated), the process cannot be reversed.
        - It is particularly important in energy models where commitments to renewals are irreversible due to 
        regulatory or policy requirements.
    """
    return (model.day_renewal_binary[PD, HR_DAY]
            >= model.day_renewal_binary[pds[pds.index(PD) - 1], HR_DAY])

demsup(model, PD, H, ABA)

Constrains the total energy supply within a specific balancing area during a designated hour and planning period to meet the total demand, factoring in all energy generation types, storage operations, and renewable energy renewals.

Parameters:

Name Type Description Default
model Pyomo model

instance containing decision variables, parameters, and sets relevant to the energy model.

required
PD str

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
H str

The specific hour within the planning period for which the supply and demand balance is evaluated.

required
ABA str

The balancing area as a string, indicating where the energy supply and demand balance is being considered.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the total energy supply from all sources including thermal plants,

wind, solar, hydro, storage, and any renewable energy renewals meets or exceeds the demand, including transmission

losses and potential load shedding within the area.

Details
  • The function aggregates energy supply from various sources:
  • Conventional thermal plant supply (TP_supply).
  • Wind and solar outputs (wind_solar_supply).
  • Hydro outputs from different hydro technologies (hydro_supply).
  • Net storage output (storage_supply).
  • Renewable energy supply from renewable sources under development (renewal_supply).
  • It accounts for adjustments like load shedding and transmission losses to ensure a realistic operational balance.
  • The demand is adjusted for cross-border exchanges (demand_us) and includes external power flows considering transmission losses between areas.

Note: Ensure that all necessary parameters such as demand_df, demand_us demand/us_demand.csv, transmap extant_transmission.csv, and transmission loss coefficients are properly set up and populated with accurate data in config.toml before using this function in a model.

Source code in COPPER.py
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
def demsup(model, PD, H, ABA):
    """
    Constrains the total energy supply within a specific balancing area during a designated hour and planning period to 
    meet the total demand, factoring in all energy generation types, storage operations, and renewable energy
    renewals.

    Parameters:
        model (Pyomo model): instance containing decision variables, parameters, and sets relevant to the energy model.
        PD (str): The planning period as a string (e.g., '2025') during which the constraint is applied.
        H (str): The specific hour within the planning period for which the supply and demand balance is evaluated.
        ABA (str): The balancing area as a string, indicating where the energy supply and demand balance is being considered.

    Returns:
        A Pyomo Constraint expression: Ensures that the total energy supply from all sources including thermal plants, 
        wind, solar, hydro, storage, and any renewable energy renewals meets or exceeds the demand, including transmission 
        losses and potential load shedding within the area.

    Details:
        - The function aggregates energy supply from various sources:
          * Conventional thermal plant supply (`TP_supply`).
          * Wind and solar outputs (`wind_solar_supply`).
          * Hydro outputs from different hydro technologies (`hydro_supply`).
          * Net storage output (`storage_supply`).
          * Renewable energy supply from renewable sources under development (`renewal_supply`).
        - It accounts for adjustments like load shedding and transmission losses to ensure a realistic operational 
          balance.
        - The demand is adjusted for cross-border exchanges (`demand_us`) and includes external power flows considering 
          transmission losses between areas.
    Note:
        Ensure that all necessary parameters such as `demand_df`, `demand_us` demand/us_demand.csv, `transmap` extant_transmission.csv, and transmission loss 
        coefficients are properly set up and populated with accurate data in config.toml before using this function in a model.
    """
    TP_supply = quicksum(model.supply[PD, H, ABA, TP, FT] for TP in tplants
                         for FT in model.ftype if FT in useful_fuels[TP].split('.'))
    wind_solar_supply = model.windonsout[PD, H, ABA] + model.windofsout[PD, H, ABA] + model.solarout[PD, H, ABA]
    hydro_supply = ror_hydroout[PD + '.' + str(H) + '.' + ABA] + model.daystoragehydroout[PD, H, ABA] + \
                   model.monthstoragehydroout[PD, H, ABA]
    storage_supply = quicksum(model.storageout[PD, ST, H, ABA] - model.storagein[PD, ST, H, ABA] for ST in st)

    trans_exports = quicksum(model.transmission[PD, H, ABA, ABBA] for ABBA in aba if ABA + '.' + ABBA in transmap)
    trans_imports = quicksum(model.transmission[PD, H, ABBA, ABA] * (1 - transloss[ABBA + '.' + ABA]) for ABBA in aba if
                             ABBA + '.' + ABA in transmap)

    renewal_supply = 0
    if hydro_development:
        renewal_supply = quicksum(
            ror_renewalout[str(H) + '.' + HR_ROR] * model.ror_renewal_binary[PD, HR_ROR] for HR_ROR in hr_ror if
            ABA == hr_ror_location[HR_ROR]) \
                         + quicksum(
            model.dayrenewalout[PD, H, HR_DAY] for HR_DAY in hr_day if ABA == hr_day_location[HR_DAY]) \
                         + quicksum(
            model.monthrenewalout[PD, H, HR_MO] for HR_MO in hr_mo if ABA == hr_month_location[HR_MO])
    return TP_supply + wind_solar_supply + hydro_supply + storage_supply + renewal_supply + trans_imports == \
        demand_df.loc[
            (PD, H), ABA] + quicksum(demand_us[ABA + '.' + str(H)] for i in aux if ABA + '.' + str(H) in demand_us) - \
        model.load_shedding[PD, H, ABA] + trans_exports

export_duals(model, folder_name, duals_of_interest)

This function saves the model duals to a CSV file Parameters: target folder where to save the file; list of variables for which to save the duals

Source code in phases/postprocessingtools.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def export_duals(model, folder_name, duals_of_interest):
    """
    This function saves the model duals to a CSV file
    Parameters: target folder where to save the file; list of variables for which to save the duals 
    """
    is_bin_or_int = []
    for c in model.component_objects(Var):
        if bool(c._data): # Check that variable has data
            if c.is_indexed():
                first_key = next(c.keys())
                first_val = c[first_key]
                prohibits_duals = (
                    first_val.domain is PositiveIntegers or first_val.domain is Binary
                )
            else: 
                prohibits_duals = (c.domain is PositiveIntegers or c.domain is Binary)
        else:
            print(f"WARNING: Variable {c.name} has no data and will not be checked for duals.")
            #prohibits_duals = True
        is_bin_or_int.append(prohibits_duals)
    export_duals = not any(is_bin_or_int)

    if export_duals:
        print("No binary or integer variables found, exporting duals.")
        with open(f"{folder_name}/duals.csv", "w+") as f:
            f.write("eq,year,hour,node,dual_price\n")
            for c in model.component_objects(Constraint, active=True):
                # export only the marginals of constraints in that list
                if str(c) in duals_of_interest:
                    for index in c:
                        year_hours_node = ",".join(str(x) for x in index)
                        try:
                            f.write(f"{c},{year_hours_node},{model.dual[c[index]]}\n")
                        except KeyError:
                            f.write(f"Missing dual for index {index}\n")
    else:
        print(
            "Model has binary or integer variables. Not exporting dual prices of demand and supply."
        )

fom_cost_constraint(model, PD)

create constraints that ensure the total fixed operation and maintenance (FOM) costs across all energy generation and transmission types do not exceed the FOM cost limits set in the model for each period.

Parameters:

Name Type Description Default
model Object

The optimization model.

required
PD

The specific period.

required

Returns:

Name Type Description
object

pyomo.core.expr.relational_expr.InequalityExpression

Source code in COPPER.py
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
def fom_cost_constraint(model, PD):
    """
    create constraints that ensure the total fixed operation and maintenance (FOM) costs across all energy generation and transmission
    types do not exceed the FOM cost limits set in the model for each period.

    Parameters:
        model (Object): The optimization model.
        PD: The specific period.

    Returns:
        object: pyomo.core.expr.relational_expr.InequalityExpression
    """
    # creates constraints for Fixed Operation and Maintenance costs for all plants existing and built by COPPER
    ind = pds.index(PD)
    wind_ons_key = 'wind_ons'
    wind_ofs_key = 'wind_ofs'
    solar_key = 'solar'

    thermal_sum = quicksum(
        (extant_thermal[pds[0] + '.' + ABA + '.' + TP] + quicksum(
            model.capacity_therm[PDD, ABA, TP] - model.retire_therm[PDD, ABA, TP] for PDD in pds[:ind + 1])) *
        fixed_o_m[TP]
        for ABA in model.aba for TP in model.tplants)

    wind_solar_sum = quicksum(
        (model.capacity_wind_ons[PDD, GL] + model.capacity_wind_ons_recon[PDD, GL]) * fixed_o_m[wind_ons_key]
        + (model.capacity_wind_ofs[PDD, GL] + model.capacity_wind_ofs_recon[PDD, GL]) * fixed_o_m[wind_ofs_key]
        + (model.capacity_solar[PDD, GL] + model.capacity_solar_recon[PDD, GL]) * fixed_o_m[solar_key] for PDD in
        pds[:ind + 1] for GL in model.gl)

    extant_wind_solar_sum = quicksum(
        extant_wind_solar[ind][str(GL) + '.' + key] * fixed_o_m[key] for GL in model.gl for key in
        [wind_ons_key, wind_ofs_key, solar_key]
        if str(GL) + '.' + key in extant_wind_solar[0])

    transmission_sum = quicksum(
        model.capacity_transmission[PDD, ABA, ABBA] * trans_o_m * distance[ABA + '.' + ABBA] for 
        PDD in pds[:ind + 1] for ABA in model.aba for ABBA in model.aba if ABA + '.' + ABBA in transmap)

    extant_transmission_sum = quicksum(
        extant_transmission[ind][ABA + '.' + ABBA] * trans_o_m * distance[ABA + '.' + ABBA] for 
        ABA in model.aba for ABBA in model.aba if ABA + '.' + ABBA in extant_transmission[ind])

    hydro_sum = quicksum(ror_hydro_capacity[PD + '.' + ABA] * fixed_o_m['hydro_run']
                         + day_hydro_capacity[PD + '.' + ABA] * fixed_o_m['hydro_daily']
                         + month_hydro_capacity[PD + '.' + ABA] * fixed_o_m['hydro_monthly'] for ABA in model.aba)

    summed_fom = thermal_sum + wind_solar_sum + extant_wind_solar_sum + transmission_sum + extant_transmission_sum + hydro_sum

    return model.fixOMcost[PD] >= summed_fom

fuel_cost_constraint(model, PD)

Defines a constraint for the annualized fuel cost of thermal generators during the economic dispatch for a specified planning period (PD). This constraint ensures that the model's fuel cost for the period does not fall below the computed cost based on the thermal plant supplies.

The function multiplies the supply of each thermal plant by its fuel cost, aggregates these products over all relevant periods, hours, and balancing areas, and scales the total to an annual basis by the number of running days.

Parameters:

Name Type Description Default
model

Pyomo model instance containing all necessary decision variables, parameters, and sets.

required
PD

Planning period as a string (e.g., '2025') for which the constraint is applied.

required

Returns:

Type Description

Pyomo Constraint: expression that bounds the fuel cost for the period PD from below by the total

annualized fuel cost calculated from the thermal supply multiplied by their respective fuel costs and normalized

over the number of run days.

Source code in COPPER.py
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
def fuel_cost_constraint(model, PD):
    """
    Defines a constraint for the annualized fuel cost of thermal generators during the economic dispatch for a 
    specified planning period (PD). This constraint ensures that the model's fuel cost for the period does not fall 
    below the computed cost based on the thermal plant supplies.

    The function multiplies the supply of each thermal plant by its fuel cost, aggregates these products over all 
    relevant periods, hours, and balancing areas, and scales the total to an annual basis by the number of running days.

    Parameters:
        model: Pyomo model instance containing all necessary decision variables, parameters, and sets.
        PD: Planning period as a string (e.g., '2025') for which the constraint is applied.

    Returns:
        Pyomo Constraint: expression that bounds the fuel cost for the period PD from below by the total 
        annualized fuel cost calculated from the thermal supply multiplied by their respective fuel costs and normalized 
        over the number of run days.
    """
    return model.fuel_cost[PD] >= quicksum(
        model.supply[PD, H, ABA, TP, FT] * fuelcost[PD + '.' + TP + '.' + FT + '.' + ABA] for H in h for ABA in aba for
        TP in
        tplants for FT in model.ftype if FT in useful_fuels[TP].split('.')) / repday_scaling

hydro_dayminflow(model, PD, H, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
H

The hour within the planning period for which the minimum flow constraint is relevant.

required
ABA

The balancing area as a string, indicating where the day-storage hydro facility is located.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the hourly output from day-storage hydroelectric facilities

in the specified balancing area (ABA) during the specified hour (H) of the planning period (PD) does not

fall below the historical minimum flow requirement.

Details
  • This function enforces a minimum flow output for day-storage hydro facilities to ensure ecological and operational stability.
  • The minimum flow requirement (day_minflow) is specified for each planning period and balancing area, reflecting regulatory or ecological standards that must be maintained.
Source code in COPPER.py
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
def hydro_dayminflow(model, PD, H, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        H: The hour within the planning period for which the minimum flow constraint is relevant.
        ABA: The balancing area as a string, indicating where the day-storage hydro facility is located.

    Returns:
        A Pyomo Constraint expression: Ensures that the hourly output from day-storage hydroelectric facilities 
        in the specified balancing area (ABA) during the specified hour (H) of the planning period (PD) does not 
        fall below the historical minimum flow requirement.

    Details:
        - This function enforces a minimum flow output for day-storage hydro facilities to ensure ecological and 
          operational stability.
        - The minimum flow requirement (`day_minflow`) is specified for each planning period and balancing area, 
          reflecting regulatory or ecological standards that must be maintained.
    """
    return model.daystoragehydroout[PD, H, ABA] >= day_minflow[PD + '.' + ABA]

hydro_dayrenewal(model, PD, D, HR_DAY)

Parameters: model: A Pyomo model instance containing all relevant decision variables, parameters, and sets. PD: The planning period as a string (e.g., '2025') during which the constraint is applied. D: The day within the planning period for which the output from renewable day-storage hydro is constrained. HR_DAY: The identifier for the renewable day-storage hydro facility.

Returns: A Pyomo Constraint expression: Limits the total output from renewable day-storage hydroelectric facilities identified by HR_DAY during the specified day (D) of the planning period (PD) to not exceed the historical daily output capacity.

Details: - This function aggregates the hourly outputs for the specified renewable day-storage hydro facility and ensures that the total does not exceed the historical daily operation limits. - It utilizes a mapping (map_hd) to determine which hours belong to the specified day (D), aiding in the aggregation of hourly outputs to enforce the daily constraint based on historic capacity data (day_renewal_historic).

Source code in COPPER.py
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
def hydro_dayrenewal(model, PD, D, HR_DAY):
    """
    Parameters:
    model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
    PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
    D: The day within the planning period for which the output from renewable day-storage hydro is constrained.
    HR_DAY: The identifier for the renewable day-storage hydro facility.

    Returns:
    A Pyomo Constraint expression: Limits the total output from renewable day-storage hydroelectric facilities 
    identified by HR_DAY during the specified day (D) of the planning period (PD) to not exceed the historical 
    daily output capacity.

    Details:
    - This function aggregates the hourly outputs for the specified renewable day-storage hydro facility and 
      ensures that the total does not exceed the historical daily operation limits.
    - It utilizes a mapping (`map_hd`) to determine which hours belong to the specified day (D), aiding in the 
      aggregation of hourly outputs to enforce the daily constraint based on historic capacity data 
      (`day_renewal_historic`).
    """
    return quicksum(model.dayrenewalout[PD, H, HR_DAY] for H in h if map_hd[H] == D) <= day_renewal_historic[
        str(D) + '.' + HR_DAY]

hydro_daystorage(model, PD, D, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
D

The day within the planning period for which the output from day-storage hydro is constrained.

required
ABA

The balancing area as a string, indicating where the day-storage hydro facility is located.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the total output from day-storage hydroelectric facilities in the

specified balancing area (ABA) during the specified day (D) of the planning period (PD) to not exceed

the historical daily output capacity.

Details
  • This function aggregates the hourly outputs for a specific day-storage hydro facility within a designated balancing area and ensures that the total does not surpass historical operation limits.
  • It utilizes a mapping (map_hd) to associate each hour with its corresponding day within the planning period, facilitating the aggregation of hourly outputs to enforce the daily constraint based on historic capacity data (day_hydro_historic).
Source code in COPPER.py
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
def hydro_daystorage(model, PD, D, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        D: The day within the planning period for which the output from day-storage hydro is constrained.
        ABA: The balancing area as a string, indicating where the day-storage hydro facility is located.

    Returns:
        A Pyomo Constraint expression: Limits the total output from day-storage hydroelectric facilities in the 
        specified balancing area (ABA) during the specified day (D) of the planning period (PD) to not exceed 
        the historical daily output capacity.

    Details:
        - This function aggregates the hourly outputs for a specific day-storage hydro facility within a 
          designated balancing area and ensures that the total does not surpass historical operation limits.
        - It utilizes a mapping (`map_hd`) to associate each hour with its corresponding day within the planning 
          period, facilitating the aggregation of hourly outputs to enforce the daily constraint based on historic 
          capacity data (`day_hydro_historic`).
    """
    return quicksum(model.daystoragehydroout[PD, H, ABA] for H in h if map_hd[H] == D) <= day_hydro_historic[
        PD + '.' + str(D) + '.' + ABA]

hydro_monthcap(model, PD, H, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
H

The hour within the planning period for which the capacity constraint is relevant.

required
ABA

The balancing area as a string, indicating where the month-storage hydro facility is located.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the hourly output from month-storage hydroelectric facilities in the

specified balancing area (ABA) during the specified hour (H) of the planning period (PD) to not exceed

the designated capacity limit.

Details
  • This function ensures that the output from month-storage hydro facilities does not exceed the maximum capacity specified for that facility in the balancing area.
  • The maximum capacity (month_hydro_capacity) is predefined for each planning period and balancing area, providing a limit that aligns with operational and regulatory requirements to ensure the stability and efficiency of hydro facility operations.
Source code in COPPER.py
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
def hydro_monthcap(model, PD, H, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        H: The hour within the planning period for which the capacity constraint is relevant.
        ABA: The balancing area as a string, indicating where the month-storage hydro facility is located.

    Returns:
        A Pyomo Constraint expression: Limits the hourly output from month-storage hydroelectric facilities in the 
        specified balancing area (ABA) during the specified hour (H) of the planning period (PD) to not exceed 
        the designated capacity limit.

    Details:
        - This function ensures that the output from month-storage hydro facilities does not exceed the maximum capacity 
          specified for that facility in the balancing area.
        - The maximum capacity (`month_hydro_capacity`) is predefined for each planning period and balancing area, 
          providing a limit that aligns with operational and regulatory requirements to ensure the stability and 
          efficiency of hydro facility operations.
    """
    return model.monthstoragehydroout[PD, H, ABA] <= month_hydro_capacity[PD + '.' + ABA]

hydro_monthminflow(model, PD, H, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
H

The hour within the planning period for which the minimum flow constraint is relevant.

required
ABA

The balancing area as a string, indicating where the month-storage hydro facility is located.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the hourly output from month-storage hydroelectric facilities

in the specified balancing area (ABA) during the specified hour (H) of the planning period (PD) does not

fall below the historical minimum flow requirement.

Details
  • This function enforces a minimum flow output for month-storage hydro facilities to ensure ecological, operational, and regulatory compliance.
  • The minimum flow requirement (month_minflow) is specified for each planning period and balancing area, reflecting constraints that ensure the stability and sustainability of water use and ecological impacts.
Source code in COPPER.py
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
def hydro_monthminflow(model, PD, H, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        H: The hour within the planning period for which the minimum flow constraint is relevant.
        ABA: The balancing area as a string, indicating where the month-storage hydro facility is located.

    Returns:
        A Pyomo Constraint expression: Ensures that the hourly output from month-storage hydroelectric facilities 
        in the specified balancing area (ABA) during the specified hour (H) of the planning period (PD) does not 
        fall below the historical minimum flow requirement.

    Details:
        - This function enforces a minimum flow output for month-storage hydro facilities to ensure ecological, 
          operational, and regulatory compliance.
        - The minimum flow requirement (`month_minflow`) is specified for each planning period and balancing area, 
          reflecting constraints that ensure the stability and sustainability of water use and ecological impacts.
    """
    return model.monthstoragehydroout[PD, H, ABA] >= month_minflow[PD + '.' + ABA]

hydro_monthrenewal(model, PD, M, HR_MO)

Parameters: model: A Pyomo model instance containing all relevant decision variables, parameters, and sets. PD: The planning period as a string (e.g., '2025') during which the constraint is applied. M: The month within the planning period for which the output from renewable month-storage hydro is constrained. HR_MO: The identifier for the renewable month-storage hydro facility.

Returns: A Pyomo Constraint expression: Limits the total output from renewable month-storage hydroelectric facilities identified by HR_MO during the specified month (M) of the planning period (PD) to not exceed the historical monthly output capacity.

Details: - This function aggregates the hourly outputs for the specified renewable month-storage hydro facility and ensures that the total does not exceed the historical monthly operation limits. - It utilizes a mapping (map_hm) to determine which hours belong to the specified month (M), aiding in the aggregation of hourly outputs to enforce the monthly constraint based on historic capacity data (month_renewal_historic).

Source code in COPPER.py
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
def hydro_monthrenewal(model, PD, M, HR_MO):
    """
    Parameters:
    model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
    PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
    M: The month within the planning period for which the output from renewable month-storage hydro is constrained.
    HR_MO: The identifier for the renewable month-storage hydro facility.

    Returns:
    A Pyomo Constraint expression: Limits the total output from renewable month-storage hydroelectric facilities 
    identified by HR_MO during the specified month (M) of the planning period (PD) to not exceed the historical 
    monthly output capacity.

    Details:
    - This function aggregates the hourly outputs for the specified renewable month-storage hydro facility and 
    ensures that the total does not exceed the historical monthly operation limits.
    - It utilizes a mapping (`map_hm`) to determine which hours belong to the specified month (M), aiding in the 
    aggregation of hourly outputs to enforce the monthly constraint based on historic capacity data 
    (`month_renewal_historic`).
    """
    return quicksum(model.monthrenewalout[PD, H, HR_MO] for H in h if map_hm[H] == M) <= month_renewal_historic[
        str(M) + '.' + HR_MO]

hydro_monthstorage(model, PD, M, ABA)

Parameters: model: A Pyomo model instance containing all relevant decision variables, parameters, and sets. PD: The planning period as a string (e.g., '2025') during which the constraint is applied. M: The month within the planning period for which the output from month-storage hydro is constrained. ABA: The balancing area as a string, indicating where the month-storage hydro facility is located.

Returns: A Pyomo Constraint expression: Limits the total output from month-storage hydroelectric facilities in the specified balancing area (ABA) during the specified month (M) of the planning period (PD) to not exceed the historical monthly output capacity.

Details: - This function aggregates the hourly outputs for a specific month-storage hydro facility within a designated balancing area and ensures that the total does not surpass historical operation limits. - It utilizes a mapping (map_hm) to associate each hour with its corresponding month within the planning period, facilitating the aggregation of hourly outputs to enforce the monthly constraint based on historic capacity data (month_hydro_historic).

Source code in COPPER.py
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
def hydro_monthstorage(model, PD, M, ABA):
    """
    Parameters:
    model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
    PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
    M: The month within the planning period for which the output from month-storage hydro is constrained.
    ABA: The balancing area as a string, indicating where the month-storage hydro facility is located.

    Returns:
    A Pyomo Constraint expression: Limits the total output from month-storage hydroelectric facilities in the 
    specified balancing area (ABA) during the specified month (M) of the planning period (PD) to not exceed 
    the historical monthly output capacity.

    Details:
    - This function aggregates the hourly outputs for a specific month-storage hydro facility within a designated 
      balancing area and ensures that the total does not surpass historical operation limits.
    - It utilizes a mapping (`map_hm`) to associate each hour with its corresponding month within the planning 
      period, facilitating the aggregation of hourly outputs to enforce the monthly constraint based on historic 
      capacity data (`month_hydro_historic`).
    """
    return quicksum(model.monthstoragehydroout[PD, H, ABA] for H in h if map_hm[H] == M) <= month_hydro_historic[
        PD + '.' + str(M) + '.' + ABA]

hydro_renewal_constraint(model, PD)

Defines a constraint on the capital costs associated with the renewal of hydro power generation capacities within the model for a given period (PD). The constraint adjusts the cost calculations based on whether investment tax credits are applicable, and differentiates between types of hydro technologies: run-of-river (HR_ROR), daily storage (HR_DAY), monthly storage (HR_MO), and pumped hydro (HR_PUMP), depending on their operation status and the year.

Parameters:

Name Type Description Default
model Pyomo Object

The Pyomo model instance that contains all the decision variables, parameters, and sets used in the model.

required
PD str

The planning period as a string for which the constraint is being calculated eg. '2025'.

required

Returns:

Type Description

Constraint expression (pyomo constraint): sets the hydro renewal capital cost for the period PD.

If hydro development is considered hydro_development is set to true in config.tom, the cost is calculated based on the renewal needs

and the potential application of tax credits. If not, the cost is set to zero.

Details
  • The cost calculations consider if the plant operations are continuous (storage_continous) and apply different costs for different periods (2025, 2030 vs. 2035 to 2050).
  • The costs are calculated by summing up the product of the renewal binary decision variables for each plant type and their respective costs, optionally adjusted by a tax factor (ITC_factor, indexed by balancing area).
  • If flag_itc_clean_tech or flag_itc_clean_elec is true in config.toml, the costs for certain periods are adjusted by their corresponding ITC factor.
  • If storage_continous is false in config.toml, additional costs related to pumped hydro renewal are included.

The constraint is modeled to ensure that model.hydrorenewalccost[PD] is at least the calculated sum of renewal costs, enforcing budget adherence for hydro renewals within the specified period.

Source code in COPPER.py
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
def hydro_renewal_constraint(model, PD):
    """
    Defines a constraint on the capital costs associated with the renewal of hydro power generation capacities 
    within the model for a given period (PD). The constraint adjusts the cost calculations based on whether investment
    tax credits are applicable, and differentiates between types of hydro technologies: run-of-river (HR_ROR),
    daily storage (HR_DAY), monthly storage (HR_MO), and pumped hydro (HR_PUMP), depending on their operation status
    and the year.

    Parameters:
        model (Pyomo Object): The Pyomo model instance that contains all the decision variables, parameters, and sets used in the model.
        PD (str): The planning period as a string for which the constraint is being calculated eg. '2025'.

    Returns:
        Constraint expression (pyomo constraint): sets the hydro renewal capital cost for the period PD.
        If hydro development is considered `hydro_development` is set to true in config.tom, the cost is calculated based on the renewal needs
        and the potential application of tax credits. If not, the cost is set to zero.

    Details:
        - The cost calculations consider if the plant operations are continuous (`storage_continous`) and apply
          different costs for different periods (2025, 2030 vs. 2035 to 2050).
        - The costs are calculated by summing up the product of the renewal binary decision variables for each plant type
          and their respective costs, optionally adjusted by a tax factor (`ITC_factor`, indexed by balancing area).
        - If `flag_itc_clean_tech` or `flag_itc_clean_elec` is true in config.toml, the costs for certain periods are adjusted 
          by their corresponding ITC factor.
        - If `storage_continous` is false in config.toml, additional costs related to pumped hydro renewal are included.

        The constraint is modeled to ensure that `model.hydrorenewalccost[PD]` is at least the calculated sum of renewal
        costs, enforcing budget adherence for hydro renewals within the specified period.
    """
    if hydro_development:
        ind = pds.index(PD)
        if flag_itc_clean_tech or flag_itc_clean_elec:
            cost_sum = quicksum(
                cost_ror_renewal[HR_ROR] * subsidy_factor[f'{hr_ror_location[HR_ROR]}.hydro_run'] * model.ror_renewal_binary[PDD, HR_ROR] 
                for PDD in pds[:ind + 1] for HR_ROR in hr_ror if PDD in ['2025', '2030']) \
                       + quicksum(
                cost_ror_renewal[HR_ROR] * model.ror_renewal_binary[PDD, HR_ROR] for PDD in pds[:ind + 1] for HR_ROR in
                hr_ror if PDD in ['2035', '2040', '2045', '2050']) \
                       + quicksum(
                cost_day_renewal[HR_DAY] * subsidy_factor[f'{hr_day_location[HR_DAY]}.hydro_daily'] * model.day_renewal_binary[PDD, HR_DAY] 
                for PDD in pds[:ind + 1] for HR_DAY in hr_day if PDD in ['2025', '2030']) \
                       + quicksum(
                cost_day_renewal[HR_DAY] * model.day_renewal_binary[PDD, HR_DAY] for PDD in pds[:ind + 1] for HR_DAY in
                hr_day if PDD in ['2035', '2040', '2045', '2050']) \
                       + quicksum(
                cost_month_renewal[HR_MO] * subsidy_factor[f'{hr_month_location[HR_MO]}.hydro_monthly'] * model.month_renewal_binary[PDD, HR_MO]
                for PDD in pds[:ind + 1] for HR_MO in hr_mo if PDD in ['2025', '2030']) \
                       + quicksum(
                cost_month_renewal[HR_MO] * model.month_renewal_binary[PDD, HR_MO] for PDD in pds[:ind + 1] for HR_MO in
                hr_mo if PDD in ['2035', '2040', '2045', '2050'])
            if not storage_continous:
                cost_sum += quicksum(
                    cost_pump_renewal[HR_PUMP] * subsidy_factor[f'{hr_pump_location[HR_PUMP]}.storage_PH'] * model.pumphydro[PDD, HR_PUMP] 
                    for PDD in pds[:ind + 1] for HR_PUMP in hr_pump if PDD in ['2025', '2030']) \
                            + quicksum(
                    cost_pump_renewal[HR_PUMP] * model.pumphydro[PDD, HR_PUMP] for PDD in pds[:ind + 1] for HR_PUMP in
                    hr_pump if PDD in ['2035', '2040', '2045', '2050'])
        else:
            cost_sum = quicksum(
                cost_ror_renewal[HR_ROR] * model.ror_renewal_binary[PDD, HR_ROR] for PDD in pds[:ind + 1] for HR_ROR in
                hr_ror) \
                       + quicksum(
                cost_day_renewal[HR_DAY] * model.day_renewal_binary[PDD, HR_DAY] for PDD in pds[:ind + 1] for HR_DAY in
                hr_day) \
                       + quicksum(
                cost_month_renewal[HR_MO] * model.month_renewal_binary[PDD, HR_MO] for PDD in pds[:ind + 1] for HR_MO in
                hr_mo)
            if not storage_continous:
                cost_sum += quicksum(
                    cost_pump_renewal[HR_PUMP] * model.pumphydro[PDD, HR_PUMP] for PDD in pds[:ind + 1] for HR_PUMP in
                    hr_pump)

        return model.hydrorenewalccost[PD] >= cost_sum
    else:
        return model.hydrorenewalccost[PD] == 0

hydro_renewal_fom_constraint(model, PD)

Defines a constraint for the fixed operational and maintenance (FO&M) costs associated with hydro power renewal for the specified planning period (PD) within the Pyomo model. The function calculates the FO&M costs based on fixed cost components for different types of hydro technologies (run-of-river, daily storage, monthly storage, and potentially pumped storage if storage_continuous is set to true in config.toml). This cost is annualized by scaling with the number of run days in a year.

Parameters:

Name Type Description Default
model object

A Pyomo model instance that includes all the decision variables, parameters, and sets used in the model.

required
PD str

The planning period as a string for which the constraint is being calculated (e.g., '2025').

required

Returns:

Type Description

return model.hydrorenewalfomcost[PD] >= fom_cost_sum (constraint): A Pyomo Constraint expression that ensures the FO&M

costs for hydro renewal during the planning period PD are covered. If hydro development is not considered (hydro_development

is False), the constraint sets the FO&M costs to zero.

Details
  • The constraint is calculated only if hydro development is considered (hydro_development is set to True in config.toml).
  • The costs come from the Fixed O&M costs for run-of-river, daily, and monthly hydro facilities, applied to their respective capacities.
  • Costs for each type of facility are summed using quicksum, considering each period up to the current one.
  • If storage is not continuous (storage_continous is False), additional costs for pumped hydro facilities are included.
  • Annualization is achieved by multiplying variable costs by the representative day demand scaling factor.

The function assumes that the O&M cost for the second economic dispatch should be equal to the first economic dispatch, as noted by JGM.

Source code in COPPER.py
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
def hydro_renewal_fom_constraint(model, PD):
    """
    Defines a constraint for the fixed operational and maintenance (FO&M) costs associated with hydro power renewal 
    for the specified planning period (PD) within the Pyomo model. The function calculates the FO&M costs based 
    on fixed cost components for different types of hydro technologies (run-of-river, daily storage,
    monthly storage, and potentially pumped storage if storage_continuous is set to true in config.toml). This cost 
    is annualized by scaling with the number of run days in a year.

    Parameters:
        model (object): A Pyomo model instance that includes all the decision variables, parameters, and sets used in the model.
        PD (str): The planning period as a string for which the constraint is being calculated (e.g., '2025').

    Returns:
        return model.hydrorenewalfomcost[PD] >= fom_cost_sum (constraint): A Pyomo Constraint expression that ensures the FO&M 
        costs for hydro renewal during the planning period PD are covered. If hydro development is not considered (hydro_development 
        is False), the constraint sets the FO&M costs to zero.

    Details:
        - The constraint is calculated only if hydro development is considered (`hydro_development` is set to True in config.toml).
        - The costs come from the Fixed O&M costs for run-of-river, daily, and monthly hydro facilities, applied to their respective 
          capacities.
        - Costs for each type of facility are summed using quicksum, considering each period up to the current one.
        - If storage is not continuous (`storage_continous` is False), additional costs for pumped hydro facilities are included.
        - Annualization is achieved by multiplying variable costs by the representative day demand scaling factor.

        The function assumes that the O&M cost for the second economic dispatch should be equal to the first economic dispatch,
        as noted by JGM.
    """

    if hydro_development:
        fom_cost_sum = quicksum(
            capacity_ror_renewal[HR_ROR] * model.ror_renewal_binary[PD, HR_ROR] * fixed_o_m_renewal[HR_ROR] for HR_ROR
            in hr_ror) \
                       + quicksum(
            capacity_day_renewal[HR_DAY] * model.day_renewal_binary[PD, HR_DAY] * fixed_o_m_renewal[HR_DAY] for HR_DAY
            in hr_day) \
                       + quicksum(
            capacity_month_renewal[HR_MO] * model.month_renewal_binary[PD, HR_MO] * fixed_o_m_renewal[HR_MO] for HR_MO
            in hr_mo)

        if not storage_continous:
            fom_cost_sum += quicksum(
                capacity_pump_renewal[HR_PUMP] * model.pumphydro[PD, HR_PUMP] * fixed_o_m_renewal[HR_PUMP] for HR_PUMP
                in hr_pump)
            ##O&M cost for hydro renewal for second economic dispatch, should be equal to first economic dispatch -JGM

        return model.hydrorenewalfomcost[PD] >= fom_cost_sum
    else:
        return model.hydrorenewalfomcost[PD] == 0

hydro_renewal_vom_constraint(model, PD)

Defines a constraint for the variable operational and maintenance (VO&M) costs associated with hydro power renewal for the specified planning period (PD) within the Pyomo model. The function calculates the VO&M costs based on variable cost components for different types of hydro technologies (run-of-river, daily storage, monthly storage). This cost is annualized by scaling with the number of run days in a year.

Parameters:

Name Type Description Default
model object

A Pyomo model instance that includes all the decision variables, parameters, and sets used in the model.

required
PD str

The planning period as a string for which the constraint is being calculated (e.g., '2025').

required

Returns:

Type Description

return model.hydrorenewalvomcost[PD] >= vom_cost_sum (constraint): A Pyomo Constraint expression that ensures the VO&M

costs for hydro renewal during the planning period PD are covered. If hydro development is not considered (hydro_development

is False), the constraint sets the VO&M costs to zero.

Details
  • The constraint is calculated only if hydro development is considered (hydro_development is set to True in config.toml).
  • It costs come from the Variable O&M costs for for run-of-river, daily, and monthly hydro facilities, scaled by the output and adjusted for the number of days in the period.
  • Costs for each type of facility are summed using quicksum, considering each period up to the current one.
  • Annualization is achieved by multiplying variable costs by the representative day demand scaling factor.
Source code in COPPER.py
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
def hydro_renewal_vom_constraint(model, PD):
    """
    Defines a constraint for the variable operational and maintenance (VO&M) costs associated with hydro power renewal 
    for the specified planning period (PD) within the Pyomo model. The function calculates the VO&M costs based 
    on variable cost components for different types of hydro technologies (run-of-river, daily storage,
    monthly storage). This cost is annualized by scaling with the number of run days in a year.

    Parameters:
        model (object): A Pyomo model instance that includes all the decision variables, parameters, and sets used in the model.
        PD (str): The planning period as a string for which the constraint is being calculated (e.g., '2025').

    Returns:
        return model.hydrorenewalvomcost[PD] >= vom_cost_sum (constraint): A Pyomo Constraint expression that ensures the VO&M 
        costs for hydro renewal during the planning period PD are covered. If hydro development is not considered (hydro_development 
        is False), the constraint sets the VO&M costs to zero.

    Details:
        - The constraint is calculated only if hydro development is considered (`hydro_development` is set to True in config.toml).
        - It costs come from the Variable O&M costs for for run-of-river, daily, and monthly hydro facilities, scaled by the output 
          and adjusted for the number of days in the period.
        - Costs for each type of facility are summed using quicksum, considering each period up to the current one.
        - Annualization is achieved by multiplying variable costs by the representative day demand scaling factor.
    """

    if hydro_development:
        vom_cost_sum = quicksum(
            ror_renewalout[str(H) + '.' + HR_ROR] * model.ror_renewal_binary[PD, HR_ROR] * variable_o_m_renewal[HR_ROR]
            for H in h for HR_ROR in hr_ror) / repday_scaling \
                       + quicksum(
            model.dayrenewalout[PD, H, HR_DAY] * variable_o_m_renewal[HR_DAY] for H in h for HR_DAY in
            hr_day) / repday_scaling \
                       + quicksum(
            model.monthrenewalout[PD, H, HR_MO] * variable_o_m_renewal[HR_MO] for H in h for HR_MO in
            hr_mo) / repday_scaling

        return model.hydrorenewalvomcost[PD] >= vom_cost_sum
    else:
        return model.hydrorenewalvomcost[PD] == 0

lifetime(model, PD, ABA, TP)

Enforces retirement of thermal plants in the model when they reach the end of their operational lifetime. This function ensures that the total retired capacity up to and including a given planning period (PD) does not exceed the initial extant capacity unless adjustments are made for units under construction, which might otherwise cause infeasibility in the model due to premature retirements.

Parameters:

Name Type Description Default
model Pyomo model

instance containing all relevant decision variables, parameters, and sets.

required
PD str

The planning period as a string (e.g., '2025') during which the constraint is evaluated.

required
ABA str

The balancing area as a string, indicating the location of the thermal plant.

required
TP str

The type of the thermal plant as a string.

required

Returns:

Type Description

A Pyomo Constraint expression: This constraint ensures that the cumulative retirement for the thermal

plant type TP in the balancing area ABA does not exceed its extant capacity from the start of the model,

adjusted for any significant changes due to new units becoming operational.

Details
  • The function first checks if the plant type in question has increased in capacity from one period to the next, indicative of new units coming online. If there's an increase, a high threshold (100,000) is set to prevent the constraint from forcing retirements that would render the model infeasible.
  • The function subtracts the cumulative retirements from the initial capacity (at the start of the model) and ensures this does not exceed the adjusted extant capacity (ex_thermal), which accounts for new units.
Note

It is critical to correctly set up the initial capacities in the extant_capacity.csv input sheet to ensure accurate implementation of this function.

Source code in COPPER.py
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
def lifetime(model, PD, ABA, TP):
    """
    Enforces retirement of thermal plants in the model when they reach the end of their operational lifetime. 
    This function ensures that the total retired capacity up to and including a given planning period (PD) does not 
    exceed the initial extant capacity unless adjustments are made for units under construction, which might otherwise 
    cause infeasibility in the model due to premature retirements.

    Parameters:
        model (Pyomo model): instance containing all relevant decision variables, parameters, and sets.
        PD (str): The planning period as a string (e.g., '2025') during which the constraint is evaluated.
        ABA (str): The balancing area as a string, indicating the location of the thermal plant.
        TP (str): The type of the thermal plant as a string.

    Returns:
        A Pyomo Constraint expression: This constraint ensures that the cumulative retirement for the thermal 
        plant type TP in the balancing area ABA does not exceed its extant capacity from the start of the model, 
        adjusted for any significant changes due to new units becoming operational.

    Details:
        - The function first checks if the plant type in question has increased in capacity from one period to the 
          next, indicative of new units coming online. If there's an increase, a high threshold (100,000) is set 
          to prevent the constraint from forcing retirements that would render the model infeasible.
        - The function subtracts the cumulative retirements from the initial capacity (at the start of the model)
          and ensures this does not exceed the adjusted extant capacity (ex_thermal), which accounts for new units.

    Note:
        It is critical to correctly set up the initial capacities in the extant_capacity.csv input sheet to ensure 
        accurate implementation of this function.
    """
    ind = pds.index(PD)
    ex_thermal = extant_thermal[PD + '.' + ABA + '.' + TP]

    #######for under construction units, this will prevent infeasibility
    if ind >= 1 and extant_thermal[PD + '.' + ABA + '.' + TP] - extant_thermal[pds[ind - 1] + '.' + ABA + '.' + TP] > 0:
        ex_thermal = 100000
    return extant_thermal[pds[0] + '.' + ABA + '.' + TP] - quicksum(
        model.retire_therm[PDD, ABA, TP] for PDD in pds[:ind + 1]) <= ex_thermal

limited_new_thermal(model, GT)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
GT

The identifier for the type of new thermal generation technology (e.g., 'gas', 'coal').

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the total installed capacity of the specified new thermal generation

technology across all balancing areas does not exceed a predefined limit (limited_new_thermal_gen[GT]).

Details
  • This function aggregates the capacity of the specified new thermal generation technology across all planning periods and balancing areas, enforcing that this total capacity remains within the set limits.
  • The limit (limited_new_thermal_gen[GT]) is a policy-driven or regulatory cap designed to control the expansion of certain types of thermal power plants, often to meet environmental standards or transition towards less carbon-intensive energy systems. It is set in config.toml value ['Thermal']['limited_new_thermal_gen']
  • Applying this constraint is critical for managing the scale and environmental impact of new thermal power developments, ensuring that they align with broader energy policy goals or compliance with climate action plans.
Source code in COPPER.py
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
def limited_new_thermal(model, GT):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        GT: The identifier for the type of new thermal generation technology (e.g., 'gas', 'coal').

    Returns:
        A Pyomo Constraint expression: Ensures that the total installed capacity of the specified new thermal generation 
        technology across all balancing areas does not exceed a predefined limit (`limited_new_thermal_gen[GT]`).

    Details:
        - This function aggregates the capacity of the specified new thermal generation technology across all planning 
        periods and balancing areas, enforcing that this total capacity remains within the set limits.
        - The limit (`limited_new_thermal_gen[GT]`) is a policy-driven or regulatory cap designed to control the expansion 
        of certain types of thermal power plants, often to meet environmental standards or transition towards less 
        carbon-intensive energy systems. It is set in config.toml value ['Thermal']['limited_new_thermal_gen']
        - Applying this constraint is critical for managing the scale and environmental impact of new thermal power 
        developments, ensuring that they align with broader energy policy goals or compliance with climate action plans.
    """
    return quicksum(model.capacity_therm[PD, ABA, GT] for PD in pds for ABA in aba) <= limited_new_thermal_gen[GT]

loadShedding(model, PD, H, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
H

The hour within the planning period for which the load shedding constraint is relevant.

required
ABA

The balancing area as a string, indicating where the load shedding is being evaluated.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the load shedding in a specified balancing area (ABA) during a given hour

(H) of the planning period (PD) to not exceed the sum of the local demand from demand_df and any additional

demand from the U.S. (demand_us) us_demand.csv input sheet, if applicable.

Details
  • This function determines the maximum permissible load shedding for each balancing area by comparing the planned load shedding to the total demand, which includes local demand plus any cross-border electricity demand from the U.S. linked to the specific hour and balancing area.
  • The constraint ensures that the amount of electricity curtailed during load shedding does not surpass the combined demand, thus maintaining supply and demand balance while considering interconnections with neighboring regions.
  • Load shedding may be considered as a last resort to balance the grid in situations where supply cannot meet demand, and this constraint helps manage the extent to which this measure can be applied.
Source code in COPPER.py
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
def loadShedding(model, PD, H, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        H: The hour within the planning period for which the load shedding constraint is relevant.
        ABA: The balancing area as a string, indicating where the load shedding is being evaluated.

    Returns:
        A Pyomo Constraint expression: Limits the load shedding in a specified balancing area (ABA) during a given hour 
        (H) of the planning period (PD) to not exceed the sum of the local demand from `demand_df` and any additional 
        demand from the U.S. (`demand_us`) us_demand.csv input sheet, if applicable.

    Details:
        - This function determines the maximum permissible load shedding for each balancing area by comparing the planned 
          load shedding to the total demand, which includes local demand plus any cross-border electricity demand from 
          the U.S. linked to the specific hour and balancing area.
        - The constraint ensures that the amount of electricity curtailed during load shedding does not surpass the 
          combined demand, thus maintaining supply and demand balance while considering interconnections with neighboring 
          regions.
        - Load shedding may be considered as a last resort to balance the grid in situations where supply cannot meet 
          demand, and this constraint helps manage the extent to which this measure can be applied.
    """
    aux = [1]
    return model.load_shedding[PD, H, ABA] <= demand_df.loc[(PD, H), ABA] + quicksum(
        demand_us[ABA + '.' + str(H)] for i in aux if ABA + '.' + str(H) in demand_us)

maxannualemissions(model, PD, ABA, TP)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the emissions constraint is applied.

required
ABA

The balancing area as a string, indicating where the fossil fuel thermal plant is located.

required
FTP

The type of fossil fuel thermal plant (e.g., 'coal', 'gas').

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the annual carbon dioxide emissions from a specific type of

fossil fuel thermal power plant (FTP) in the specified balancing area (ABA) do not exceed the emissions

capacity limit, calculated based on the plant's capacity and a maximum emissions limit (max_em_limit).

Details
  • This function calculates the total annual CO2 emissions by multiplying the hourly power supply by the CO2 emission factor for each type of plant, adjusted for the number of run days in a year to provide an annualized value.
  • The allowable emissions are determined by the net capacity of the plant, factoring in any new installations or retirements up to the planning period, and multiplied by the max_em_limit, which is a regulatory limit set in the configuration file (config.toml under 'Regulations' -> 'CER' -> 'max_em_limit').
  • The constraint is critical for compliance with environmental regulations that aim to limit greenhouse gas emissions from thermal power generation, contributing to broader climate action goals.
  • only applied if flag_cer and applies for years hard coded years 2035, 2040 and 2045.
Source code in COPPER.py
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
def maxannualemissions(model, PD, ABA, TP):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the emissions constraint is applied.
        ABA: The balancing area as a string, indicating where the fossil fuel thermal plant is located.
        FTP: The type of fossil fuel thermal plant (e.g., 'coal', 'gas').

    Returns:
        A Pyomo Constraint expression: Ensures that the annual carbon dioxide emissions from a specific type of 
        fossil fuel thermal power plant (FTP) in the specified balancing area (ABA) do not exceed the emissions 
        capacity limit, calculated based on the plant's capacity and a maximum emissions limit (`max_em_limit`).

    Details:
        - This function calculates the total annual CO2 emissions by multiplying the hourly power supply by the 
          CO2 emission factor for each type of plant, adjusted for the number of run days in a year to provide an 
          annualized value.
        - The allowable emissions are determined by the net capacity of the plant, factoring in any new installations 
          or retirements up to the planning period, and multiplied by the `max_em_limit`, which is a regulatory 
          limit set in the configuration file (`config.toml` under 'Regulations' -> 'CER' -> 'max_em_limit').
        - The constraint is critical for compliance with environmental regulations that aim to limit greenhouse gas 
          emissions from thermal power generation, contributing to broader climate action goals.
        - only applied if flag_cer and applies for years hard coded years 2035, 2040 and 2045.
    """

    ind = pds.index(PD)
    return sum(model.supply[PD, H, ABA, TP, FT] * carbondioxide[TP + '.' + FT] for H in h for FT in model.ftype if
               FT in useful_fuels[TP].split('.')) / repday_scaling <= (
            sum(model.capacity_therm[PDD, ABA, TP] - model.retire_therm[PDD, ABA, TP] for PDD in pds[:ind + 1]) +
            extant_thermal[pds[0] + '.' + ABA + '.' + TP]) * max_em_limit

maxannualemissions2050(model, PD, ABA, TP)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the emissions constraint is applied.

required
ABA

The balancing area as a string, indicating where the fossil fuel thermal plant is located.

required
FTP

The type of fossil fuel thermal plant (e.g., 'coal', 'gas').

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the annual carbon dioxide emissions from a specific type of

fossil fuel thermal power plant (FTP) in the specified balancing area (ABA) do not exceed the emissions

capacity limit, calculated based on the plant's capacity and a maximum emissions limit (max_em_limit).

Details
  • This function calculates the total annual CO2 emissions by multiplying the hourly power supply by the CO2 emission factor for each type of plant, adjusted for the number of run days in a year to provide an annualized value.
  • The allowable emissions are determined by the net capacity of the plant, factoring in any new installations or retirements up to the planning period, and multiplied by the max_em_limit, which is a regulatory limit set in the configuration file (config.toml under 'Regulations' -> 'CER' -> 'max_em_limit').
  • The constraint is critical for compliance with environmental regulations that aim to limit greenhouse gas emissions from thermal power generation, contributing to broader climate action goals.
  • only applied if flag_cer and applies for hard coded year 2050.
Source code in COPPER.py
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
def maxannualemissions2050(model, PD, ABA, TP):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the emissions constraint is applied.
        ABA: The balancing area as a string, indicating where the fossil fuel thermal plant is located.
        FTP: The type of fossil fuel thermal plant (e.g., 'coal', 'gas').

    Returns:
        A Pyomo Constraint expression: Ensures that the annual carbon dioxide emissions from a specific type of 
        fossil fuel thermal power plant (FTP) in the specified balancing area (ABA) do not exceed the emissions 
        capacity limit, calculated based on the plant's capacity and a maximum emissions limit (`max_em_limit`).

    Details:
        - This function calculates the total annual CO2 emissions by multiplying the hourly power supply by the 
          CO2 emission factor for each type of plant, adjusted for the number of run days in a year to provide an 
          annualized value.
        - The allowable emissions are determined by the net capacity of the plant, factoring in any new installations 
          or retirements up to the planning period, and multiplied by the `max_em_limit`, which is a regulatory 
          limit set in the configuration file (`config.toml` under 'Regulations' -> 'CER' -> 'max_em_limit').
        - The constraint is critical for compliance with environmental regulations that aim to limit greenhouse gas 
          emissions from thermal power generation, contributing to broader climate action goals.
        - only applied if flag_cer and applies for hard coded year 2050.
    """

    ind = pds.index(PD)
    return sum(model.supply[PD, H, ABA, TP, FT] * carbondioxide[TP + '.' + FT] for H in h for FT in model.ftype if
               FT in useful_fuels[TP].split('.')) / repday_scaling <= (
            sum(model.capacity_therm[PDD, ABA, TP] - model.retire_therm[PDD, ABA, TP] for PDD in pds[:ind + 1]) +
            extant_thermal[pds[0] + '.' + ABA + '.' + TP]) * max_em_limit_2050

maxcapfactor(model, PD, ABA, TP)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
ABA

The balancing area as a string, indicating where the thermal plant is located.

required
TP

The type of thermal plant as a string.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the total generation from a specific type of thermal power plant (TP)

within a balancing area (ABA) during the planning period (PD) to not exceed the product of its maximum

capacity factor, the total hours in the period, and the net capacity of the plant after accounting for new

installations and retirements.

Details
  • This constraint calculates the allowable generation by multiplying the net capacity (including both extant capacity at the start of the planning period and any capacity adjustments due to installations or retirements) by the number of hours in the period and the maximum capacity factor specified for that plant type (max_cap_fact[TP]).
  • The maximum capacity factor, which represents the maximum percentage of time the plant can operate at full capacity over the planning period, is configured in an external data source (generation_type_data.csv).
  • This constraint is critical for ensuring that thermal power plants operate within their technical and operational limits, aiding in reliable energy production and planning while preventing over-utilization of resources.
Source code in COPPER.py
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
def maxcapfactor(model, PD, ABA, TP):
    """
        Parameters:
            model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
            PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
            ABA: The balancing area as a string, indicating where the thermal plant is located.
            TP: The type of thermal plant as a string.

        Returns:
            A Pyomo Constraint expression: Limits the total generation from a specific type of thermal power plant (TP) 
            within a balancing area (ABA) during the planning period (PD) to not exceed the product of its maximum 
            capacity factor, the total hours in the period, and the net capacity of the plant after accounting for new 
            installations and retirements.

        Details:
            - This constraint calculates the allowable generation by multiplying the net capacity (including both extant 
            capacity at the start of the planning period and any capacity adjustments due to installations or retirements) 
            by the number of hours in the period and the maximum capacity factor specified for that plant type (`max_cap_fact[TP]`).
            - The maximum capacity factor, which represents the maximum percentage of time the plant can operate at full 
            capacity over the planning period, is configured in an external data source (`generation_type_data.csv`).
            - This constraint is critical for ensuring that thermal power plants operate within their technical and 
            operational limits, aiding in reliable energy production and planning while preventing over-utilization of 
            resources.
    """
    ind = pds.index(PD)
    return quicksum(
        model.supply[PD, H, ABA, TP, FT] for H in h for FT in model.ftype if FT in useful_fuels[TP].split('.')) <= (
            quicksum(model.capacity_therm[PDD, ABA, TP] - model.retire_therm[PDD, ABA, TP] for PDD in pds[:ind + 1])
            + extant_thermal[pds[0] + '.' + ABA + '.' + TP]) * hours * max_cap_fact[TP]

min_installed_storage(model, ABA)

Parameters: model: A Pyomo model instance containing all relevant decision variables, parameters, and sets. ABA: The balancing area as a string, indicating where the minimum capacity requirements for pumped hydro storage are enforced.

Returns: A Pyomo Constraint expression: Ensures that the total installed capacity of pumped hydro storage in the specified balancing area (ABA) meets or exceeds a predefined lower bound (LB_PHP_installed_limit[ABA]).

Details: - This function aggregates the capacity of all pumped hydro storage facilities within the balancing area, both from the model's variable capacities (model.capacity_storage) across all planning periods and the baseline installed capacity (extant_storage[PD + '.' + ABA + '.' + 'storage_PH']). - The aggregated total is then compared against the minimum installed capacity limit set for the area, ensuring that the region maintains sufficient pumped hydro storage capacity to meet regulatory or operational standards. - This constraint is crucial for maintaining energy storage capacity at levels that support grid stability and compliance with regional energy policies.

Source code in COPPER.py
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
    def min_installed_storage(model, ABA):
        """
        	Parameters:
             model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
             ABA: The balancing area as a string, indicating where the minimum capacity requirements for pumped hydro storage are enforced.

        	Returns:
             A Pyomo Constraint expression: Ensures that the total installed capacity of pumped hydro storage in the specified 
             balancing area (ABA) meets or exceeds a predefined lower bound (`LB_PHP_installed_limit[ABA]`).

        	Details:
             - This function aggregates the capacity of all pumped hydro storage facilities within the balancing area, both from 
             the model's variable capacities (`model.capacity_storage`) across all planning periods and the baseline installed 
             capacity (`extant_storage[PD + '.' + ABA + '.' + 'storage_PH']`).
             - The aggregated total is then compared against the minimum installed capacity limit set for the area, 
             ensuring that the region maintains sufficient pumped hydro storage capacity to meet regulatory or operational 
             standards.
             - This constraint is crucial for maintaining energy storage capacity at levels that support grid stability and 
             compliance with regional energy policies.
        """
        return quicksum(model.capacity_storage[PD, ST, ABA]  
            for PD in pds for ST in st) + quicksum(extant_storage[PD + '.' + ABA + '.' + 'storage_PH']
			for PD in pds) >= PH_storage_limits[ABA]

model_vars_as_dfs(model, debug=False)

This function converts the model variables to Pandas dataframes and returns them as a dictionary Parameters: Pyomo model instance, boolean flag for debugging

Source code in phases/postprocessingtools.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def model_vars_as_dfs(model, debug=False) -> dict[str, pd.DataFrame]:
    """
    This function converts the model variables to Pandas dataframes and returns them as a dictionary
    Parameters: Pyomo model instance, boolean flag for debugging
    """
    df_dict = {}
    # loop over all variables
    for m_var in model.component_objects(Var, active=True):
        # store values in a series
        var_values = pd.Series(m_var.get_values())

        # get names of indices
        subsets = list(m_var.index_set().subsets())
        subset_names = [x.name for x in subsets]

        # print information if desired
        if debug:
            print(f"{m_var} is indexed in {subset_names}")

        # transform to dataframe and store in dictionary
        df = var_values.to_frame().reset_index()
        if not df.empty:
            df.columns = subset_names + ["value"]
        df_dict[f"{m_var}"] = df.copy()

    return df_dict

month_onetime(model, PD, HR_MO)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
HR_MO

The identifier for the month-storage hydro facility.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that once the month-storage hydro facility's renewal is activated

(represented by a binary variable being set to 1), it cannot be deactivated (set back to 0) in any subsequent

planning period.

Details
  • This function imposes a non-decreasing constraint on the binary variable associated with the renewal of month-storage hydro facilities.
  • The constraint verifies that the binary status in the current planning period (PD) must be greater than or equal to its status in the previous period, enforcing a policy or operational requirement that once a renewal decision is made, it must be maintained in future periods.
  • This is crucial for long-term energy planning and regulatory compliance where continuity in renewable investments or upgrades is mandated.
Source code in COPPER.py
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
def month_onetime(model, PD, HR_MO):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        HR_MO: The identifier for the month-storage hydro facility.

    Returns:
        A Pyomo Constraint expression: Ensures that once the month-storage hydro facility's renewal is activated 
        (represented by a binary variable being set to 1), it cannot be deactivated (set back to 0) in any subsequent 
        planning period.

    Details:
        - This function imposes a non-decreasing constraint on the binary variable associated with the renewal of 
        month-storage hydro facilities.
        - The constraint verifies that the binary status in the current planning period (PD) must be greater than or 
        equal to its status in the previous period, enforcing a policy or operational requirement that once a renewal 
        decision is made, it must be maintained in future periods.
        - This is crucial for long-term energy planning and regulatory compliance where continuity in renewable 
        investments or upgrades is mandated.
    """
    return (model.month_renewal_binary[PD, HR_MO]
            >= model.month_renewal_binary[pds[pds.index(PD) - 1], HR_MO])

must_build_capacity(model, PD, ABA, cap)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
ABA

The balancing area as a string, indicating where the generators are located.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the total net capacity of the specified generation type in the

specified balancing area (ABA) fora specific period (PD) does not fall below a predefined minimum limit

((Alberta.a, 2025, biomass)[capacity_limit]).

Details
  • The function calculates the total net capacity of a specified power plants in the balancing area fora specifiec period, taking into account the initial existing capacity and any subsequent capacity changes due to new installations or retirements.
  • The cumulative capacity from the beginning of the period up to and including the current planning period is considered, ensuring that the capacity does not fall below the minimum allowed as specified in min_development.
  • This constraint is important for managing the scale of specific power platn expansion in compliance with regional energy policies or sustainability goals, which may require the expansion of specific facilities.
Source code in COPPER.py
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
def must_build_capacity(model, PD, ABA, cap):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        ABA: The balancing area as a string, indicating where the generators are located.

    Returns:
        A Pyomo Constraint expression: Ensures that the total net capacity of the specified generation type in the 
        specified balancing area (ABA) fora  specific period (PD) does not fall below a predefined minimum limit 
        (`(Alberta.a, 2025, biomass)[capacity_limit]`).

    Details:
        - The function calculates the total net capacity of a specified power plants in the balancing area fora  specifiec period,
        taking into account the initial existing capacity and any subsequent capacity changes due to new installations or retirements.
        - The cumulative capacity from the beginning of the period up to and including the current planning period is considered, 
        ensuring that the capacity does not fall below the minimum allowed as specified in `min_development`.
        - This constraint is important for managing the scale of specific power platn expansion in compliance with regional energy 
        policies or sustainability goals, which may require the expansion of specific facilities.
    """
    # Initialization
    ind = pds.index(PD)
    y = int(PD)
    must_build_cap = cap
    capacity, min_capacity = 0, 0

    # Loop to establish capacity limits
    for gen in model.tplants:
        if gen.endswith("pre2010") or gen.endswith("2010to2024") or gen.endswith("post2025"):
            suffix = gen.split("_")[-1]
            g = gen.removesuffix("_" + suffix)

            if g.endswith("under25"):
                suffix = gen.split("_")[-1]
                g = gen.removesuffix("_" + suffix)
        else:
            g = gen

        if g == must_build_cap:
            capacity += extant_thermal[pds[0] + '.' + ABA + '.' + gen] + quicksum(
                model.capacity_therm[PDD, ABA, gen] - model.retire_therm[PDD, ABA, gen] for PDD in
                pds[:ind + 1])
        else:
            capacity += 0

    try:
        min_capacity = min_development.query('ABA == @ABA & pd == @y & gen_type == @must_build_cap')['capacity_required'].values[0]
    except IndexError:
        min_capacity = 0

    if type(capacity) is int:
        return Constraint.Skip
    else:
        return capacity >= min_capacity

nationalcarbonlimit(model, PD)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the national emission limit is applied.

required

Returns:

Type Description

A Pyomo Constraint expression: Restricts the total carbon dioxide emissions from all thermal power plants

across the nation during the specified planning period (PD) to not exceed the national emission limit for that period.

Details
  • This function calculates the total emissions by aggregating the carbon dioxide output from all thermal power plants across different balancing areas, taking into account the carbon emission factors of each plant type (carbondioxide[TP]), and the amount supplied during the specific planning period.
  • The emissions are normalized to a daily basis considering the number of run days in a year, ensuring compliance with the national emission limits (nat_em_limit[PD]), which are predefined for each planning period.
  • The constraint is essential for meeting national environmental objectives and adhering to international climate agreements targeting reduced greenhouse gas emissions in the power sector.
Source code in COPPER.py
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
def nationalcarbonlimit(model, PD):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the national emission limit is applied.

    Returns:
        A Pyomo Constraint expression: Restricts the total carbon dioxide emissions from all thermal power plants 
        across the nation during the specified planning period (PD) to not exceed the national emission limit for that period.

    Details:
        - This function calculates the total emissions by aggregating the carbon dioxide output from all thermal power 
        plants across different balancing areas, taking into account the carbon emission factors of each plant type 
        (`carbondioxide[TP]`), and the amount supplied during the specific planning period.
        - The emissions are normalized to a daily basis considering the number of run days in a year, ensuring compliance 
        with the national emission limits (`nat_em_limit[PD]`), which are predefined for each planning period.
        - The constraint is essential for meeting national environmental objectives and adhering to international 
        climate agreements targeting reduced greenhouse gas emissions in the power sector.
    """
    return quicksum(
        model.supply[PD, H, ABA, TP, FT] * carbondioxide[TP + '.' + FT] / 1000000 for H in h for TP in tplants
        for ABA in aba for FT in model.ftype if FT in useful_fuels[TP].split('.')) <= \
        nat_em_limit[PD] / repday_scaling

newthermallimit(model, PD, LTP)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
LTP

The type of new thermal plant (e.g., 'coal', 'gas') for which the installation is to be limited.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the total capacity of new thermal plants of type LTP

installed during the specified planning period (PD) across all balancing areas does not exceed zero,

effectively prohibiting the addition of new capacity for this plant type.

Details
  • This function sums the capacities of all new thermal power plants of a specific type (LTP) across different balancing areas during a given planning period, and enforces a constraint that this total capacity must be zero.
  • The constraint is significant for scenarios where environmental policies or strategic energy transitions require a halt or reduction in the development of certain types of thermal power plants to meet carbon reduction goals or other environmental targets.
Source code in COPPER.py
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
def newthermallimit(model, PD, LTP):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        LTP: The type of new thermal plant (e.g., 'coal', 'gas') for which the installation is to be limited.

    Returns:
        A Pyomo Constraint expression: Ensures that the total capacity of new thermal plants of type LTP 
        installed during the specified planning period (PD) across all balancing areas does not exceed zero, 
        effectively prohibiting the addition of new capacity for this plant type.

    Details:
        - This function sums the capacities of all new thermal power plants of a specific type (LTP) across different 
        balancing areas during a given planning period, and enforces a constraint that this total capacity must be zero.
        - The constraint is significant for scenarios where environmental policies or strategic energy transitions 
        require a halt or reduction in the development of certain types of thermal power plants to meet carbon 
        reduction goals or other environmental targets.
    """
    return quicksum(model.capacity_therm[PD, ABA, LTP] for ABA in aba) <= 0

noNewCCSafter2035(model, ABACCS, CCST, PDD)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
ABACCS

The balancing area as a string, specifying where the new CCS capacity constraint is to be enforced.

required
CCST

The CCS technology type as a string, for which the new installation is to be prohibited.

required
PDD

The specific planning period after 2035 (e.g., '2036', '2040', etc.) during which the constraint is applied.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that no new capacity for the specified CCS technology is installed

in the designated balancing area during the specified planning period after 2035.

Details
  • This function enforces a policy that no new installations of specified CCS technologies are allowed in certain regions after the year 2035, reflecting a strategic move to either phase out these technologies or prevent their expansion due to environmental, economic, or policy considerations.
  • By setting the capacity to zero for the specified technology and planning periods, the model effectively restricts the development of new CCS facilities in response to evolving climate policies or emissions targets.
Source code in COPPER.py
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
def noNewCCSafter2035(model, ABACCS, CCST, PDD):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        ABACCS: The balancing area as a string, specifying where the new CCS capacity constraint is to be enforced.
        CCST: The CCS technology type as a string, for which the new installation is to be prohibited.
        PDD: The specific planning period after 2035 (e.g., '2036', '2040', etc.) during which the constraint is applied.

    Returns:
        A Pyomo Constraint expression: Ensures that no new capacity for the specified CCS technology is installed 
        in the designated balancing area during the specified planning period after 2035.

    Details:
        - This function enforces a policy that no new installations of specified CCS technologies are allowed in 
        certain regions after the year 2035, reflecting a strategic move to either phase out these technologies 
        or prevent their expansion due to environmental, economic, or policy considerations.
        - By setting the capacity to zero for the specified technology and planning periods, the model effectively 
        restricts the development of new CCS facilities in response to evolving climate policies or emissions targets.
    """
    return model.capacity_therm[PDD, ABACCS, CCST] <= 0

no_CCS_in_specified_aba(model, NOCCS, CCST)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
NOCCS

The balancing area as a string, specifying where the CCS capacity constraint is to be enforced.

required
CCST

The CCS technology type as a string, for which the installation is to be limited.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the total capacity of a specific CCS technology type within

the specified balancing area remains at zero across all planning periods.

Details
  • This function sums the capacities of a specific CCS technology type across all planning periods within a designated balancing area, and enforces that this total capacity must not exceed zero.
  • This zero-capacity constraint reflects specific policy decisions or environmental strategies aimed at either phasing out particular CCS technologies or preventing their adoption within certain regions.
  • The enforcement of this constraint is crucial for meeting regional or national environmental targets or adhering to local regulations that restrict the use of certain CCS technologies.
Source code in COPPER.py
4372
4373
4374
4375
4376
4377
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
def no_CCS_in_specified_aba(model, NOCCS, CCST):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        NOCCS: The balancing area as a string, specifying where the CCS capacity constraint is to be enforced.
        CCST: The CCS technology type as a string, for which the installation is to be limited.

    Returns:
        A Pyomo Constraint expression: Ensures that the total capacity of a specific CCS technology type within 
        the specified balancing area remains at zero across all planning periods.

    Details:
        - This function sums the capacities of a specific CCS technology type across all planning periods within 
          a designated balancing area, and enforces that this total capacity must not exceed zero.
        - This zero-capacity constraint reflects specific policy decisions or environmental strategies aimed at 
          either phasing out particular CCS technologies or preventing their adoption within certain regions.
        - The enforcement of this constraint is crucial for meeting regional or national environmental targets 
          or adhering to local regulations that restrict the use of certain CCS technologies.
    """
    return quicksum(model.capacity_therm[PDD, NOCCS, CCST] for PDD in pds) <= 0

no_more_pre2025(model, PD, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
ABA

The balancing area as a string, indicating where the diesel generators are located.

required

Returns:

Type Description

A Pyomo Constraint expression: Enforces that the total new capacity attributed to pre-2025 thermal generators

in the specified balancing area must be zero across all planning periods.

Details
  • This function aims to ensure that thermal generators categorized as "pre2025" are no longer created, these are older unregulated units. All newly created units must be designated for regulation.
Source code in COPPER.py
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
def no_more_pre2025(model, PD, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        ABA: The balancing area as a string, indicating where the diesel generators are located.

    Returns:
        A Pyomo Constraint expression: Enforces that the total new capacity attributed to pre-2025 thermal generators 
        in the specified balancing area must be zero across all planning periods.

    Details:
        - This function aims to ensure that thermal generators categorized as "pre2025" are no longer created, these are
		older unregulated units. All newly created units must be designated for regulation.
    """
    return quicksum(model.capacity_therm[PD, ABA, TP] for TP in pre2025_tplants) <= 0

no_more_under25(model, PD, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
ABA

The balancing area as a string, indicating where the diesel generators are located.

required

Returns:

Type Description

A Pyomo Constraint expression: Enforces that the total new capacity attributed to thermal generators under 25MW

in the specified balancing area must be zero across all planning periods.

Details
  • This function aims to ensure that thermal generators categorized as "under25" are no longer created, as COPPER cannot determine the size of an individual plant. All newly created units must not be tagged as "under25.
Source code in COPPER.py
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
def no_more_under25(model, PD, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        ABA: The balancing area as a string, indicating where the diesel generators are located.

    Returns:
        A Pyomo Constraint expression: Enforces that the total new capacity attributed to thermal generators under 25MW
        in the specified balancing area must be zero across all planning periods.

    Details:
        - This function aims to ensure that thermal generators categorized as "under25" are no longer created, as COPPER cannot
        determine the size of an individual plant. All newly created units must not be tagged as "under25.
    """
    return quicksum(model.capacity_therm[PD, ABA, TP] for TP in under25_tplants) <= 0

no_transmission_expansion_period_1(model, PD)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the total capacity of new transmission capacity installed

during the first planning period (PD) across each balancing areas is constrained to zero.

Details
  • This function sums the new capacities of all transmission lies across all balancing areas during the first planning period, and enforces a constraint that this total capacity must be zero.
  • The constraint is significant for scenarios where the first planning period is close enough to present day that any significant new development by the end of the first planning period would be highly improbable.
Source code in COPPER.py
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
def no_transmission_expansion_period_1(model, PD):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.

    Returns:
        A Pyomo Constraint expression: Ensures that the total capacity of new transmission capacity installed 
        during the first planning period (PD) across each balancing areas is constrained to zero.

    Details:
        - This function sums the new capacities of all transmission lies across all balancing areas 
        during the first planning period, and enforces a constraint that this total capacity must be zero.
        - The constraint is significant for scenarios where the first planning period is close enough to present day
        that any significant new development by the end of the first planning period would be highly improbable.
    """
    aba_transmission = [model.capacity_transmission[PD, ABA, ABBA] for 
                        ABA in aba for ABBA in aba if ABA + '.' + ABBA in transmap]
    if len(aba_transmission) > 0:
        return  quicksum(aba_transmission) == 0
    else:
        return Constraint.Skip

nonemittinglimit(model, PD)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the non-emitting generation limit is applied.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the sum of electricity generated from non-emitting sources

(including wind, solar, hydro, and other non-emitting thermal plants) within the specified planning period

(PD) constitutes at least a certain percentage (defined by nonemitting_limit[PD]) of the total electricity

generation from all sources.

Details
  • This function calculates the total generation from non-emitting sources, which includes output from wind, solar, run-of-river hydro, day and month storage hydro, and non-emitting thermal plants, and compares this to the overall generation, which includes these sources plus all other thermal power plant outputs.
  • The required minimum percentage of total generation from non-emitting sources is specified for each planning period in nonemitting_limit[PD].
  • Enforcing this constraint is crucial for regions aiming to meet sustainability targets or reduce greenhouse gas emissions through increased reliance on renewable and non-emitting energy sources.
Source code in COPPER.py
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
def nonemittinglimit(model, PD):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the non-emitting generation limit is applied.

    Returns:
        A Pyomo Constraint expression: Ensures that the sum of electricity generated from non-emitting sources 
        (including wind, solar, hydro, and other non-emitting thermal plants) within the specified planning period 
        (PD) constitutes at least a certain percentage (defined by `nonemitting_limit[PD]`) of the total electricity 
        generation from all sources.

    Details:
        - This function calculates the total generation from non-emitting sources, which includes output from wind, 
        solar, run-of-river hydro, day and month storage hydro, and non-emitting thermal plants, and compares this 
        to the overall generation, which includes these sources plus all other thermal power plant outputs.
        - The required minimum percentage of total generation from non-emitting sources is specified for each planning 
        period in `nonemitting_limit[PD]`.
        - Enforcing this constraint is crucial for regions aiming to meet sustainability targets or reduce greenhouse 
        gas emissions through increased reliance on renewable and non-emitting energy sources.
    """
    return quicksum(
        model.supply[PD, H, ABA, NEP, NEFT] for H in h for ABA in aba for NEP in non_emitting for NEFT in
        model.ftype if NEFT in non_emitting_ftype[NEP].split('.')) + quicksum(
        model.windonsout[PD, H, ABA] + model.windofsout[PD, H, ABA] + model.solarout[PD, H, ABA] + ror_hydroout[
            PD + '.' + str(H) + '.' + ABA] + model.daystoragehydroout[PD, H, ABA] + model.monthstoragehydroout[
            PD, H, ABA] for H in h for ABA in aba) >= \
        nonemitting_limit[PD] * (
                quicksum(model.supply[PD, H, ABA, TP, FT] for H in h for ABA in aba for TP in tplants for FT in
                         model.ftype if FT in useful_fuels[TP].split('.')) + quicksum(
            model.windonsout[PD, H, ABA] + model.windofsout[PD, H, ABA] + model.solarout[PD, H, ABA] +
            ror_hydroout[PD + '.' + str(H) + '.' + ABA] + model.daystoragehydroout[PD, H, ABA] +
            model.monthstoragehydroout[PD, H, ABA] for H in h for ABA in aba))

nuclear_cap_var(model, PD, ABA)

Associates nuclear capacity in model.capacity_therm with abstracted capacity variable (model.capacity_nuclear). Only required for linearization of semi-continuous nuclear capacity varibale constraints.

Source code in COPPER.py
3020
3021
3022
3023
3024
3025
def nuclear_cap_var(model, PD, ABA):
    """
    Associates nuclear capacity in model.capacity_therm with abstracted capacity variable (model.capacity_nuclear).
    Only required for linearization of semi-continuous nuclear capacity varibale constraints.
    """
    return model.capacity_nuclear[PD, ABA] <= model.capacity_therm[PD, ABA, 'nuclear']

nuclearminsize(model, PD, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
ABA

The balancing area as a string, indicating where the generators are located.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that if new conventional nuclear plants new nuclear plants

are installed, they are at least 600 MW in any given period (PD) and balancing area (ABA).

Details
  • The constraint is important for ensuring conventional nuclear plants are within the economies of scale necessary to guarantee the capital costs are realistic.
Source code in COPPER.py
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
def nuclearminsize(model, PD, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        ABA: The balancing area as a string, indicating where the generators are located.

    Returns:
        A Pyomo Constraint expression: Ensures that if new conventional nuclear plants new nuclear plants 
        are installed, they are at least 600 MW in any given period (PD) and balancing area (ABA).

    Details:
        - The constraint is important for ensuring conventional nuclear plants are within the economies of 
          scale necessary to guarantee the capital costs are realistic.
    """
    return model.capacity_nuclear[PD, ABA] >= 600 * model.nuclear_binary[PD, ABA]

obj_rule(model)

Objective function to minimize the total discounted and inflation-adjusted cost of an energy system over a set of planning periods. This function aggregates the costs associated with capital, fuel, fixed and variable operations and maintenance (O&M), hydro renewal, and new storage, both in terms of capital and O&M costs.

Parameters:

Name Type Description Default
model

A Pyomo model instance, which contains all decision variables, parameters, and sets relevant to the energy system being optimized.

required

Returns:

Type Description

A single float value representing the total cost, which is the sum of discounted and inflation-adjusted costs

for capital expenditures, fuel, O&M, and hydro and storage related expenses across all planning periods.

Details
  • Each cost component (capcost, fuel_cost, varOMcost, fixOMcost, hydrorenewalccost, hydrorenewalfomcost, hydrorenewalvomcost, storage_ccost, storage_omcost) is multiplied by a discount coefficient (disc_coef) and an inflation coefficient (inf_coef) to convert future costs to present value terms for comparison and aggregation.
  • The discount coefficient adjusts for the time value of money, decreasing the value of future expenditures. This coefficient changes based on whether the current period is the last in the series or not, with special treatment for the final period using a different discount rate (discount_2050). The final period is representative of a single year, whereas the periods preceding are representative of 5-year periods. Periods that represent 5-year periods are discounted back as a 5-year annuity series. The final period is discounted back to the present as a single future value.
  • The inflation coefficient scales costs based on expected inflation, adjusting the nominal cost to real terms as compared to a base year (refyear).
  • The function iterates through all planning periods, accumulating the adjusted costs for each category, and returns the total as the objective value to minimize.
Usage

This function should be set as the objective in a Pyomo model setup to optimize the economic performance of an energy system: model.obj = Objective(rule=obj_rule)

Note

discount, discount_2050, inflation, and refyear are predefined in config.toml.

Source code in COPPER.py
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
def obj_rule(model):
    """
    Objective function to minimize the total discounted and inflation-adjusted cost of an energy system over a set of 
    planning periods. This function aggregates the costs associated with capital, fuel, fixed and variable operations 
    and maintenance (O&M), hydro renewal, and new storage, both in terms of capital and O&M costs.

    Parameters:
        model: A Pyomo model instance, which contains all decision variables, parameters, and sets relevant to the energy 
               system being optimized.

    Returns:
        A single float value representing the total cost, which is the sum of discounted and inflation-adjusted costs 
        for capital expenditures, fuel, O&M, and hydro and storage related expenses across all planning periods.

    Details:
        - Each cost component (capcost, fuel_cost, varOMcost, fixOMcost, hydrorenewalccost, hydrorenewalfomcost, hydrorenewalvomcost, 
          storage_ccost, storage_omcost) is multiplied by a discount coefficient (`disc_coef`) and an inflation 
          coefficient (`inf_coef`) to convert future costs to present value terms for comparison and aggregation.
        - The discount coefficient adjusts for the time value of money, decreasing the value of future expenditures. 
          This coefficient changes based on whether the current period is the last in the series or not, with special 
          treatment for the final period using a different discount rate (`discount_2050`). The final period is representative
		  of a single year, whereas the periods preceding are representative of 5-year periods. Periods that represent 5-year
		  periods are discounted back as a 5-year annuity series. The final period is discounted back to the present 
		  as a single future value.
        - The inflation coefficient scales costs based on expected inflation, adjusting the nominal cost to real terms 
          as compared to a base year (`refyear`).
        - The function iterates through all planning periods, accumulating the adjusted costs for each category, 
          and returns the total as the objective value to minimize.

    Usage:
        This function should be set as the objective in a Pyomo model setup to optimize the economic performance of an 
        energy system:
            model.obj = Objective(rule=obj_rule)

    Note:
        `discount`, `discount_2050`, `inflation`, and `refyear` are predefined in config.toml.
    """
    tcapcost = 0
    tfcost = 0
    tfixedOM = 0
    tvariableOM = 0
    tcarboncreditrev = 0
    thydrorenewalccost = 0
    thydrorenewalfomcost = 0
    thydrorenewalvomcost = 0
    tstorage_ccost = 0
    tstorage_omcost = 0
    for PD in pds:
        ind = pds.index(PD)

        if ind != (len(pds) - 1):
            # We use disc_ann_coef as the coeffecient to bring an n-year series Annuity to a net present value
            first_term = (1 / (1 + discount) ** (int(pds[ind + 1]) - int(PD)))
            second_term = 1
            third_term = discount
            disc_ann_coef = ((second_term - first_term) / third_term)
            if (disc_ann_coef < 0):
                print(disc_ann_coef)
                sys.exit()
            disc_coef = disc_ann_coef * (1 / (1 + discount) ** ((
                                                                        int(PD) - 1) - refyear))  # New, we are using annuity series combined with future value to present value formula
        else:
            disc_coef = (1 / (1 + discount_2050) ** (
                    int(PD) - refyear))  ## Use this for the final subperiod since we are not annualizing it -- treat it as a future to present value
        inf_coef = ((1 + inflation) ** (int(PD) - refyear))

        tcapcost += model.capcost[PD] * disc_coef * inf_coef
        tfcost += model.fuel_cost[PD] * disc_coef * inf_coef  ##This is treating fuel cost like a single future value
        tvariableOM += model.varOMcost[PD] * disc_coef * inf_coef
        tcarboncreditrev += model.carboncredit_revenue[PD] * disc_coef * inf_coef
        tfixedOM += model.fixOMcost[PD] * disc_coef * inf_coef
        thydrorenewalccost += model.hydrorenewalccost[PD] * disc_coef * inf_coef
        thydrorenewalfomcost += model.hydrorenewalfomcost[PD] * disc_coef * inf_coef
        thydrorenewalvomcost += model.hydrorenewalvomcost[PD] * disc_coef * inf_coef
        tstorage_ccost += model.storage_ccost[PD] * disc_coef * inf_coef
        tstorage_omcost += model.storage_omcost[PD] * disc_coef * inf_coef
    return (
                tcapcost + tfcost + tvariableOM + tfixedOM - tcarboncreditrev \
                    + thydrorenewalccost + thydrorenewalfomcost \
                        + thydrorenewalvomcost + tstorage_ccost + tstorage_omcost)

overlim_fuel_constraint(model, PD, ABA, TP, FT)

Parameters:

Name Type Description Default
model

Pyomo model instance containing all necessary decision variables, parameters, and sets.

required
PD

Planning period as a string (e.g., '2025') for which the constraint is applied.

required
ABA

Balancing area as a string (e.g., 'British Columbia').

required
TP

Thermal plant as a string

required
FT

Fuel type as a string

required
Source code in COPPER.py
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
def overlim_fuel_constraint(model, PD, ABA, TP, FT):
    """

    Parameters:
        model: Pyomo model instance containing all necessary decision variables, parameters, and sets.
        PD: Planning period as a string (e.g., '2025') for which the constraint is applied.
        ABA: Balancing area as a string (e.g., 'British Columbia').
        TP: Thermal plant as a string 
        FT: Fuel type as a string

    """

    if TP in useful_fuels_overlim.keys() and FT in useful_fuels_overlim[TP].split('.'):
        if not flag_cer:
            return sum(model.supply[PD, H, ABA, TP, FT] for H in h) <= 0
        elif PD in ['2025', '2030']:
            return sum(model.supply[PD, H, ABA, TP, FT] for H in h) <= 0
        else:
            return Constraint.Skip
    else:
        return Constraint.Skip

parloadCCS(model, PD, H, ABA, CCST, CCSFT)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
H

The hour within the planning period for which the partial load constraint is relevant.

required
ABA

The balancing area as a string, indicating where the CCS-equipped thermal plant is located.

required
CCST

The CCS technology type equipped on the thermal plant.

required

Returns:

Type Description

A Pyomo Constraint expression: Restricts the hourly supply of CCS-equipped thermal power plants to no more

than 80% of their calculated available capacity, considering both installed and retired capacities up to

the current planning period.

Details
  • This function assesses the maximum permissible supply from CCS-equipped thermal plants by calculating their total effective capacity—considering both extant capacity at the start of the planning periods and adjustments due to capacity installations or retirements.
  • The supply limitation to 80% of available capacity is to represent the 20% parasitic load to support CCS.
Source code in COPPER.py
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
def parloadCCS(model, PD, H, ABA, CCST, CCSFT):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        H: The hour within the planning period for which the partial load constraint is relevant.
        ABA: The balancing area as a string, indicating where the CCS-equipped thermal plant is located.
        CCST: The CCS technology type equipped on the thermal plant.

    Returns:
        A Pyomo Constraint expression: Restricts the hourly supply of CCS-equipped thermal power plants to no more 
        than 80% of their calculated available capacity, considering both installed and retired capacities up to 
        the current planning period.

    Details:
        - This function assesses the maximum permissible supply from CCS-equipped thermal plants by calculating 
          their total effective capacity—considering both extant capacity at the start of the planning periods and 
          adjustments due to capacity installations or retirements.
        - The supply limitation to 80% of available capacity is to represent the 20% parasitic load to support CCS.

    """
    ind = pds.index(PD)
    for FT in model.ftype:
        if FT in CCSFT.split('.'):
            return model.supply[PD, H, ABA, CCST, FT] <= (quicksum(
                model.capacity_therm[PDD, ABA, CCST] - model.retire_therm[PDD, ABA, CCST] for PDD in pds[:ind + 1]) +
                                                          extant_thermal[pds[0] + '.' + ABA + '.' + CCST]) * (
                        1 - parasitic_load)

phaseout_constraint_generator(model)

Generator function to create phaseout constraints for the model.

Source code in COPPER.py
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
def phaseout_constraint_generator(model):
    """
    Generator function to create phaseout constraints for the model.
    """
    constraints = []
    for PHOT in ph_out_t:
        ind = pds.index(phase_out_type_year[PHOT])
        for ABA in aba:
            for PDD in pds[ind:]:
                constraints.append(((PHOT, ABA, PDD), phaseout_rule(model, PHOT, ABA, PDD)))
            # Add aggregate constraint for the total capacity over the phaseout period
            constraints.append(((PHOT, ABA, 'years_leading_up'), aggregate_phaseout_rule(model, PHOT, ABA)))

    for AP in list(set(prov_ph_out_t.index.get_level_values(0))):
        for PHOT in list(set(prov_ph_out_t.index.get_level_values(1))):
            try:
                ind = pds.index(str(prov_ph_out_t.query('AP == @AP & gen_type == @PHOT')['phase_out_year'].values[0]))
                for suffix in aba1:
                    ABA = AP + '.' + suffix
                    if ABA in aba:
                        for PDD in pds[ind:]:
                            if ((PHOT, ABA, PDD), phaseout_rule(model, PHOT, ABA, PDD)) not in constraints:
                                constraints.append(((PHOT, ABA, PDD), phaseout_rule(model, PHOT, ABA, PDD)))
                        # Add aggregate constraint for the total capacity over the phaseout period
                        if ((PHOT, ABA, 'years_leading_up'), aggregate_phaseout_rule(model, PHOT, ABA)) not in constraints:
                            constraints.append(((PHOT, ABA, 'years_leading_up'), aggregate_phaseout_rule(model, PHOT, ABA)))
            except IndexError:
                continue

    return constraints

phaseout_rule(model, PHOT, ABA, PDD)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PHOT

The identifier for the type of thermal plant being phased out (e.g., 'coal', 'oil').

required
ABA

The balancing area as a string, indicating where the phase-out is taking place.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the combined existing and net installed capacity of thermal

power plants of type PHOT in the specified balancing area (ABA) does not exceed zero by the end of the

designated phase-out period.

Details
  • The function calculates the total net capacity of the specified type of thermal plant up to the year designated for phase-out, which is determined based on the planning period index mapped in phase_out_type_year[PHOT].
  • This includes the initial extant capacity and any changes due to capacity additions or retirements up to and including the phase-out year.
  • The constraint is crucial for aligning with environmental policies or agreements that mandate the reduction or elimination of certain types of fossil-fuel-based generation to mitigate climate change or reduce environmental impacts.
Source code in COPPER.py
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
def phaseout_rule(model, PHOT, ABA, PDD):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PHOT: The identifier for the type of thermal plant being phased out (e.g., 'coal', 'oil').
        ABA: The balancing area as a string, indicating where the phase-out is taking place.

    Returns:
        A Pyomo Constraint expression: Ensures that the combined existing and net installed capacity of thermal 
        power plants of type PHOT in the specified balancing area (ABA) does not exceed zero by the end of the 
        designated phase-out period.

    Details:
        - The function calculates the total net capacity of the specified type of thermal plant up to the year 
        designated for phase-out, which is determined based on the planning period index mapped in `phase_out_type_year[PHOT]`.
        - This includes the initial extant capacity and any changes due to capacity additions or retirements up to 
        and including the phase-out year.
        - The constraint is crucial for aligning with environmental policies or agreements that mandate the reduction 
        or elimination of certain types of fossil-fuel-based generation to mitigate climate change or reduce 
        environmental impacts.
    """
    try:
        existing_cap = extant_thermal[phase_out_type_year[PDD] + '.' + ABA + '.' + PHOT]
    except KeyError:
        existing_cap = 0

    return (existing_cap + model.capacity_therm[PDD, ABA, PHOT]) <= model.retire_therm[PDD, ABA, PHOT]

pickle_model(model, path)

Serializes a Pyomo model using dill imported as pickle.

Args:

model: The Pyomo model to serialize.
path: The path to save the pickled model to.

Returns:

None
Source code in phases/postprocessingtools.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def pickle_model(model, path) -> None:
    """Serializes a Pyomo model using `dill` imported as `pickle`.

    ## Args:
        model: The Pyomo model to serialize.
        path: The path to save the pickled model to.

    ## Returns:
        None

    """
    model_path = Path(path)
    with model_path.open("wb") as fo:
        try:
            # this takes ~ 5 min for a test model. Probably would be faster
            # without serializing all the functions
            pickle.dump(model, fo, byref=True, recurse=True)
        except Exception as e:
            print(e.with_traceback())
            logging.error("An exeption was trown", exc_info=True, stack_info=True)

planning_reserve(model, PD, SEAS, AP)

Calculates and ensures that the available generation capacity within a province (AP is a list of all provinces defined in config.toml) and season (SEAS) meets or exceeds the required planning reserve margin for the specified planning period (PD). This function considers various sources of power generation including thermal, wind, solar, hydro, and storage, both existing and potentially newly developed, to satisfy the demand plus a specified reserve margin.

Parameters:

Name Type Description Default
model Pyomo Object

A Pyomo model instance containing decision variables, parameters, and sets related to the energy model.

required
PD str

The planning period, represented as a string, for which the reserve capacity is being calculated.

required
SEAS str

The season (e.g., 'summer', 'winter') for which the capacity reserve requirement is assessed.

required
AP str

province, represented as a string, for which the calculation is performed.

required

Returns:

Type Description

cap_val >= peak_demand[PD + '.' + SEAS + '.' + AP] * (float(reserve_margin_dict[AP]) + 1) (Pyomo Constraint): Evaluates to True if the

total generation capacity within the province AP during the planning period PD and season SEAS meets or exceeds the peak demand

multiplied by the (specified reserve margin + 1).

Details
  • This function aggregates the capacity from various sources including:
  • Existing and new thermal capacities, adjusted for retirements.
  • Wind and solar capacities, both existing and new, weighted by seasonal capacity factors.
  • Run-of-river, daily, and monthly hydro capacities.
  • Additional capacities from storage if storage is continuously operational.
  • Renewable capacities (if hydro development is assumed) like run-of-river, daily, and monthly renewals.
  • The capacities are adjusted for each generation type's relevance to the season and province.
  • The required reserve margin for AP is applied to the peak demand to determine the necessary total capacity.
  • Each generation source contributes to the total capacity, which must at least match the adjusted demand value considering the reserve margin.
Source code in COPPER.py
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
def planning_reserve(model, PD, SEAS, AP):
    """
    Calculates and ensures that the available generation capacity within a province (AP is a list of all provinces defined in config.toml) and 
    season (SEAS) meets or exceeds the required planning reserve margin for the specified planning period (PD). This 
    function considers various sources of power generation including thermal, wind, solar, hydro, and storage, both 
    existing and potentially newly developed, to satisfy the demand plus a specified reserve margin.

    Parameters:
        model (Pyomo Object): A Pyomo model instance containing decision variables, parameters, and sets related to the energy model.
        PD (str): The planning period, represented as a string, for which the reserve capacity is being calculated.
        SEAS (str): The season (e.g., 'summer', 'winter') for which the capacity reserve requirement is assessed.
        AP (str): province, represented as a string, for which the calculation is performed.

    Returns:
        cap_val >= peak_demand[PD + '.' + SEAS + '.' + AP] * (float(reserve_margin_dict[AP]) + 1) (Pyomo Constraint): Evaluates to True if the 
        total generation capacity within the province AP during the planning period PD and season SEAS meets or exceeds the peak demand 
        multiplied by the (specified reserve margin + 1).

    Details:
        - This function aggregates the capacity from various sources including:
        * Existing and new thermal capacities, adjusted for retirements.
        * Wind and solar capacities, both existing and new, weighted by seasonal capacity factors.
        * Run-of-river, daily, and monthly hydro capacities.
        * Additional capacities from storage if storage is continuously operational.
        * Renewable capacities (if hydro development is assumed) like run-of-river, daily, and monthly renewals.
        - The capacities are adjusted for each generation type's relevance to the season and province.
        - The required reserve margin for AP is applied to the peak demand to determine the necessary total capacity.
        - Each generation source contributes to the total capacity, which must at least match the adjusted demand value
        considering the reserve margin.
    """
    ind = pds.index(PD)
    season_cv = capacity_value[capacity_value["Season"] == SEAS]

    # wind
    exist_wind_reserve = quicksum(
        extant_wind_solar[pds.index(PD)][str(GL) + '.' + 'wind'] * float(
            season_cv[map_gl_to_pr[int(GL)]].loc['wind_ons'])
                            for GL in gl if str(GL) + '.' + 'wind' in extant_wind_solar[0] and AP == map_gl_to_pr[int(GL)])

    new_wind_ons = quicksum(
        (model.capacity_wind_ons[PDD, GL] + model.capacity_wind_ons_recon[PDD, GL]) * float(
            season_cv[map_gl_to_pr[int(GL)]].loc['wind_ons'])
                            for PDD in pds[:ind + 1] for GL in gl
                            if AP == map_gl_to_pr[int(GL)])

    new_wind_ofs = quicksum((model.capacity_wind_ofs[PDD, GL] + model.capacity_wind_ofs_recon[PDD, GL]) * float(
        season_cv[map_gl_to_pr[int(GL)]].loc['wind_ofs'])
                            for PDD in pds[:ind + 1] for GL in gl
                            if AP == map_gl_to_pr[int(GL)])
    new_wind_reserve = new_wind_ons + new_wind_ofs

    # solar
    exist_solar_reserve = quicksum(
        extant_wind_solar[pds.index(PD)][str(GL) + '.' + 'solar'] * float(
            season_cv[map_gl_to_pr[int(GL)]].loc['solar'])
                            for GL in gl if str(GL) + '.' + 'solar' in extant_wind_solar[0] if AP == map_gl_to_pr[int(GL)])

    new_solar_reserve = quicksum((model.capacity_solar[PDD, GL] + model.capacity_solar_recon[PDD, GL]) \
                                    * float(season_cv[map_gl_to_pr[int(GL)]].loc['solar'])
                                    for PDD in pds[:ind + 1] for GL in gl if AP == map_gl_to_pr[int(GL)])

    # hydro
    ror_hydro_reserve = quicksum(ror_hydro_capacity[PD + '.' + ABA]
                                    * float(season_cv.at['hydro_run', AP])
                                    for ABA in regions_per_province[AP])
    day_hydro_reserve = quicksum(day_hydro_capacity[PD + '.' + ABA]
                                    * float(season_cv.at['hydro_daily', AP])
                                    for ABA in regions_per_province[AP])
    month_hydro_reserve = quicksum(month_hydro_capacity[PD + '.' + ABA]
                                    * float(season_cv.at['hydro_monthly', AP])
                                    for ABA in regions_per_province[AP])

    # thermal
    thermal_reserve = quicksum((extant_thermal[pds[0] + '.' + ABA + '.' + TP] + quicksum(
        model.capacity_therm[PDD, ABA, TP] - model.retire_therm[PDD, ABA, TP] for PDD in pds[:ind + 1])) *
                                float(season_cv.at[TP, AP])
                                for ABA in regions_per_province[AP] for TP in tplants)

    cap_val = thermal_reserve + exist_wind_reserve + exist_solar_reserve \
               + new_wind_reserve + new_solar_reserve \
               + ror_hydro_reserve + day_hydro_reserve + month_hydro_reserve

    # hydro renewal
    if hydro_development:
        ror_renewal_reserve = quicksum(
            capacity_ror_renewal[HR_ROR] * model.ror_renewal_binary[PDD, HR_ROR]
            * float(season_cv.at['hydro_run', AP])
            for PDD in pds[:ind + 1] for HR_ROR in hr_ror
            if hr_ror_location[HR_ROR] in regions_per_province[AP])
        day_renewal_reserve = quicksum(
            capacity_day_renewal[HR_DAY] * model.day_renewal_binary[PDD, HR_DAY]
            * float(season_cv.at['hydro_daily', AP])
            for PDD in pds[:ind + 1] for HR_DAY in hr_day
            if hr_day_location[HR_DAY] in regions_per_province[AP])
        month_renewal_reserve = quicksum(
            capacity_month_renewal[HR_MO] * model.month_renewal_binary[PDD, HR_MO]
            * float(season_cv.at['hydro_monthly', AP])
            for PDD in pds[:ind + 1] for HR_MO in hr_mo
            if hr_month_location[HR_MO] in regions_per_province[AP])
        cap_val += ror_renewal_reserve + day_renewal_reserve + month_renewal_reserve

    # storage
    if storage_continous:
        storage_reserve = quicksum(
            model.capacity_storage[PDD, ST, ABA] * float(season_cv.at[ST, AP])
            for PDD in pds[:ind + 1] for ABA in regions_per_province[AP] for ST in st)
        cap_val += storage_reserve

    return cap_val >= peak_demand[PD + '.' + SEAS + '.' + AP] * (float(reserve_margin_dict[AP]) + 1)

planning_reserve_aggregated(model, PD, SEAS, TECH)

Calculates and ensures that the aggregated capacity across all power generation types in all applicable regions meets or exceeds the required planning reserve margin for a specified planning period and season. This function aggregates generation capacity from thermal, wind, solar, hydro, and storage sources, considering both existing and newly developed capacities.

Parameters:

Name Type Description Default
model Pyomo Object

Pyomo model instance containing decision variables, parameters, and sets for the energy model.

required
PD str

String representing the planning period for which the reserve requirement is calculated.

required
TECH str

indicating the technology for which the calculation is relevant.

required
SEAS str

season to which the calculation is applied, between winter and summer

required

Returns:

Type Description

Pyomo Constraint: checks if the total available generation capacity (cap_val)

is at least the product of peak demand (dem_val) for the period and season, and the required reserve margin.

Details
  • The function iterates over all areas (AP) and aggregates capacity values for all types of generation: thermal, wind (onshore and offshore, including reconstructed capacities), solar (including reconstructed), run-of-river, daily, and monthly hydro, and storage if continuous operation is assumed.
  • For each area, the capacity is adjusted for the specific capacity value of the season for wind and solar.
  • Hydro development and continuous storage operation are conditionally included in the capacity summation.
  • The aggregated capacity for each area is compared against the adjusted demand value, which is the peak demand multiplied by the sum of 1 and the aggregate reserve margin (e.g., 1 + 15% reserve margin). Warning: Only use this constraint if you are simulating regions and would like to analyze a shared planning reserve.
Source code in COPPER.py
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
    def planning_reserve_aggregated(model, PD, SEAS, TECH):
        """
        Calculates and ensures that the aggregated capacity across all power generation types in all applicable regions
        meets or exceeds the required planning reserve margin for a specified planning period and season. This function
        aggregates generation capacity from thermal, wind, solar, hydro, and storage sources, considering both existing and
        newly developed capacities.

        Parameters:
            model (Pyomo Object): Pyomo model instance containing decision variables, parameters, and sets for the energy model.
            PD (str): String representing the planning period for which the reserve requirement is calculated.
            TECH (str): indicating the technology for which the calculation is relevant.
            SEAS (str): season to which the calculation is applied, between winter and summer

        Returns:
            Pyomo Constraint: checks if the total available generation capacity (cap_val) 
            is at least the product of peak demand (dem_val) for the period and season, and the required reserve margin.

        Details:
            - The function iterates over all areas (AP) and aggregates capacity values for all types of generation:
            thermal, wind (onshore and offshore, including reconstructed capacities), solar (including reconstructed),
            run-of-river, daily, and monthly hydro, and storage if continuous operation is assumed.
            - For each area, the capacity is adjusted for the specific capacity value of the season for wind and solar.
            - Hydro development and continuous storage operation are conditionally included in the capacity summation.
            - The aggregated capacity for each area is compared against the adjusted demand value, which is the peak demand 
            multiplied by the sum of 1 and the aggregate reserve margin (e.g., 1 + 15% reserve margin).
		Warning: Only use this constraint if you are simulating regions and would like to analyze a shared planning reserve.
        """
        ind = pds.index(PD)
        cap_val = 0
        dem_val = peak_demand_aggregate[PD + '.' + SEAS]
        season_cv = capacity_value[(capacity_value.index == TECH) & (capacity_value["Season"] == SEAS)]
        # Initialize reserves
        thermal_reserve = 0
        ## VRE
        solar_reserve = 0  # new solar reserve
        exist_solar_reserve = 0
        windon_reserve = 0  # new wind onshore reserve
        windoff_reserve = 0  # new wind offshore reserve
        exist_wind_reserve = 0
        ror_hydro_reserve = 0
        day_hydro_reserve = 0
        month_hydro_reserve = 0

        for AP in ap:

            if "wind" in TECH:
                windon_reserve = quicksum(
                    (model.capacity_wind_ons[PDD, GL] + model.capacity_wind_ons_recon[PDD, GL]) * float(
                        season_cv[map_gl_to_pr[int(GL)]].iloc[0]) for PDD in pds[:ind + 1] for GL in gl if
                    AP == map_gl_to_pr[int(GL)] if season_cv.index[0] == "wind_ons")

                exist_wind_reserve = quicksum(extant_wind_solar[pds.index(PD)][str(GL) + '.' + 'wind'] * float(
                    season_cv[map_gl_to_pr[int(GL)]].iloc[0]) for GL in gl if
                                              str(GL) + '.' + 'wind' in extant_wind_solar[0] and AP == map_gl_to_pr[
                                                  int(GL)])

                windoff_reserve = quicksum(
                    (model.capacity_wind_ofs[PDD, GL] + model.capacity_wind_ofs_recon[PDD, GL]) * float(
                        season_cv[map_gl_to_pr[int(GL)]].iloc[0]) for PDD in pds[:ind + 1] for GL in gl if
                    AP == map_gl_to_pr[int(GL)] if season_cv.index[0] == "wind_ofs")

            elif "solar" in TECH:
                solar_reserve = quicksum((model.capacity_solar[PDD, GL] + model.capacity_solar_recon[PDD, GL]) * float(
                    season_cv[map_gl_to_pr[int(GL)]].iloc[0]) for PDD in pds[:ind + 1] for GL in gl if
                                         AP == map_gl_to_pr[int(GL)])
                exist_solar_reserve = quicksum(extant_wind_solar[pds.index(PD)][str(GL) + '.' + 'solar'] * float(
                    season_cv[map_gl_to_pr[int(GL)]].iloc[0]) for GL in gl if
                                               str(GL) + '.' + 'solar' in extant_wind_solar[0] and AP == map_gl_to_pr[
                                                   int(GL)])

            elif "hydro" in TECH:
                ror_hydro_reserve = quicksum(ror_hydro_capacity[PD + '.' + ABA] * float(season_cv.at[TECH, AP])
                                             for ABA in regions_per_province[AP]
                                             if season_cv.index[0] == "hydro_run")
                day_hydro_reserve = quicksum(day_hydro_capacity[PD + '.' + ABA] * float(season_cv.at[TECH, AP])
                                             for ABA in regions_per_province[AP]
                                             if season_cv.index[0] == "hydro_daily")
                month_hydro_reserve = quicksum(month_hydro_capacity[PD + '.' + ABA] * float(season_cv.at[TECH, AP])
                                               for ABA in regions_per_province[AP]
                                               if season_cv.index[0] == "hydro_monthly")

            else:
                thermal_reserve = quicksum((extant_thermal[pds[0] + '.' + ABA + '.' + TP] + quicksum(
                    model.capacity_therm[PDD, ABA, TP] - model.retire_therm[PDD, ABA, TP]
                    for PDD in pds[:ind + 1])) * float(season_cv.at[TECH, AP])
                                           for ABA in regions_per_province[AP] for TP in tplants if TECH == TP)

            cap_val += thermal_reserve + windon_reserve + windoff_reserve + exist_solar_reserve + exist_wind_reserve \
                       + solar_reserve + ror_hydro_reserve + day_hydro_reserve + month_hydro_reserve

            if hydro_development and "hydro" in TECH:
                ror_renewal_reserve = quicksum(
                    capacity_ror_renewal[HR_ROR] * model.ror_renewal_binary[PDD, HR_ROR] * float(season_cv.at[TECH, AP])
                    for PDD in pds[:ind + 1]
                    for HR_ROR in hr_ror
                    if season_cv.index[0] == "hydro_run")
                day_renewal_reserve = quicksum(
                    capacity_day_renewal[HR_DAY] * model.day_renewal_binary[PDD, HR_DAY] * float(season_cv.at[TECH, AP])
                    for PDD in pds[:ind + 1]
                    for HR_DAY in hr_day
                    if season_cv.index[0] == "hydro_daily")
                month_renewal_reserve = quicksum(
                    capacity_month_renewal[HR_MO] * model.month_renewal_binary[PDD, HR_MO] * float(
                        season_cv.at[TECH, AP])
                    for PDD in pds[:ind + 1]
                    for HR_MO in hr_mo
                    if season_cv.index[0] == "hydro_monthly")
                cap_val += ror_renewal_reserve + day_renewal_reserve + month_renewal_reserve

            if storage_continous:
                storage_reserve = quicksum(model.capacity_storage[PDD, ST, ABA] * float(season_cv.at[TECH, AP])
                                           for PDD in pds[:ind + 1]
                                           for ST in st for ABA in regions_per_province[AP] if ST == TECH
                                           )
                cap_val += storage_reserve

        if type(cap_val) is int or float:
            return Constraint.Skip
        else:
            return cap_val >= dem_val * (float(reserve_margin_aggregate) + 1)

proportional_expansion_period_1(model, PD, TECH, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
TECH

indicating the technology for which the calculation is relevant.

required
ABA

indicating the balancing area for which the calculation is relevant.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the total capacity of new generation capacity installed

during the first planning period (PD) across each balancing areas is constrained to end up being

proportional to the grid composition that currently exists, effectively mimicking as if there was no

new capacity for the first planning period.

Details
  • This function sums the new capacities of all generators across all balancing areas during the first planning period, and enforces a constraint that this total capacity must remain proportional to the current grid composition.
  • The constraint is significant for scenarios where the first planning period is close enough to present day that any significant new development by the end of the first planning period would be highly improbable.
Source code in COPPER.py
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
def proportional_expansion_period_1(model, PD, TECH, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        TECH: indicating the technology for which the calculation is relevant.
        ABA: indicating the balancing area for which the calculation is relevant.

    Returns:
        A Pyomo Constraint expression: Ensures that the total capacity of new generation capacity installed 
        during the first planning period (PD) across each balancing areas is constrained to end up being 
        proportional to the grid composition that currently exists, effectively mimicking as if there was no 
        new capacity for the first planning period.

    Details:
        - This function sums the new capacities of all generators across all balancing areas during the first 
        planning period, and enforces a constraint that this total capacity must remain proportional 
        to the current grid composition.
        - The constraint is significant for scenarios where the first planning period is close enough to present 
        day that any significant new development by the end of the first planning period would be highly improbable.
    """
    # calculate total extant capacity for balancing area
    extant_capacity_ba = sum(extant_capacity[key] for key in extant_capacity.keys() if key.startswith(PD + '.' + ABA))

    # calculate extant proprtion of balancing area grid composition for TECH
    tech_prop = (extant_capacity[PD + '.' + ABA + '.' + TECH] / extant_capacity_ba)

    # calculate total new capacity
    tot_new_capacity = quicksum(model.capacity_therm[PD, ABA, TP] for TP in tplants) \
        + quicksum(model.capacity_wind_ons[PD, GL] + model.capacity_wind_ons_recon[PD, GL] \
            + model.capacity_wind_ofs[PD, GL] + model.capacity_wind_ofs_recon[PD, GL] \
            + model.capacity_solar[PD, GL] + model.capacity_solar_recon[PD, GL] 
                for GL in gl if ABA == map_gl_to_ba[int(GL)]) \
        + quicksum(model.capacity_transmission[PD, ABA, ABBA] for ABBA in model.aba if ABA + '.' + ABBA in transmap)

    if storage_continous:
        tot_new_capacity += quicksum(model.capacity_storage[PD, ST, ABA] for ST in st)

    if hydro_development:
        tot_new_capacity += quicksum(capacity_ror_renewal[HR_ROR] * model.ror_renewal_binary[PD, HR_ROR] for HR_ROR in hr_ror 
                                 if ABA == hr_ror_location[HR_ROR]) \
            + quicksum(capacity_day_renewal[HR_DAY] * model.day_renewal_binary[PD, HR_DAY] for HR_DAY in hr_day
                       if ABA == hr_day_location[HR_DAY]) \
            + quicksum(capacity_month_renewal[HR_MO] * model.month_renewal_binary[PD, HR_MO] for HR_MO in hr_mo
                       if ABA == hr_month_location[HR_MO])

        if not storage_continous:
            tot_new_capacity += quicksum(capacity_pump_renewal[HR_PUMP] * model.pumphydro[PDD, HR_PUMP] for HR_PUMP in hr_pump
                                     if ABA == hr_pump_location[HR_PUMP])

    # apply constraint to relevant variable based on technology type
    new_capacity_tech = 0

    if TECH == 'wind_ons':
        new_capacity_tech = quicksum(model.capacity_wind_ons[PD, GL] + model.capacity_wind_ons_recon[PD, GL] 
                                     for GL in gl if ABA == map_gl_to_ba[int(GL)])
    elif TECH == 'wind_ofs':
        new_capacity_tech = quicksum(model.capacity_wind_ofs[PD, GL] + model.capacity_wind_ofs_recon[PD, GL] 
                                     for GL in gl if ABA == map_gl_to_ba[int(GL)])
    elif TECH == 'solar':
        new_capacity_tech = quicksum(model.capacity_solar[PD, GL] + model.capacity_solar_recon[PD, GL]
                                     for GL in gl if ABA == map_gl_to_ba[int(GL)])
    elif TECH in st and storage_continous:
        new_capacity_tech = model.capacity_storage[PD, ST, ABA]

    elif 'nuclear' in TECH:
        new_capacity_tech = model.capacity_therm[PD, ABA, TECH]

    # elif TECH in tplants:
    #     new_capacity_tech = model.capacity_therm[PD, ABA, TECH]

    # elif TECH == 'hydro_run' and hydro_development:
    #     new_capacity_tech = quicksum(capacity_ror_renewal[HR_ROR] * model.ror_renewal_binary[PD, HR_ROR] for HR_ROR in hr_ror 
    #                              if ABA == hr_ror_location[HR_ROR])
    # elif TECH == 'hydro_daily' and hydro_development:
    #     new_capacity_tech = quicksum(capacity_day_renewal[HR_DAY] * model.day_renewal_binary[PD, HR_DAY] for HR_DAY in hr_day
    #                                  if ABA == hr_day_location[HR_DAY])
    # elif TECH == 'hydro_monthly' and hydro_development:
    #     new_capacity_tech = quicksum(capacity_month_renewal[HR_MO] * model.month_renewal_binary[PD, HR_MO] for HR_MO in hr_mo
    #                                  if ABA == hr_month_location[HR_MO])

    # elif TECH == 'storage_PH' and hydro_development and not storage_continous:
    #     new_capacity_tech = quicksum(capacity_ror_renewal[HR_PUMP] * model.ror_renewal_binary[PD, HR_PUMP] for HR_PUMP in hr_pump 
    #                                  if ABA == hr_pump_location[HR_PUMP])

    if type(new_capacity_tech) is int:
        return Constraint.Skip
    else:
        return new_capacity_tech <= tech_prop * tot_new_capacity

provincialcarbonlimit(model, AP)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
AP

The provincial identifier where the emission limits are to be enforced.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the total carbon dioxide emissions from power plants

within the specified province (AP) do not exceed the regulatory emission limit for that province.

Details
  • This function calculates the total emissions by aggregating the carbon dioxide output from all thermal power plants in the province, factoring in the operational hours, the carbon emission factors of each plant type (carbondioxide[TP]), and the corresponding supply amount at the last planning period within each hour.
  • The emissions are normalized to a daily basis considering the number of run days in a year, allowing the model to conform to annual provincial emission limits (carbon_limit[AP]).
  • This constraint is crucial for adhering to provincial or national environmental policies aimed at reducing greenhouse gas emissions from the power sector.
Source code in COPPER.py
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
def provincialcarbonlimit(model, AP):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        AP: The provincial identifier where the emission limits are to be enforced.

    Returns:
        A Pyomo Constraint expression: Ensures that the total carbon dioxide emissions from power plants 
        within the specified province (AP) do not exceed the regulatory emission limit for that province.

    Details:
        - This function calculates the total emissions by aggregating the carbon dioxide output from all thermal power 
        plants in the province, factoring in the operational hours, the carbon emission factors of each plant type 
        (`carbondioxide[TP]`), and the corresponding supply amount at the last planning period within each hour.
        - The emissions are normalized to a daily basis considering the number of run days in a year, allowing the model 
        to conform to annual provincial emission limits (`carbon_limit[AP]`).
        - This constraint is crucial for adhering to provincial or national environmental policies aimed at reducing 
        greenhouse gas emissions from the power sector.
    """
    return quicksum(
        model.supply[pds[-1], H, ABA, TP, FT] * carbondioxide[TP + '.' + FT] / 1000000 for H in h for TP in tplants
        for ABA in aba
        if AP in ABA for FT in model.ftype if FT in useful_fuels[TP].split('.')) <= carbon_limit[
        AP] / repday_scaling

pumcap(model, PD, ST, H, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2040') during which the constraint is applied.

required
ST

The storage type identifier as a string, specifically referring to types like 'storage_LI'.

required
H

The hour within the planning period for which the storage capacity constraint is relevant.

required
ABA

The balancing area as a string, indicating where the energy storage is located.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the energy content in the storage facility at hour H during the planning

period PD to not exceed the sum of the base area storage capacity, potential new capacity additions, and any

capacity attributed to hydro development, scaled by the number of operational hours the storage can cover.

Details
  • This function calculates the maximum allowable storage capacity for a given type of storage in a specific balancing area and hour, considering both newly constructed capacity and capacity from hydro development.
  • The capacity for new construction (pump_new_con) is calculated differently based on the storage type and the planning period, with specific conditions for 'storage_LI' starting from 2040.
  • If hydro development is active and storage is not continuous, additional capacity from pumped hydro retrofits (pump_integer_cap) is considered, based on the location-specific renewal capacity.
  • The total permissible storage capacity is calculated by adding the base storage capacity from extant_storage to the capacities calculated from new construction and hydro development, then multiplying by the operational hours specified in storage_hours[ST].
Source code in COPPER.py
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
def pumcap(model, PD, ST, H, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2040') during which the constraint is applied.
        ST: The storage type identifier as a string, specifically referring to types like 'storage_LI'.
        H: The hour within the planning period for which the storage capacity constraint is relevant.
        ABA: The balancing area as a string, indicating where the energy storage is located.

    Returns:
        A Pyomo Constraint expression: Limits the energy content in the storage facility at hour H during the planning 
        period PD to not exceed the sum of the base area storage capacity, potential new capacity additions, and any 
        capacity attributed to hydro development, scaled by the number of operational hours the storage can cover.

    Details:
        - This function calculates the maximum allowable storage capacity for a given type of storage in a specific 
          balancing area and hour, considering both newly constructed capacity and capacity from hydro development.
        - The capacity for new construction (`pump_new_con`) is calculated differently based on the storage type and 
          the planning period, with specific conditions for 'storage_LI' starting from 2040.
        - If hydro development is active and storage is not continuous, additional capacity from pumped hydro retrofits 
          (`pump_integer_cap`) is considered, based on the location-specific renewal capacity.
        - The total permissible storage capacity is calculated by adding the base storage capacity from `extant_storage` 
          to the capacities calculated from new construction and hydro development, then multiplying by the operational 
          hours specified in `storage_hours[ST]`.
    """
    ind = pds.index(PD)
    pump_new_con = 0
    pump_integer_cap = 0

    if storage_continous:
        if ST == 'storage_LI' and int(PD) >= 2040:
            pump_new_con = quicksum(model.capacity_storage[PDD, ST, ABA] for PDD in pds[ind - 2:ind + 1])
        else:
            pump_new_con = quicksum(model.capacity_storage[PDD, ST, ABA] for PDD in pds[:ind + 1])
    if hydro_development and not storage_continous:
        pump_integer_cap = quicksum(
            model.pumphydro[PDD, HR_PUMP] * capacity_pump_renewal[HR_PUMP] for PDD in pds[:ind + 1] for HR_PUMP in
            hr_pump if hr_pump_location[HR_PUMP] == ABA)
    return model.storageenergy[PD, ST, H, ABA] <= (
            extant_storage[PD + '.' + ABA + '.' + ST] + pump_integer_cap + pump_new_con) * storage_hours[ST]

pumpen(model, PD, ST, H, ABA)

pumped hydro energy storage - energy balance constraint Parameters: model: A Pyomo model instance containing all relevant decision variables, parameters, and sets. PD: The planning period as a string (e.g., '2025') during which the energy storage calculations are performed. ST: The storage type identifier as a string. H: The hour within the planning period for which the energy balance is calculated. ABA: The balancing area as a string, indicating where the energy storage is located.

Returns:

Type Description

A Pyomo Constraint expression: Equates the energy content in storage at hour H + time_diff[H] to the energy

content at hour H, adjusted for outflows, inflows, and efficiency losses/gains.

Details
  • The function calculates the energy balance for a storage unit by updating its energy content from one hour to the next.
  • It considers the energy output from the storage (model.storageout), the energy input into the storage (model.storagein), and the storage efficiency (storage_efficiency[ST]), which scales the energy input.
  • The time_diff[H] is used to link consecutive operational hours, ensuring continuity in the energy accounting.
Source code in COPPER.py
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
def pumpen(model, PD, ST, H, ABA):
    """
    pumped hydro energy storage - energy balance constraint
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the energy storage calculations are performed.
        ST: The storage type identifier as a string.
        H: The hour within the planning period for which the energy balance is calculated.
        ABA: The balancing area as a string, indicating where the energy storage is located.

    Returns:
        A Pyomo Constraint expression: Equates the energy content in storage at hour H + time_diff[H] to the energy 
        content at hour H, adjusted for outflows, inflows, and efficiency losses/gains.

    Details:
        - The function calculates the energy balance for a storage unit by updating its energy content from one hour to the next.
        - It considers the energy output from the storage (`model.storageout`), the energy input into the storage (`model.storagein`),
          and the storage efficiency (`storage_efficiency[ST]`), which scales the energy input.
        - The `time_diff[H]` is used to link consecutive operational hours, ensuring continuity in the energy accounting.
    """
    return model.storageenergy[PD, ST, H + time_diff[H], ABA] == model.storageenergy[PD, ST, H, ABA] - model.storageout[
        PD, ST, H, ABA] + model.storagein[PD, ST, H, ABA] * storage_efficiency[ST]

pumpretrofitlimit(model, PD, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
ABA

The balancing area as a string, indicating where the capacity constraint is being considered.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the total retrofitted pumped hydro storage capacity up to the

planning period PD in the balancing area ABA to not exceed the sum of the daily and monthly hydro generator capacities

at the start of the period series, multiplied by a config.toml limit factor (pump_ret_limit).

Details
  • This function sums the capacities of pumped hydro storage (identified by 'storage_PH') for all previous periods up to and including PD.
  • The limit for the total capacity is set based on a percentage of the initial day and month hydro capacities, facilitating controlled growth in storage capacity based on existing hydro infrastructure.
  • The calculation takes into account the sum of daily and monthly hydro reservoir capacities at the model's start and applies a factor (pump_ret_limit) to set the maximum allowable retrofitted capacity.
Source code in COPPER.py
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
def pumpretrofitlimit(model, PD, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        ABA: The balancing area as a string, indicating where the capacity constraint is being considered.

    Returns:
        A Pyomo Constraint expression: Limits the total retrofitted pumped hydro storage capacity up to the 
        planning period PD in the balancing area ABA to not exceed the sum of the daily and monthly hydro generator capacities 
        at the start of the period series, multiplied by a config.toml limit factor (`pump_ret_limit`).

    Details:
        - This function sums the capacities of pumped hydro storage (identified by 'storage_PH') for all previous 
        periods up to and including PD.
        - The limit for the total capacity is set based on a percentage of the initial day and month hydro capacities,
        facilitating controlled growth in storage capacity based on existing hydro infrastructure.
        - The calculation takes into account the sum of daily and monthly hydro reservoir capacities at the model's start and
        applies a factor (`pump_ret_limit`) to set the maximum allowable retrofitted capacity.
    """
    ind = pds.index(PD)
    return quicksum(model.capacity_storage[PDD, 'storage_PH', ABA] for PDD in pds[:ind + 1]) <= (
            day_hydro_capacity[pds[0] + '.' + ABA] + month_hydro_capacity[pds[0] + '.' + ABA]) * pump_ret_limit

ramp_down(model, PD, H, ABA, TP)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
H

The hour within the planning period for which the ramp-down constraint is relevant.

required
ABA

The balancing area as a string, indicating where the thermal plant is located.

required
TP

The type of thermal plant as a string.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the decrease in output of a thermal power plant from one hour to the next

does not exceed the allowable ramp, calculated as a percentage of the plant's capacity minus any retired capacity.

The output at hour H + time_diff[H] must not be less than the output at hour H minus this calculated ramp-down capacity.

Details
  • This function constrains the rate at which thermal power plants decrease their generation, adhering to a specified ramp rate percentage.
  • The ramp rate (ramp_rate_percent[TP]) is applied to the sum of the plant's capacity after accounting for retirements and the initial extant capacity.
  • time_diff[H] represents the time difference to the next relevant hour, adjusting the ramp rate accordingly to ensure operational safety and stability in power grid management.
Source code in COPPER.py
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
def ramp_down(model, PD, H, ABA, TP):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        H: The hour within the planning period for which the ramp-down constraint is relevant.
        ABA: The balancing area as a string, indicating where the thermal plant is located.
        TP: The type of thermal plant as a string.

    Returns:
        A Pyomo Constraint expression: Ensures that the decrease in output of a thermal power plant from one hour to the next 
        does not exceed the allowable ramp, calculated as a percentage of the plant's capacity minus any retired capacity. 
        The output at hour H + time_diff[H] must not be less than the output at hour H minus this calculated ramp-down capacity.

    Details:
        - This function constrains the rate at which thermal power plants decrease their generation, adhering to a specified 
          ramp rate percentage.
        - The ramp rate (`ramp_rate_percent[TP]`) is applied to the sum of the plant's capacity after accounting for retirements 
          and the initial extant capacity.
        - `time_diff[H]` represents the time difference to the next relevant hour, adjusting the ramp rate accordingly to 
          ensure operational safety and stability in power grid management.
    """
    ind = pds.index(PD)
    for FT in model.ftype:
        if FT in useful_fuels[TP].split('.'):
            return model.supply[PD, H + time_diff[H], ABA, TP, FT] >= model.supply[PD, H, ABA, TP, FT] - (quicksum(
                model.capacity_therm[PDD, ABA, TP] - model.retire_therm[PDD, ABA, TP] 
                for PDD in pds[:ind + 1]) + extant_thermal[pds[0] + '.' + ABA + '.' + TP]) * ramp_rate_percent[TP] * time_diff[H]

ramp_up(model, PD, H, ABA, TP)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
H

The hour within the planning period for which the ramp-up constraint is relevant.

required
ABA

The balancing area as a string, indicating where the thermal plant is located.

required
TP

The type of thermal plant as a string.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the increase in output of a thermal power plant from one hour to the next.

The output at hour H + time_diff[H] cannot exceed the output at hour H plus the allowable ramp based on

the plant's ramp rate percentage and its capacity after accounting for retirements.

Details
  • This constraint ensures that the rate at which thermal power plants increase their generation does not exceed their specified ramp rate capacity.
  • The ramp rate (ramp_rate_percent[TP]) is applied to the sum of the plant's capacity after accounting for retirements and the initial extant capacity.
  • time_diff[H] represents the time difference to the next relevant hour, scaling the ramp rate accordingly to maintain stability and reliability in power supply.
Source code in COPPER.py
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
def ramp_up(model, PD, H, ABA, TP):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        H: The hour within the planning period for which the ramp-up constraint is relevant.
        ABA: The balancing area as a string, indicating where the thermal plant is located.
        TP: The type of thermal plant as a string.

    Returns:
        A Pyomo Constraint expression: Limits the increase in output of a thermal power plant from one hour to the next. 
        The output at hour H + time_diff[H] cannot exceed the output at hour H plus the allowable ramp based on 
        the plant's ramp rate percentage and its capacity after accounting for retirements.

    Details:
        - This constraint ensures that the rate at which thermal power plants increase their generation does not exceed 
          their specified ramp rate capacity.
        - The ramp rate (`ramp_rate_percent[TP]`) is applied to the sum of the plant's capacity after accounting for 
          retirements and the initial extant capacity.
        - `time_diff[H]` represents the time difference to the next relevant hour, scaling the ramp rate accordingly 
          to maintain stability and reliability in power supply.
    """
    ind = pds.index(PD)
    for FT in model.ftype:
        if FT in useful_fuels[TP].split('.'):
            return model.supply[PD, H + time_diff[H], ABA, TP, FT] <= model.supply[PD, H, ABA, TP, FT] + (quicksum(
                model.capacity_therm[PDD, ABA, TP] - model.retire_therm[PDD, ABA, TP] 
                for PDD in pds[:ind + 1]) + extant_thermal[pds[0] + '.' + ABA + '.' + TP]) * ramp_rate_percent[TP] * time_diff[H]

refurb_limits(model, PD, ABA, RP)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
ABA

The balancing area as a string, indicating where the diesel generators are located.

required
RP

The identifier for the refurbished plant type.

required

Returns:

Type Description

A Pyomo Constraint expression: Enforces that the total new capacity attributed to refurbished plants

in the specified balancing area must be less than or equal to the total base capacity retired.

Details
  • This function aims to ensure that thermal generators categorized as "refurbish" does not exceed the retired amount of base capacity of the thermal generator.
Source code in COPPER.py
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
def refurb_limits(model, PD, ABA, RP):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        ABA: The balancing area as a string, indicating where the diesel generators are located.
        RP: The identifier for the refurbished plant type.

    Returns:
        A Pyomo Constraint expression: Enforces that the total new capacity attributed to refurbished plants
        in the specified balancing area must be less than or equal to the total base capacity retired.

    Details:
        - This function aims to ensure that thermal generators categorized as "refurbish" does not 
        exceed the retired amount of base capacity of the thermal generator.
    """
    return model.capacity_therm[PD, ABA, RP] <= quicksum(model.retire_therm[PD, ABA, TP] for TP in refurb_tplant[RP])

refurb_retrofit_limits(model, PD, ABA, RP)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
ABA

The balancing area as a string, indicating where the generators are located.

required
RP

Dictionary entry of refurbished plant and "related" CCS retrofit plant names (e.g., {'gasCC_refurbish': 'gasCC_ccs_retrofit'}).

required

Returns:

Type Description

Pyomo Constraint expression: Ensures that total new capacity attributed to refurbished plants in the

planning period PD, within the balancing area ABA and of type TP, does not exceed the initial extant

base capacity minus prior CCS retrofits up to the current planning period.

Details
  • This function aims to ensure that thermal generators categorized as "refurbish" do not exceed the retired amount of base capacity of the thermal generator, with consideration given to plants that have been retrofit with CCS and are not eligible to be refurbished.
Source code in COPPER.py
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
def refurb_retrofit_limits(model, PD, ABA, RP):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        ABA: The balancing area as a string, indicating where the generators are located.
        RP: Dictionary entry of refurbished plant and "related" CCS retrofit plant names (e.g., {'gasCC_refurbish': 'gasCC_ccs_retrofit'}).

    Returns:
        Pyomo Constraint expression: Ensures that total new capacity attributed to refurbished plants in the 
        planning period PD, within the balancing area ABA and of type TP, does not exceed the initial extant 
        base capacity minus prior CCS retrofits up to the current planning period.

    Details:
        - This function aims to ensure that thermal generators categorized as "refurbish" do not 
        exceed the retired amount of base capacity of the thermal generator, with consideration given to plants 
        that have been retrofit with CCS and are not eligible to be refurbished.
    """
    refurb_plant = RP
    retro_plant = related_tplant_options[RP]
    return model.capacity_therm[PD, ABA, refurb_plant] <= quicksum(
        model.retire_therm[PD, ABA, TP] for TP in refurb_tplant[refurb_plant]) - model.capacity_therm[PD, ABA, retro_plant]

renewal_daycap(model, PD, H, HR_DAY)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
H

The hour within the planning period for which the capacity constraint is relevant.

required
ABA

The balancing area as a string, indicating where the day-storage hydro facility is located.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the hourly output from day-storage hydroelectric facilities in the

specified balancing area (ABA) during the specified hour (H) of the planning period (PD) to not exceed

the designated capacity limit.

Details
  • This function ensures that the output from day-storage hydro facilities does not surpass the maximum capacity specified for that facility in the balancing area.
  • The maximum capacity (day_hydro_capacity) is predefined for each planning period and balancing area, reflecting operational and regulatory limits that ensure the sustainability and efficiency of hydro facility operations.
Source code in COPPER.py
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
def renewal_daycap(model, PD, H, HR_DAY):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        H: The hour within the planning period for which the capacity constraint is relevant.
        ABA: The balancing area as a string, indicating where the day-storage hydro facility is located.

    Returns:
        A Pyomo Constraint expression: Limits the hourly output from day-storage hydroelectric facilities in the 
        specified balancing area (ABA) during the specified hour (H) of the planning period (PD) to not exceed 
        the designated capacity limit.

    Details:
        - This function ensures that the output from day-storage hydro facilities does not surpass the maximum capacity 
        specified for that facility in the balancing area.
        - The maximum capacity (`day_hydro_capacity`) is predefined for each planning period and balancing area, reflecting
        operational and regulatory limits that ensure the sustainability and efficiency of hydro facility operations.
    """
    return model.dayrenewalout[PD, H, HR_DAY] <= capacity_day_renewal[HR_DAY] * model.day_renewal_binary[PD, HR_DAY]

renewal_dayminflow(model, PD, H, HR_DAY)

Parameters: model: A Pyomo model instance containing all relevant decision variables, parameters, and sets. PD: The planning period as a string (e.g., '2025') during which the constraint is applied. H: The hour within the planning period for which the minimum flow constraint is relevant. HR_DAY: The identifier for the renewable day-storage hydro facility.

Returns: A Pyomo Constraint expression: Ensures that the hourly output from renewable day-storage hydro facilities, identified by HR_DAY, during the specified hour (H) of the planning period (PD) does not fall below a calculated minimum flow requirement.

Details: - The minimum flow is calculated as the product of the renewable facility's capacity (capacity_day_renewal[HR_DAY]), a predefined minimum flow coefficient (hydro_minflow), and a binary variable that indicates whether the facility is operational (model.day_renewal_binary[PD, HR_DAY]) during that hour. - This constraint is crucial for maintaining ecological balance and ensuring regulatory compliance regarding water flow rates from hydro facilities.

Source code in COPPER.py
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
def renewal_dayminflow(model, PD, H, HR_DAY):
    """
    Parameters:
    model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
    PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
    H: The hour within the planning period for which the minimum flow constraint is relevant.
    HR_DAY: The identifier for the renewable day-storage hydro facility.

    Returns:
    A Pyomo Constraint expression: Ensures that the hourly output from renewable day-storage hydro facilities,
    identified by HR_DAY, during the specified hour (H) of the planning period (PD) does not fall below a 
    calculated minimum flow requirement.

    Details:
    - The minimum flow is calculated as the product of the renewable facility's capacity (`capacity_day_renewal[HR_DAY]`), 
      a predefined minimum flow coefficient (`hydro_minflow`), and a binary variable that indicates whether the facility 
      is operational (`model.day_renewal_binary[PD, HR_DAY]`) during that hour.
    - This constraint is crucial for maintaining ecological balance and ensuring regulatory compliance regarding 
      water flow rates from hydro facilities.
    """
    return model.dayrenewalout[PD, H, HR_DAY] >= capacity_day_renewal[HR_DAY] * hydro_minflow * \
        model.day_renewal_binary[PD, HR_DAY]

renewal_monthcap(model, PD, H, HR_MO)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
H

The hour within the planning period for which the capacity constraint is relevant.

required
HR_MO

The identifier for the renewable month-storage hydro facility.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the hourly output from renewable month-storage hydroelectric facilities,

identified by HR_MO, during the specified hour (H) of the planning period (PD) based on its designated

renewable capacity and whether it is operational during that period.

Details
  • This function ensures that the output from renewable month-storage hydro facilities does not exceed the maximum capacity designated for that facility, multiplied by a binary variable indicating whether the facility is operational (model.month_renewal_binary[PD, HR_MO]) in that hour.
  • The constraint uses the product of the facility's renewable capacity (capacity_month_renewal[HR_MO]) and the operational status to determine the allowable output, aligning with strategies for flexible and sustainable hydro facility operation.
Source code in COPPER.py
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
def renewal_monthcap(model, PD, H, HR_MO):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        H: The hour within the planning period for which the capacity constraint is relevant.
        HR_MO: The identifier for the renewable month-storage hydro facility.

    Returns:
        A Pyomo Constraint expression: Limits the hourly output from renewable month-storage hydroelectric facilities, 
        identified by HR_MO, during the specified hour (H) of the planning period (PD) based on its designated 
        renewable capacity and whether it is operational during that period.

    Details:
        - This function ensures that the output from renewable month-storage hydro facilities does not exceed the
        maximum capacity designated for that facility, multiplied by a binary variable indicating whether the facility 
        is operational (`model.month_renewal_binary[PD, HR_MO]`) in that hour.
        - The constraint uses the product of the facility's renewable capacity (`capacity_month_renewal[HR_MO]`) and 
        the operational status to determine the allowable output, aligning with strategies for flexible and sustainable 
        hydro facility operation.
    """
    return model.monthrenewalout[PD, H, HR_MO] <= capacity_month_renewal[HR_MO] * model.month_renewal_binary[
        PD, HR_MO]

renewal_monthminflow(model, PD, H, HR_MO)

Parameters: model: A Pyomo model instance containing all relevant decision variables, parameters, and sets. PD: The planning period as a string (e.g., '2025') during which the constraint is applied. H: The hour within the planning period for which the minimum flow constraint is relevant. HR_MO: The identifier for the renewable month-storage hydro facility.

Returns: A Pyomo Constraint expression: Ensures that the hourly output from renewable month-storage hydro facilities, identified by HR_MO, during the specified hour (H) of the planning period (PD) does not fall below a calculated minimum flow requirement.

Details: - The minimum flow is determined by multiplying the renewable facility's designated capacity (capacity_month_renewal[HR_MO]), a specific minimum flow coefficient (hydro_minflow), and a binary operation status indicator (model.month_renewal_binary[PD, HR_MO]). - This constraint supports regulatory compliance and ecological stability by ensuring consistent water flow from renewable hydro facilities.

Source code in COPPER.py
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
def renewal_monthminflow(model, PD, H, HR_MO):
    """
    Parameters:
    model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
    PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
    H: The hour within the planning period for which the minimum flow constraint is relevant.
    HR_MO: The identifier for the renewable month-storage hydro facility.

    Returns:
    A Pyomo Constraint expression: Ensures that the hourly output from renewable month-storage hydro facilities,
    identified by HR_MO, during the specified hour (H) of the planning period (PD) does not fall below a 
    calculated minimum flow requirement.

    Details:
    - The minimum flow is determined by multiplying the renewable facility's designated capacity (`capacity_month_renewal[HR_MO]`), 
    a specific minimum flow coefficient (`hydro_minflow`), and a binary operation status indicator (`model.month_renewal_binary[PD, HR_MO]`).
    - This constraint supports regulatory compliance and ecological stability by ensuring consistent water flow from 
    renewable hydro facilities.
    """
    return model.monthrenewalout[PD, H, HR_MO] >= capacity_month_renewal[HR_MO] * hydro_minflow * \
        model.month_renewal_binary[PD, HR_MO]

retire(model, PD, ABA, TP)

Constrains the retirement of thermal plants within a specific balancing area and type to not exceed the sum of extant capacity and any capacity added minus retirements in prior periods. This ensures that retirements are limited to actual existing capacities.

Parameters:

Name Type Description Default
model pyomo model

A Pyomo model instance that contains decision variables, parameters, and sets relevant to the energy model.

required
PD string

The planning period as a string (e.g., '2025') during which the retirement constraint is applied.

required
ABA string

The balancing area as a string where the thermal plant is located.

required
TP string

The type of the thermal plant as a string, the list of possible strings comes from tplants which comes from thermal plants in generation_type_data.csv input sheet.

required

Returns:

Type Description

Pyomo Constraint expression: Ensures that the thermal plant retirements in the planning period PD,

within the balancing area ABA and of type TP, do not exceed the initial extant capacity plus any net

increase in capacity from new construction minus prior retirements up to the current planning period.

Details
  • The function calculates the permissible retirement capacity by adding the initial extant capacity of the plant (at the beginning of the series) to the net capacity changes (new capacity minus retirements) up to the period just before PD.
  • This constraint helps in accurate modeling of power system dynamics, ensuring retirements are within realistic limits based on actual plant capacities.
Source code in COPPER.py
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
def retire(model, PD, ABA, TP):
    """
    Constrains the retirement of thermal plants within a specific balancing area and type to not exceed the 
    sum of extant capacity and any capacity added minus retirements in prior periods. This ensures that 
    retirements are limited to actual existing capacities.

    Parameters:
        model (pyomo model): A Pyomo model instance that contains decision variables, parameters, and sets relevant to the energy model.
        PD (string): The planning period as a string (e.g., '2025') during which the retirement constraint is applied.
        ABA (string): The balancing area as a string where the thermal plant is located.
        TP (string): The type of the thermal plant as a string, the list of possible strings comes from tplants which comes from thermal plants in generation_type_data.csv input sheet.

    Returns:
        Pyomo Constraint expression: Ensures that the thermal plant retirements in the planning period PD,
        within the balancing area ABA and of type TP, do not exceed the initial extant capacity plus any net
        increase in capacity from new construction minus prior retirements up to the current planning period.

    Details:
        - The function calculates the permissible retirement capacity by adding the initial extant capacity of the plant
          (at the beginning of the series) to the net capacity changes (new capacity minus retirements) up to the 
          period just before PD.
        - This constraint helps in accurate modeling of power system dynamics, ensuring retirements are within 
          realistic limits based on actual plant capacities.
    """
    ind = pds.index(PD)
    return model.retire_therm[PD, ABA, TP] <= extant_thermal[pds[0] + '.' + ABA + '.' + TP] + quicksum(
        model.capacity_therm[PDD, ABA, TP] - model.retire_therm[PDD, ABA, TP] for PDD in pds[:ind])

retire_retrofits(model, PD, ABA, RP)

Forces capacity that was retrofit with CCS to retire the base capacity, defined as the sum of the extant capacity and any capacity added minus retirements in prior periods. This ensures that CCS retrofits are added to existing plants and not creating new plants.

Parameters:

Name Type Description Default
model pyomo model

A Pyomo model instance that contains decision variables, parameters, and sets relevant to the energy model.

required
PD string

The planning period as a string (e.g., '2025') during which the retirement constraint is applied.

required
ABA string

The balancing area as a string where the thermal plant is located.

required
RP

The identifier for the retrofit plant type.

required

Returns:

Type Description

Pyomo Constraint expression: Ensures that the thermal plant retirements in the planning period PD,

within the balancing area ABA, is greater than or equal to the CCS retrofits in the current planning period.

Details
  • This constraint helps in accurate modeling of power system dynamics, ensuring the addition of CCS retrofits does not create new plants, but keeps the balance of plants constant.
Source code in COPPER.py
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
def retire_retrofits(model, PD, ABA, RP):
    """
    Forces capacity that was retrofit with CCS to retire the base capacity, defined as the sum of the extant 
    capacity and any capacity added minus retirements in prior periods. This ensures that CCS retrofits are 
    added to existing plants and not creating new plants.

    Parameters:
        model (pyomo model): A Pyomo model instance that contains decision variables, parameters, and sets relevant to the energy model.
        PD (string): The planning period as a string (e.g., '2025') during which the retirement constraint is applied.
        ABA (string): The balancing area as a string where the thermal plant is located.
        RP: The identifier for the retrofit plant type.

    Returns:
        Pyomo Constraint expression: Ensures that the thermal plant retirements in the planning period PD,
        within the balancing area ABA, is greater than or equal to the CCS retrofits in the current planning period.

    Details:
        - This constraint helps in accurate modeling of power system dynamics, ensuring the addition of CCS retrofits 
        does not create new plants, but keeps the balance of plants constant.
    """
    return quicksum(model.retire_therm[PD, ABA, TP] for TP in retrofit_tplant[RP]) >= model.capacity_therm[PD, ABA, RP]

retrofit_limits(model, PD, ABA, RP)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
ABA

The balancing area as a string, indicating where the diesel generators are located.

required
RP

The identifier for the retrofit plant type.

required

Returns:

Type Description

Pyomo Constraint expression: Ensures that total new capacity attributed to CCS retrofits in the

planning period PD, within the balancing area ABA and of type TP, does not exceed the initial extant

base capacity minus prior CCS retrofits up to the current planning period.

Details
  • This function aims to ensure that thermal generators categorized as "ccs_retrofit" does not exceed the amount of base capacity of the thermal generator.
Source code in COPPER.py
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
def retrofit_limits(model, PD, ABA, RP):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        ABA: The balancing area as a string, indicating where the diesel generators are located.
        RP: The identifier for the retrofit plant type.

    Returns:
        Pyomo Constraint expression: Ensures that total new capacity attributed to CCS retrofits in the 
        planning period PD, within the balancing area ABA and of type TP, does not exceed the initial extant 
        base capacity minus prior CCS retrofits up to the current planning period.

    Details:
        - This function aims to ensure that thermal generators categorized as "ccs_retrofit" does not 
        exceed the amount of base capacity of the thermal generator.
    """
    ind = pds.index(PD)
    return model.capacity_therm[PD, ABA, RP] <= quicksum(extant_thermal[pds[0] + '.' + ABA + '.' + TP] for TP in retrofit_tplant[RP]) \
                                                - quicksum(model.capacity_therm[PDD, ABA, RP] for PDD in pds[:ind])

ror_onetime(model, PD, HR_ROR)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
HR_ROR

The identifier for the run-of-river (RoR) hydro facility.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that if a run-of-river hydro facility's renewal is activated in one

planning period, it must remain activated in all subsequent periods.

Details
  • This function imposes a non-decreasing constraint on the binary variable associated with the renewal of run-of-river hydro facilities. If the binary variable is set to 1 (activated) in any given period, it cannot be set back to 0 in any future period.
  • The constraint leverages a time series approach, where the binary status of the renewal in the current period (PD) must be greater than or equal to its status in the previous period. This enforces a policy or operational requirement that once renewal is committed, the decision cannot be reversed.
Source code in COPPER.py
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
def ror_onetime(model, PD, HR_ROR):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        HR_ROR: The identifier for the run-of-river (RoR) hydro facility.

    Returns:
        A Pyomo Constraint expression: Ensures that if a run-of-river hydro facility's renewal is activated in one 
        planning period, it must remain activated in all subsequent periods.

    Details:
        - This function imposes a non-decreasing constraint on the binary variable associated with the renewal of 
        run-of-river hydro facilities. If the binary variable is set to 1 (activated) in any given period, it cannot 
        be set back to 0 in any future period.
        - The constraint leverages a time series approach, where the binary status of the renewal in the current 
        period (`PD`) must be greater than or equal to its status in the previous period. This enforces a policy 
        or operational requirement that once renewal is committed, the decision cannot be reversed.
    """
    return (model.ror_renewal_binary[PD, HR_ROR]
            >= model.ror_renewal_binary[pds[pds.index(PD) - 1], HR_ROR])

rpf_solar_max(model, PD, AP)

Constraint function for renewable portfolio standards on maximum solar capacity.

Parameters: model (pyomo.environ.ConcreteModel): Pyomo model instance. PD (str): Period identifier. AP (str): Area or region identifier.

Returns: pyomo.environ.Constraint: A Pyomo constraint representing the requirement that the total capacity of solar energy sources does not exceed the maximum solar capacity standards for the given period and area.

The function calculates the total solar capacity from existing and proposed installations and ensures it does not exceed the maximum capacity standards specified in the 'solar_max_ap_limit' dictionary for the given period and area. These values are read from the renewable_portfolio_max.csv input sheet

Source code in COPPER.py
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
4647
4648
4649
def rpf_solar_max(model, PD, AP):
    """
    Constraint function for renewable portfolio standards on maximum solar capacity.

    Parameters:
    model (pyomo.environ.ConcreteModel): Pyomo model instance.
    PD (str): Period identifier.
    AP (str): Area or region identifier.

    Returns:
    pyomo.environ.Constraint: A Pyomo constraint representing the requirement that the total
    capacity of solar energy sources does not exceed the maximum solar capacity standards
    for the given period and area.

    The function calculates the total solar capacity from existing and proposed installations
    and ensures it does not exceed the maximum capacity standards specified in the 'solar_max_ap_limit'
    dictionary for the given period and area. These values are read from the renewable_portfolio_max.csv input sheet
    """
    ind = pds.index(PD)
    return quicksum(
        (model.capacity_solar[PDD, GL] + model.capacity_solar_recon[PDD, GL]) for PDD in pds[:ind + 1] for GL in gl
        if AP == map_gl_to_pr[int(GL)]) + quicksum(
        extant_wind_solar[pds.index(PD)][str(GL) + '.' + 'solar'] for GL in gl if
        str(GL) + '.' + 'solar' in extant_wind_solar[0] and AP == map_gl_to_pr[int(GL)]) <= solar_max_ap_limit[PD][
        AP]

rpf_solar_min(model, PD, AP)

Constraint function for renewable portfolio standards on minimum solar capacity.

Parameters: model (pyomo.environ.ConcreteModel): Pyomo model instance. PD (str): Period identifier. AP (str): Area or region identifier.

Returns: pyomo.environ.Constraint: A Pyomo constraint representing the requirement that the total capacity of solar energy sources satisfies the minimum solar capacity standards for the given period and area.

The function calculates the total solar capacity from existing and proposed installations and ensures it meets or exceeds the minimum capacity standards specified in the 'solar_min_ap_limit' dictionary for the given period and area. These minimum values are defined in renewable_portfolio_min.csv

Source code in COPPER.py
4655
4656
4657
4658
4659
4660
4661
4662
4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
def rpf_solar_min(model, PD, AP):
    """
    Constraint function for renewable portfolio standards on minimum solar capacity.

    Parameters:
    model (pyomo.environ.ConcreteModel): Pyomo model instance.
    PD (str): Period identifier.
    AP (str): Area or region identifier.

    Returns:
    pyomo.environ.Constraint: A Pyomo constraint representing the requirement that the total
    capacity of solar energy sources satisfies the minimum solar capacity standards
    for the given period and area.

    The function calculates the total solar capacity from existing and proposed installations
    and ensures it meets or exceeds the minimum capacity standards specified in the 'solar_min_ap_limit'
    dictionary for the given period and area. These minimum values are defined in renewable_portfolio_min.csv
    """
    ind = pds.index(PD)
    return quicksum(
        (model.capacity_solar[PDD, GL] + model.capacity_solar_recon[PDD, GL]) for PDD in pds[:ind + 1] for GL in gl
        if AP == map_gl_to_pr[int(GL)]) + quicksum(
        extant_wind_solar[pds.index(PD)][str(GL) + '.' + 'solar'] for GL in gl if
        str(GL) + '.' + 'solar' in extant_wind_solar[0] and AP == map_gl_to_pr[int(GL)]) >= solar_min_ap_limit[PD][
        AP]

rpf_wind_ofs_max(model, PD, AP)

see rpf_wind_ons_max() for details, same implementation but for wind offshore generation

Source code in COPPER.py
4566
4567
4568
4569
4570
4571
4572
4573
4574
def rpf_wind_ofs_max(model, PD, AP):
    """see rpf_wind_ons_max() for details, same implementation but for wind offshore generation"""
    ind = pds.index(PD)
    return quicksum(
        (model.capacity_wind_ofs[PDD, GL] + model.capacity_wind_ofs_recon[PDD, GL]) for PDD in pds[:ind + 1] for GL
        in gl if AP == map_gl_to_pr[int(GL)]) + quicksum(
        extant_wind_solar[pds.index(PD)][str(GL) + '.' + 'wind_ofs'] for GL in gl if
        str(GL) + '.' + 'wind_ofs' in extant_wind_solar[0] and AP == map_gl_to_pr[int(GL)]) <= \
        wind_ofs_max_ap_limit[PD][AP]

rpf_wind_ofs_min(model, PD, AP)

see rpf_wind_ons_min() for details, same implementation but for wind offshore generation except input values are read from These minimum values are defined in renewable_portfolio_max.csv

Source code in COPPER.py
4611
4612
4613
4614
4615
4616
4617
4618
4619
def rpf_wind_ofs_min(model, PD, AP):
    """see rpf_wind_ons_min() for details, same implementation but for wind offshore generation except input values are read from These minimum values are defined in renewable_portfolio_max.csv"""
    ind = pds.index(PD)
    return quicksum(
        (model.capacity_wind_ofs[PDD, GL] + model.capacity_wind_ofs_recon[PDD, GL]) for PDD in pds[:ind + 1] for GL
        in gl if AP == map_gl_to_pr[int(GL)]) + quicksum(
        extant_wind_solar[pds.index(PD)][str(GL) + '.' + 'wind_ofs'] for GL in gl if
        str(GL) + '.' + 'wind_ofs' in extant_wind_solar[0] and AP == map_gl_to_pr[int(GL)]) >= \
        wind_ofs_min_ap_limit[PD][AP]

rpf_wind_ons_max(model, PD, AP)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the capacity limit is applied.

required
AP

The administrative province as a string, specifying where the wind capacity limit is to be enforced.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the combined installed and extant capacity of onshore wind

generation in the specified administrative province (AP) does not exceed the maximum capacity limit

defined by wind_ons_max_ap_limit[PD][AP].

Details
  • This function sums the capacity of both newly installed and existing onshore wind power facilities up to the given planning period (PD) and ensures that the total does not exceed the limit specified in the wind_ons_max_ap_limit for that province and period. The limit is set in renewable_portfolio_max.csv
  • The enforcement of this constraint is contingent on the presence of a defined maximum capacity limit for onshore wind in the renewable portfolio standards, specifically under renewable_portfolio_standards['wind_ons.max'].
  • This constraint is critical for regions aiming to balance the development of wind energy with other forms of energy production, ensuring that wind energy development aligns with broader energy policy objectives and does not exceed planned capacity for environmental, economic, or grid reliability reasons.
Source code in COPPER.py
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
def rpf_wind_ons_max(model, PD, AP):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the capacity limit is applied.
        AP: The administrative province as a string, specifying where the wind capacity limit is to be enforced.

    Returns:
        A Pyomo Constraint expression: Ensures that the combined installed and extant capacity of onshore wind 
        generation in the specified administrative province (AP) does not exceed the maximum capacity limit 
        defined by `wind_ons_max_ap_limit[PD][AP]`.

    Details:
        - This function sums the capacity of both newly installed and existing onshore wind power facilities up to 
        the given planning period (PD) and ensures that the total does not exceed the limit specified in the 
        `wind_ons_max_ap_limit` for that province and period. The limit is set in renewable_portfolio_max.csv
        - The enforcement of this constraint is contingent on the presence of a defined maximum capacity limit for 
        onshore wind in the renewable portfolio standards, specifically under `renewable_portfolio_standards['wind_ons.max']`.
        - This constraint is critical for regions aiming to balance the development of wind energy with other 
        forms of energy production, ensuring that wind energy development aligns with broader energy policy 
        objectives and does not exceed planned capacity for environmental, economic, or grid reliability reasons.
    """
    ind = pds.index(PD)
    return quicksum(
        (model.capacity_wind_ons[PDD, GL] + model.capacity_wind_ons_recon[PDD, GL]) for PDD in pds[:ind + 1] for GL
        in gl if AP == map_gl_to_pr[int(GL)]) + quicksum(
        extant_wind_solar[pds.index(PD)][str(GL) + '.' + 'wind_ons'] for GL in gl if
        str(GL) + '.' + 'wind_ons' in extant_wind_solar[0] and AP == map_gl_to_pr[int(GL)]) <= \
        wind_ons_max_ap_limit[PD][AP]

rpf_wind_ons_min(model, PD, AP)

Constraint function for renewable portfolio standards on minimum wind (onsite) capacity.

Parameters: model (pyomo.environ.ConcreteModel): Pyomo model instance. PD (str): Period identifier. AP (str): Area or region identifier.

Returns: pyomo.environ.Constraint: A Pyomo constraint representing the requirement that the total capacity of onsite wind energy sources satisfies the minimum wind capacity standards for the given period and area.

Details: The function calculates the total onsite wind capacity from existing and proposed installations and ensures it meets or exceeds the minimum capacity standards specified in the 'wind_ons_min_ap_limit' dictionary for the given period and area. These minimum values are defined in renewable_portfolio_min.csv

Source code in COPPER.py
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
def rpf_wind_ons_min(model, PD, AP):
    """
    Constraint function for renewable portfolio standards on minimum wind (onsite) capacity.

    Parameters:
    model (pyomo.environ.ConcreteModel): Pyomo model instance.
    PD (str): Period identifier.
    AP (str): Area or region identifier.

    Returns:
    pyomo.environ.Constraint: A Pyomo constraint representing the requirement that the total
    capacity of onsite wind energy sources satisfies the minimum wind capacity standards
    for the given period and area.

    Details:
    The function calculates the total onsite wind capacity from existing and proposed installations
    and ensures it meets or exceeds the minimum capacity standards specified in the 'wind_ons_min_ap_limit'
    dictionary for the given period and area. These minimum values are defined in renewable_portfolio_min.csv
    """
    ind = pds.index(PD)
    return quicksum(
        (model.capacity_wind_ons[PDD, GL] + model.capacity_wind_ons_recon[PDD, GL]) for PDD in pds[:ind + 1] for GL
        in gl if AP == map_gl_to_pr[int(GL)]) + quicksum(
        extant_wind_solar[pds.index(PD)][str(GL) + '.' + 'wind_ons'] for GL in gl if
        str(GL) + '.' + 'wind_ons' in extant_wind_solar[0] and AP == map_gl_to_pr[int(GL)]) >= \
        wind_ons_min_ap_limit[PD][AP]

solar_recon(model, PD, GL)

same as wind_ofs_recon() but for solar facilities

Source code in COPPER.py
2545
2546
2547
2548
2549
def solar_recon(model, PD, GL):
    """same as wind_ofs_recon() but for solar facilities"""
    ind = pds.index(PD)
    return model.capacity_solar_recon[PD, GL] <= solar_recon_capacity[PD + '.' + GL] + quicksum(
        solar_recon_capacity[PDD + '.' + GL] - model.capacity_solar_recon[PDD, GL] for PDD in pds[:ind])

solarcaplimit(model, GL)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
GL

The geographical location identifier for the solar power facility.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the total installed solar power capacity at the specified

geographical location (GL) to not exceed a predefined maximum capacity (maxsolar[GL]).

Details
  • This function aggregates the capacity of solar power facilities, both existing and reconstructed, across all planning periods, ensuring that the total does not surpass the maximum allowable capacity defined for that location.
  • The constraint is important for managing the growth of solar power installations within sustainable limits and aligning with regional energy production policies or grid capacity constraints.
Source code in COPPER.py
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
def solarcaplimit(model, GL):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        GL: The geographical location identifier for the solar power facility.

    Returns:
        A Pyomo Constraint expression: Limits the total installed solar power capacity at the specified 
        geographical location (GL) to not exceed a predefined maximum capacity (`maxsolar[GL]`).

    Details:
        - This function aggregates the capacity of solar power facilities, both existing and reconstructed, across 
          all planning periods, ensuring that the total does not surpass the maximum allowable capacity defined for 
          that location.
        - The constraint is important for managing the growth of solar power installations within sustainable limits 
          and aligning with regional energy production policies or grid capacity constraints.
    """
    return quicksum(model.capacity_solar[PD, GL] + model.capacity_solar_recon[PD, GL] for PD in pds) <= maxsolar[GL]

solarg(model, PD, H, ABA)

Constrains the solar power output in a specific balancing area during a designated hour and planning period and simulated hours. This function limits the solar power output to the sum of the calculated potential generation from all relevant solar installations within the area, considering both existing and reconstructed capacity adjusted for the capacity factor, plus any existing generation capacity.

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets for the energy model.

required
PD

The planning period, represented as a string (e.g., '2025'), during which the constraint is applied.

required
H

The specific hour within the planning period for which the output constraint is relevant.

required
ABA

The balancing area as a string, indicating where the solar power generation is being considered.

required

Returns:

Type Description

A Pyomo Constraint expression: Ensures that the solar power output (model.solarout) for the specified

hour H, planning period PD, and balancing area ABA does not exceed the theoretical maximum based on installed

capacity (both existing and newly constructed) and the capacity factors for that hour.

Details
  • The maximum output is calculated by aggregating the potential generation from all solar panels (accounting for both existing and newly constructed capacities) within the specified balancing area. The capacity factors, which vary by hour and location, are considered by accessing solarcf_df.
  • The function also incorporates extant solar generation capacities (extant_solar_gen) that are predefined for the specific hour and balancing area.
  • The output capacity for each generator is adjusted based on typical resource availability during that hour, which is accessed from a dataframe (solarcf_df).
Note

Ensure that the solarcf_df dataframe and the extant_solar_gen data structure and extant_wind_solar.csv are properly set up and populated with accurate capacity factor data and existing generation levels before using this function in your research.

Source code in COPPER.py
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
def solarg(model, PD, H, ABA):
    """
    Constrains the solar power output in a specific balancing area during a designated hour and planning period and simulated hours. 
    This function limits the solar power output to the sum of the calculated potential generation from all relevant 
    solar installations within the area, considering both existing and reconstructed capacity adjusted for the 
    capacity factor, plus any existing generation capacity.

    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets for the energy model.
        PD: The planning period, represented as a string (e.g., '2025'), during which the constraint is applied.
        H: The specific hour within the planning period for which the output constraint is relevant.
        ABA: The balancing area as a string, indicating where the solar power generation is being considered.

    Returns:
        A Pyomo Constraint expression: Ensures that the solar power output (`model.solarout`) for the specified 
        hour H, planning period PD, and balancing area ABA does not exceed the theoretical maximum based on installed 
        capacity (both existing and newly constructed) and the capacity factors for that hour.

    Details:
        - The maximum output is calculated by aggregating the potential generation from all solar panels (accounting 
          for both existing and newly constructed capacities) within the specified balancing area. The capacity factors,
          which vary by hour and location, are considered by accessing `solarcf_df`.
        - The function also incorporates extant solar generation capacities (`extant_solar_gen`) that are predefined 
          for the specific hour and balancing area.
        - The output capacity for each generator is adjusted based on typical resource availability during that hour,
          which is accessed from a dataframe (`solarcf_df`).

    Note:
        Ensure that the `solarcf_df` dataframe and the `extant_solar_gen` data structure and extant_wind_solar.csv are properly set up and populated
        with accurate capacity factor data and existing generation levels before using this function in your research.
    """
    ind = pds.index(PD)
    return model.solarout[PD, H, ABA] <= quicksum(
        (
                model.capacity_solar[PDD, GL]
                + model.capacity_solar_recon[PDD, GL]) * solarcf_df.loc[H, int(GL)] for PDD in pds[:ind + 1] \
        for GL in gl if ABA == map_gl_to_ba[int(GL)]) \
        + extant_solar_gen.loc[(PD, H), ABA]

storage_ccost_constraint(model, PD)

Defines a constraint for the capital costs associated with new storage facilities within a given planning period (PD). The function computes these costs based on the model's storage capacity decision variables, adjusted for tax credits if applicable. The constraint ensures that the allocated capital cost for new storage does not exceed the calculated cost sum for the period.

Parameters:

Name Type Description Default
model Pyomo Object

A Pyomo model instance, containing all decision variables, parameters, and sets relevant to the model.

required
PD str

The planning period as a string for which the constraint is being calculated (e.g., '2025').

required

Returns:

Type Description

model.storage_ccost[PD] >= storage_cost_sum (Pyomo Constraint): expression that conditions the new storage capital cost for the period PD based on whether

storage is continuously operational and whether investment tax credits apply.

Details
  • The constraint calculates the total capital costs by summing the product of storage capacity and per-unit cost, factoring in tax credits if flag_itc_clean_tech or flag_itc_clean_elec is True for earlier periods ('2025', '2030') and full costs for later periods ('2035', '2040', '2045', '2050').
  • If storage_continous is True in config.toml, the function calculates and sums the costs for all storage technologies (ST) in all balancing areas (ABA) up to the current period index found in pds.
  • If storage_continous is False, the function sets the new storage capital cost constraint to zero, indicating no storage capacity additions are considered.
  • storage_cost[ST + '.' + PDD] refers to the cost associated with each storage technology and period, and model.capacity_storage[PDD, ST, ABA] refers to the decision variable for new storage capacity.
Source code in COPPER.py
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
def storage_ccost_constraint(model, PD):
    """
    Defines a constraint for the capital costs associated with new storage facilities within a given planning period (PD).
    The function computes these costs based on the model's storage capacity decision variables, adjusted for tax credits
    if applicable. The constraint ensures that the allocated capital cost for new storage does not exceed the calculated
    cost sum for the period.

    Parameters:
        model (Pyomo Object): A Pyomo model instance, containing all decision variables, parameters, and sets relevant to the model.
        PD (str): The planning period as a string for which the constraint is being calculated (e.g., '2025').

    Returns:
        model.storage_ccost[PD] >= storage_cost_sum (Pyomo Constraint): expression that conditions the new storage capital cost for the period PD based on whether
        storage is continuously operational and whether investment tax credits apply.

    Details:
        - The constraint calculates the total capital costs by summing the product of storage capacity and per-unit cost,
          factoring in tax credits if `flag_itc_clean_tech` or `flag_itc_clean_elec` is True for earlier periods ('2025', '2030')
          and full costs for later periods ('2035', '2040', '2045', '2050').
        - If `storage_continous` is True in config.toml, the function calculates and sums the costs for all storage technologies (ST)
          in all balancing areas (ABA) up to the current period index found in `pds`.
        - If `storage_continous` is False, the function sets the new storage capital cost constraint to zero, indicating
          no storage capacity additions are considered.
        - `storage_cost[ST + '.' + PDD]` refers to the cost associated with each storage technology and period, and
          `model.capacity_storage[PDD, ST, ABA]` refers to the decision variable for new storage capacity.

    """
    if storage_continous:
        ind = pds.index(PD)
        if flag_itc_clean_tech or flag_itc_clean_elec:
            storage_cost_sum = quicksum(
                model.capacity_storage[PDD, ST, ABA] * subsidy_factor[f'{ABA}.{ST}'] * storage_cost[ST + '.' + PDD] for PDD in
                pds[:ind + 1] for ST in st for ABA in aba if PDD in ['2025', '2030'])
            storage_cost_sum += quicksum(
                model.capacity_storage[PDD, ST, ABA] * storage_cost[ST + '.' + PDD] for PDD in pds[:ind + 1] for ST in
                st for ABA in aba if PDD in ['2035', '2040', '2045', '2050'])
        else:
            storage_cost_sum = quicksum(
                model.capacity_storage[PDD, ST, ABA] * storage_cost[ST + '.' + PDD] for PDD in pds[:ind + 1] for ST in
                st for ABA in aba)

        return model.storage_ccost[PD] >= storage_cost_sum
    else:
        return model.storage_ccost[PD] == 0

storage_omcost_constraint(model, PD)

Establishes a constraint on the operational and maintenance (O&M) costs for storage facilities within the model for a specified planning period (PD). This constraint ensures that the O&M costs do not exceed the sum calculated based on the storage capacities across different technologies and balancing areas, if storage is continuously operational. If storage is not continuous, the O&M costs are set to zero for that period.

Parameters:

Name Type Description Default
model

A Pyomo model instance, which includes all decision variables, parameters, and sets related to the model.

required
PD

The planning period as a string for which the O&M cost constraint is being calculated (e.g., '2025').

required

Returns:

Type Description

model.storage_omcost[PD] >= X (Pyomo Constraint): expression that sets the O&M costs for storage facilities during the period PD. The costs

are computed as the sum of products of fixed O&M costs per unit for each storage technology and their respective

capacities if storage operation is continuous. If not, the O&M costs are constrained to zero.

Details
  • The function checks if storage_continous is True as specified in config.toml. If so, it sums up the fixed O&M costs for all storage technologies (ST) and balancing areas (ABA) from the start of the planning series up to and including PD.
  • Fixed O&M costs for each storage type and period combination (ST + '.' + PDD) are multiplied by the capacity decisions (model.capacity_storage[PDD, ST, ABA]) to get the total O&M cost.
  • If storage_continous is False, it implies that no new storage facilities are considered operational, and thus the O&M costs are set to zero.
Source code in COPPER.py
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
def storage_omcost_constraint(model, PD):
    """
    Establishes a constraint on the operational and maintenance (O&M) costs for storage facilities within the model 
    for a specified planning period (PD). This constraint ensures that the O&M costs do not exceed the sum calculated 
    based on the storage capacities across different technologies and balancing areas, if storage is continuously 
    operational. If storage is not continuous, the O&M costs are set to zero for that period.

    Parameters:
        model: A Pyomo model instance, which includes all decision variables, parameters, and sets related to the model.
        PD: The planning period as a string for which the O&M cost constraint is being calculated (e.g., '2025').

    Returns:
        model.storage_omcost[PD] >= X (Pyomo Constraint): expression that sets the O&M costs for storage facilities during the period PD. The costs
        are computed as the sum of products of fixed O&M costs per unit for each storage technology and their respective 
        capacities if storage operation is continuous. If not, the O&M costs are constrained to zero.

    Details:
        - The function checks if `storage_continous` is True as specified in config.toml. If so, it sums up the fixed O&M costs for all storage
          technologies (ST) and balancing areas (ABA) from the start of the planning series up to and including PD.
        - Fixed O&M costs for each storage type and period combination (ST + '.' + PDD) are multiplied by the capacity
          decisions (`model.capacity_storage[PDD, ST, ABA]`) to get the total O&M cost.
        - If `storage_continous` is False, it implies that no new storage facilities are considered operational, and thus
          the O&M costs are set to zero.
    """

    if storage_continous:
        ind = pds.index(PD)
        new_capacity_omcost = quicksum(store_fix_o_m[ST + '.' + PDD] * model.capacity_storage[PDD, ST, ABA]
                                       for PDD in pds[:ind + 1] for ST in st for ABA in aba)
        extant_capacity_omcost = quicksum(store_fix_o_m[ST + '.' + PD] * extant_storage[PD + '.' + ABA + '.' + ST]
                                          for ST in st for ABA in aba)
        return model.storage_omcost[PD] >= new_capacity_omcost + extant_capacity_omcost
    else:
        return model.storage_omcost[PD] == 0

storage_rest_monthly(model, PD, ST, FHOM, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
ST

The storage type identifier as a string.

required
FHOM

The first hour of the month within the planning period for which the storage energy is set to zero.

required
ABA

The balancing area as a string, indicating where the storage is located.

required

Returns:

Type Description

A Pyomo Constraint expression: Forces the energy level in the storage facility to be zero at the first hour

of each month during the planning period, for the specified storage type and balancing area.

Details
  • This function is used to simulate operational resets or policy-driven requirements where storage facilities must start each month with zero stored energy.
  • It helps in modeling scenarios where monthly operational strategies are recalibrated or where regulatory requirements necessitate such conditions.
Source code in COPPER.py
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
def storage_rest_monthly(model, PD, ST, FHOM, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        ST: The storage type identifier as a string.
        FHOM: The first hour of the month within the planning period for which the storage energy is set to zero.
        ABA: The balancing area as a string, indicating where the storage is located.

    Returns:
        A Pyomo Constraint expression: Forces the energy level in the storage facility to be zero at the first hour 
        of each month during the planning period, for the specified storage type and balancing area.

    Details:
        - This function is used to simulate operational resets or policy-driven requirements where storage facilities 
          must start each month with zero stored energy.
        - It helps in modeling scenarios where monthly operational strategies are recalibrated or where regulatory 
          requirements necessitate such conditions.
    """
    return model.storageenergy[PD, ST, FHOM, ABA] == 0

storageinmax(model, PD, ST, H, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2040') during which the constraint is applied.

required
ST

The storage type identifier as a string.

required
H

The hour within the planning period for which the input capacity constraint is relevant.

required
ABA

The balancing area as a string, indicating where the storage is located.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the input to the storage facility at hour H during the planning

period PD to not exceed the sum of the base area storage capacity, potential new capacity additions, and

any capacity attributed to hydro development if applicable.

Details: - The function calculates the maximum permissible input to storage facilities, accounting for base storage capacity and additional capacities from new constructions or hydro developments. - For continuous storage scenarios, it computes additional new capacity based on the storage type and specific conditions from the planning period. - In scenarios involving hydro development with non-continuous storage, the function includes additional capacity from hydro pump retrofits, specific to conditions and locations. - This constraint helps manage the inflow to storage facilities, ensuring that inputs do not exceed the facility's capacity to store energy efficiently and safely.

Source code in COPPER.py
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
def storageinmax(model, PD, ST, H, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2040') during which the constraint is applied.
        ST: The storage type identifier as a string.
        H: The hour within the planning period for which the input capacity constraint is relevant.
        ABA: The balancing area as a string, indicating where the storage is located.

    Returns:
        A Pyomo Constraint expression: Limits the input to the storage facility at hour H during the planning 
        period PD to not exceed the sum of the base area storage capacity, potential new capacity additions, and 
        any capacity attributed to hydro development if applicable.
    Details:
        - The function calculates the maximum permissible input to storage facilities, accounting for base 
          storage capacity and additional capacities from new constructions or hydro developments.
        - For continuous storage scenarios, it computes additional new capacity based on the storage type 
          and specific conditions from the planning period.
        - In scenarios involving hydro development with non-continuous storage, the function includes additional 
          capacity from hydro pump retrofits, specific to conditions and locations.
        - This constraint helps manage the inflow to storage facilities, ensuring that inputs do not exceed 
          the facility's capacity to store energy efficiently and safely.
    """
    ind = pds.index(PD)

    pump_new_con = 0
    pump_integer_cap = 0

    if storage_continous:
        if ST == 'storage_LI' and int(PD) >= 2040:
            pump_new_con = quicksum(model.capacity_storage[PDD, ST, ABA] for PDD in pds[ind - 2:ind + 1])
        else:
            pump_new_con = quicksum(model.capacity_storage[PDD, ST, ABA] for PDD in pds[:ind + 1])
    if hydro_development and not storage_continous:
        pump_integer_cap = quicksum(
            model.pumphydro[PDD, HR_PUMP] * capacity_pump_renewal[HR_PUMP] for PDD in pds[:ind + 1] for HR_PUMP in
            hr_pump if hr_pump_location[HR_PUMP] == ABA)
    return model.storagein[PD, ST, H, ABA] <= extant_storage[PD + '.' + ABA + '.' + ST] + pump_integer_cap + pump_new_con

storageoutmax(model, PD, ST, H, ABA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2040') during which the constraint is applied.

required
ST

The storage type identifier as a string.

required
H

The hour within the planning period for which the output capacity constraint is relevant.

required
ABA

The balancing area as a string, indicating where the storage is located.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the output from the storage facility at hour H during the planning

period PD to not exceed the sum of the base area storage capacity, potential new capacity additions, and

any capacity attributed to hydro development if applicable.

Details
  • The function determines the maximum allowable output from storage facilities by accounting for base storage capacity and additional capacities from new constructions or hydro developments.
  • For continuous storage scenarios, the function calculates additional new capacity based on the type of storage and the specific conditions from the planning period.
  • If hydro development is active and storage operations are not continuous, the function includes additional capacity from hydro pump retrofits based on specific conditions.
  • The total permissible output from the storage facility is then set by summing the base capacity and any additional capacities, providing a limit to ensure that the output does not exceed operational capabilities.
Source code in COPPER.py
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
def storageoutmax(model, PD, ST, H, ABA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2040') during which the constraint is applied.
        ST: The storage type identifier as a string.
        H: The hour within the planning period for which the output capacity constraint is relevant.
        ABA: The balancing area as a string, indicating where the storage is located.

    Returns:
        A Pyomo Constraint expression: Limits the output from the storage facility at hour H during the planning 
        period PD to not exceed the sum of the base area storage capacity, potential new capacity additions, and 
        any capacity attributed to hydro development if applicable.

    Details:
        - The function determines the maximum allowable output from storage facilities by accounting for base 
          storage capacity and additional capacities from new constructions or hydro developments.
        - For continuous storage scenarios, the function calculates additional new capacity based on the type 
          of storage and the specific conditions from the planning period.
        - If hydro development is active and storage operations are not continuous, the function includes additional 
          capacity from hydro pump retrofits based on specific conditions.
        - The total permissible output from the storage facility is then set by summing the base capacity and any 
          additional capacities, providing a limit to ensure that the output does not exceed operational capabilities.
    """
    ind = pds.index(PD)
    pump_new_con = 0
    pump_integer_cap = 0

    if storage_continous:
        if ST == 'storage_LI' and int(PD) >= 2040:
            pump_new_con = quicksum(model.capacity_storage[PDD, ST, ABA] for PDD in pds[ind - 2:ind + 1])
        else:
            pump_new_con = quicksum(model.capacity_storage[PDD, ST, ABA] for PDD in pds[:ind + 1])
    if hydro_development and not storage_continous:
        pump_integer_cap = quicksum(
            model.pumphydro[PDD, HR_PUMP] * capacity_pump_renewal[HR_PUMP] for PDD in pds[:ind + 1] for HR_PUMP in
            hr_pump if hr_pump_location[HR_PUMP] == ABA)
    return model.storageout[PD, ST, H, ABA] <= extant_storage[PD + '.' + ABA + '.' + ST] + pump_integer_cap + pump_new_con

transcap(model, PD, H, ABA, ABBA)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
PD

The planning period as a string (e.g., '2025') during which the transmission capacity constraint is applied.

required
H

The hour within the planning period for which the transmission constraint is relevant.

required
ABA

The originating area balancing authority as a string.

required
ABBA

The receiving area balancing authority as a string.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the electricity transmission between two specific area balancing authorities

(ABA and ABBA) to not exceed the sum of the planned and existing transmission capacities.

Details
  • This function calculates the maximum allowable transmission capacity between two balancing authorities by summing up the capacities planned up to and including the specified planning period and any existing capacities noted at the beginning of the period.
  • The capacities considered include those added in previous periods and still existing, ensuring that the limit reflects both the physical capacity and the operational constraints.
  • The constraint ensures that the transmission does not exceed infrastructure limits, supporting reliable and efficient grid operations and facilitating compliance with regulatory standards for inter-regional electricity transfers.
Source code in COPPER.py
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
def transcap(model, PD, H, ABA, ABBA):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the transmission capacity constraint is applied.
        H: The hour within the planning period for which the transmission constraint is relevant.
        ABA: The originating area balancing authority as a string.
        ABBA: The receiving area balancing authority as a string.

    Returns:
        A Pyomo Constraint expression: Limits the electricity transmission between two specific area balancing authorities 
        (ABA and ABBA) to not exceed the sum of the planned and existing transmission capacities.

    Details:
        - This function calculates the maximum allowable transmission capacity between two balancing authorities by 
          summing up the capacities planned up to and including the specified planning period and any existing capacities 
          noted at the beginning of the period.
        - The capacities considered include those added in previous periods and still existing, ensuring that the limit reflects
          both the physical capacity and the operational constraints.
        - The constraint ensures that the transmission does not exceed infrastructure limits, supporting reliable and 
          efficient grid operations and facilitating compliance with regulatory standards for inter-regional electricity transfers.
    """

    ind = pds.index(PD)
    return model.transmission[PD, H, ABA, ABBA] <= (quicksum(
        model.capacity_transmission[PDD, ABA, ABBA] for PDD in pds[:ind + 1] for i in aux if
        ABA + '.' + ABBA in transmap)
                                                    + quicksum(
                extant_transmission[pds.index(PD)][ABA + '.' + ABBA] for i in aux if
                ABA + '.' + ABBA in extant_transmission[pds.index(PD)]))

transmission_expansion_cap(model, ABA, ABBA)

existing transmission extant_transmission values are sourced from 'extant_transmission.csv' input sheet. Parameters: model: A Pyomo model instance containing all relevant decision variables, parameters, and sets. PD: The planning period as a string (e.g., '2025') during which the transmission capacity constraint is applied. H: The hour within the planning period for which the transmission constraint is relevant. ABA: The originating area balancing authority as a string. ABBA: The receiving area balancing authority as a string.

Returns:

Type Description

A Pyomo Constraint expression: Limits the electricity transmission between two specific area balancing authorities

(ABA and ABBA) to not exceed the sum of the planned and existing transmission capacities.

Details
  • This function calculates the maximum allowable transmission capacity between two balancing authorities by summing up the capacities planned up to and including the specified planning period and any existing capacities noted at the beginning of the period.
  • The capacities considered include those added in previous periods and those that are still in existance.
  • The constraint ensures that the transmission capacity does not exceed potential build limits.
Source code in COPPER.py
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
def transmission_expansion_cap(model, ABA, ABBA):
    """
    existing transmission extant_transmission values are sourced from 'extant_transmission.csv' input sheet.
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        PD: The planning period as a string (e.g., '2025') during which the transmission capacity constraint is applied.
        H: The hour within the planning period for which the transmission constraint is relevant.
        ABA: The originating area balancing authority as a string.
        ABBA: The receiving area balancing authority as a string.

    Returns:
        A Pyomo Constraint expression: Limits the electricity transmission between two specific area balancing authorities 
        (ABA and ABBA) to not exceed the sum of the planned and existing transmission capacities.

    Details:
        - This function calculates the maximum allowable transmission capacity between two balancing authorities by 
        summing up the capacities planned up to and including the specified planning period and any existing capacities 
        noted at the beginning of the period.
        - The capacities considered include those added in previous periods and those that are still in existance.
        - The constraint ensures that the transmission capacity does not exceed potential build limits.
    """

    return quicksum(model.capacity_transmission[PD, ABA, ABBA] for PD in pds) <= CTE_coef * quicksum(
        extant_transmission[0][ABA + '.' + ABBA] for i in aux if ABA + '.' + ABBA in extant_transmission[0])

transmission_expansion_custom_cap(model, ABA, ABBA)

Evaluates the transmission expansion capability for a given pair of balancing areas.

This function checks the total capacity of transmission between two specified balancing areas against the predefined expansion limits. If the combination of these areas is listed in the transmission expansion limits, it uses that limit; otherwise, it defaults to a high limit of 100000.

Parameters:

Name Type Description Default
model Model

The optimization model that includes the transmission capacities.

required
ABA str

The abbreviation of the first balancing area.

required
ABBA str

The abbreviation of the second balancing area (connected to ABA).

required

Returns:

Name Type Description
Constraint

A constraint expression that ensures the sum of transmission capacities from

all planning decisions (PD) between ABA and ABBA does not exceed the predefined limit (TEL).

Note

The function assumes that 'trans_expansion_limits', 'model.capacity_transmission', and 'pds' are accessible within the scope where this function is called.

Source code in COPPER.py
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
def transmission_expansion_custom_cap(model, ABA, ABBA):
    """
    Evaluates the transmission expansion capability for a given pair of balancing areas.

    This function checks the total capacity of transmission between two specified balancing areas
    against the predefined expansion limits. If the combination of these areas is listed in the
    transmission expansion limits, it uses that limit; otherwise, it defaults to a high limit of 100000.

    Parameters:
        model (Model): The optimization model that includes the transmission capacities.
        ABA (str): The abbreviation of the first balancing area.
        ABBA (str): The abbreviation of the second balancing area (connected to ABA).

    Returns:
        Constraint: A constraint expression that ensures the sum of transmission capacities from
    all planning decisions (PD) between ABA and ABBA does not exceed the predefined limit (TEL).

    Note: 
        The function assumes that 'trans_expansion_limits', 'model.capacity_transmission', and 'pds'
        are accessible within the scope where this function is called.
    """
    if ABA + '.' + ABBA in trans_expansion_limits:
        TEL = trans_expansion_limits[ABA + '.' + ABBA]
    else:
        TEL = 100000

    return quicksum(model.capacity_transmission[PD, ABA, ABBA] for PD in pds) <= TEL

underlim_fuel_constraint(model, PD, ABA, TP, FT)

Function for fuel pricing with different carbon offset standards. The fuel used by a thermal plant has to be the more expensive fuel out of all available fuels to that plant after it reaches the emission limit for the cheaper options

Parameters:

Name Type Description Default
model

Pyomo model instance containing all necessary decision variables, parameters, and sets.

required
PD

Planning period as a string (e.g., '2025') for which the constraint is applied.

required
ABA

Balancing area as a string (e.g., 'British Columbia').

required
TP

Thermal plant as a string

required
FT

Fuel type as a string

required
Source code in COPPER.py
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
def underlim_fuel_constraint(model, PD, ABA, TP, FT):
    """
    Function for fuel pricing with different carbon offset standards.
    The fuel used by a thermal plant has to be the more expensive fuel out of all available fuels
    to that plant after it reaches the emission limit for the cheaper options

    Parameters:
        model: Pyomo model instance containing all necessary decision variables, parameters, and sets.
        PD: Planning period as a string (e.g., '2025') for which the constraint is applied.
        ABA: Balancing area as a string (e.g., 'British Columbia').
        TP: Thermal plant as a string 
        FT: Fuel type as a string

    """

    ind = pds.index(PD)
    if TP in useful_fuels_underlim.keys() and FT in useful_fuels_underlim[TP].split('.'):
        return sum(model.supply[PD, H, ABA, TP, FT] * carbondioxide[TP + '.' + FT] for H in h) / repday_scaling <= (
                sum(model.capacity_therm[PDD, ABA, TP] - model.retire_therm[PDD, ABA, TP] for PDD in pds[:ind + 1]) +
                extant_thermal[pds[0] + '.' + ABA + '.' + TP]) * offset_threshold[PD]
    else:
        return Constraint.Skip

unpickle_model(path)

Unpickles a previously pickled Pyomo model from file using dill.

Parameters:

Name Type Description Default
path

Path to the pickled Pyomo model file

required

Returns:

Name Type Description
model ConcreteModel

The unpickled Pyomo ConcreteModel

Source code in phases/postprocessingtools.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def unpickle_model(path) -> ConcreteModel:
    """Unpickles a previously pickled Pyomo model from file using `dill`.

    Args:
        path: Path to the pickled Pyomo model file

    Returns:
        model: The unpickled Pyomo ConcreteModel

    """
    model_path = Path(path)
    with model_path.open("rb") as fi:
        model = pickle.load(fi)
    return model

variable_frame(instance, results_dir)

This function creates an Excel file with all model variables as tables. Parameters: Pyomo model instance object

Source code in phases/postprocessingtools.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def variable_frame(instance, results_dir):
    """
    This function creates an Excel file with all model variables as tables.
    Parameters: Pyomo model instance object
    """
    # Set-up step        
    variable_series = []
    sheet_names = []
    model_vars = instance.component_map(ctype=Var)

    # Get resulting variables values
    for k in model_vars.keys():
        v = model_vars[k]
        series = pd.Series(v.extract_values(), index=v.extract_values().keys())
        try:
            if isinstance(series.index[0], tuple):
                series = series.unstack(level=1)
            else:
                series = pd.DataFrame(series)
        except IndexError:
            series = pd.DataFrame(series)
        series.columns = pd.MultiIndex.from_tuples([(k, t) for t in series.columns])
        sheet_names.append(series.columns[0][0])
        variable_series.append(series)

    # Write to Excel file
    Variables_file = pd.ExcelWriter(f"{results_dir}/Variables_DataFrame.xlsx", engine="openpyxl")
    for i, temp_df in enumerate(variable_series):
        temp_df.to_excel(Variables_file, sheet_name=f"{sheet_names[i]}", startrow=1, header=True, index=True)
    Variables_file.close()

vom_cost_constraint(model, PD)

Defines a constraint on the variable operation and maintenance (VOM) costs for various types of power generation (thermal, wind, solar, and hydro) and load shedding during each economic dispatch period.

This function calculates the total VOM costs for all power plant types across all hours and locations, scaled to an annual cost by the representative day demand scaling factor. Representative days are set in config.toml. It then ensures that this total VOM cost does not exceed the variable operation and maintenance budget specified in the model for the given period (PD).

Parameters:

Name Type Description Default
model pyomo object

The Pyomo model instance containing all the decision variables, parameters, and sets used in the model.

required
PD string

The planning period as a string for which the constraint is being calculated eg. '2025'.

required

Returns:

Type Description

Constraint expression (pyomo constraint): enforces the total annualized VOM costs is to be less

than or equal to the model's variable O&M cost allowance for the period PD.

The function uses several model components: - model.supply[PD, H, ABA, TP]: Power supply from different plant types (TP) during each hour (H) and at each location (ABA). - model.windonsout, model.windofsout, model.solarout: Power output from wind (onshore and offshore) and solar units. - ror_hydroout, model.daystoragehydroout, model.monthstoragehydroout: Outputs from run-of-river, daily, and monthly storage hydro units. - model.load_shedding: Power load shedding amount, indicating power that must be cut due to insufficient supply or other issues. - variable_o_m: Dictionary of variable O&M cost coefficients for different types of energy sources. - load_shedding_cost: Cost associated with load shedding.

Each term in the returned expression is scaled by the represenative day demand scaling factor, representing an annualization of the calculated costs.

Source code in COPPER.py
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
def vom_cost_constraint(model, PD):
    """
    Defines a constraint on the variable operation and maintenance (VOM) costs for various types of power generation 
    (thermal, wind, solar, and hydro) and load shedding during each economic dispatch period.

    This function calculates the total VOM costs for all power plant types across all hours and locations, scaled to an annual
    cost by the representative day demand scaling factor. Representative days are set in config.toml. It 
    then ensures that this total VOM cost does not exceed the variable operation and maintenance budget specified in 
    the model for the given period (`PD`).

    Parameters:
        model (pyomo object): The Pyomo model instance containing all the decision variables, parameters, and sets used in the model.
        PD (string): The planning period as a string for which the constraint is being calculated eg. '2025'.

    Returns:
        Constraint expression (pyomo constraint): enforces the total annualized VOM costs is to be less 
        than or equal to the model's variable O&M cost allowance for the period `PD`.

    The function uses several model components:
    - `model.supply[PD, H, ABA, TP]`: Power supply from different plant types (TP) during each hour (H) and at each 
      location (ABA).
    - `model.windonsout`, `model.windofsout`, `model.solarout`: Power output from wind (onshore and offshore) and solar 
      units.
    - `ror_hydroout`, `model.daystoragehydroout`, `model.monthstoragehydroout`: Outputs from run-of-river, daily, and 
      monthly storage hydro units.
    - `model.load_shedding`: Power load shedding amount, indicating power that must be cut due to insufficient supply 
      or other issues.
    - `variable_o_m`: Dictionary of variable O&M cost coefficients for different types of energy sources.
    - `load_shedding_cost`: Cost associated with load shedding.

    Each term in the returned expression is scaled by the represenative day demand scaling factor, 
    representing an annualization of the calculated costs.
    """

    CCS_om_cost = quicksum(
        model.supply[PD, H, ABA, TP, FT] * (carbondioxide[TP + '.' + FT] * CCS_capture_rate / (1 - CCS_capture_rate))
        * CCS_om for H in h for ABA in aba for TP in tplants if TP in CCS_tech for FT in model.ftype if FT in
        useful_fuels[TP].split('.')) / repday_scaling

    transmission_op_cost = quicksum(model.transmission[PD, H, ABA, ABBA] * hurdle_cost for H in h for ABA in aba 
                                 for ABBA in aba if ABA + '.' + ABBA in transmap 
                                 if provinces_per_region[ABA] != provinces_per_region[ABBA]) / repday_scaling

    return model.varOMcost[PD] >= (
            quicksum(model.supply[PD, H, ABA, TP, FT] * variable_o_m[TP] for H in h for ABA in aba for TP in
                     tplants for FT in model.ftype if FT in useful_fuels[TP].split('.')) / repday_scaling
            + quicksum(model.windonsout[PD, H, ABA] * variable_o_m['wind_ons']
                       + model.windofsout[PD, H, ABA] * variable_o_m['wind_ofs']
                       + model.solarout[PD, H, ABA] * variable_o_m['solar']
                       + ror_hydroout[PD + '.' + str(H) + '.' + ABA] * variable_o_m['hydro_run']
                       + model.daystoragehydroout[PD, H, ABA] * variable_o_m['hydro_daily']
                       + model.monthstoragehydroout[PD, H, ABA] * variable_o_m['hydro_monthly'] for H in h for ABA in
                       aba) / repday_scaling
            + quicksum(model.load_shedding[PD, H, ABA] * load_shedding_cost for ABA in aba for H in h) / repday_scaling
            + transmission_op_cost
            + CCS_om_cost
    )

wind_ofs_recon(model, PD, GL)

Constrains the reconstruction capacity of offshore wind facilities to not exceed the existing end-of-life capacity available for those facilities. This ensures that reconstructed capacity additions are within the limits of previously installed capacities that are due for replacement or have been phased out.

Parameters:

Name Type Description Default
model

A Pyomo model instance containing decision variables, parameters, and sets for the energy model.

required
PD

The planning period as a string (e.g., '2025') for which the constraint is applied.

required
GL

The grid location identifier for the offshore wind facility.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the capacity of reconstructed offshore wind facilities in location GL

during the planning period PD to the sum of the designated reconstruction capacity and any unused

reconstruction capacity from prior periods.

Source code in COPPER.py
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
def wind_ofs_recon(model, PD, GL):
    """
    Constrains the reconstruction capacity of offshore wind facilities to not exceed the existing end-of-life 
    capacity available for those facilities. This ensures that reconstructed capacity additions are within 
    the limits of previously installed capacities that are due for replacement or have been phased out.

    Parameters:
        model: A Pyomo model instance containing decision variables, parameters, and sets for the energy model.
        PD: The planning period as a string (e.g., '2025') for which the constraint is applied.
        GL: The grid location identifier for the offshore wind facility.

    Returns:
        A Pyomo Constraint expression: Limits the capacity of reconstructed offshore wind facilities in location GL 
        during the planning period PD to the sum of the designated reconstruction capacity and any unused 
        reconstruction capacity from prior periods.
    """
    ind = pds.index(PD)
    return model.capacity_wind_ofs_recon[PD, GL] <= wind_ofs_recon_capacity[PD + '.' + GL] + quicksum(
        wind_ofs_recon_capacity[PDD + '.' + GL] - model.capacity_wind_ofs_recon[PDD, GL] for PDD in pds[:ind])

wind_ons_recon(model, PD, GL)

same as wind_ofs_recon() but for onshore wind facilities

Source code in COPPER.py
2534
2535
2536
2537
2538
def wind_ons_recon(model, PD, GL):
    """same as wind_ofs_recon() but for onshore wind facilities"""
    ind = pds.index(PD)
    return model.capacity_wind_ons_recon[PD, GL] <= wind_ons_recon_capacity[PD + '.' + GL] + quicksum(
        wind_ons_recon_capacity[PDD + '.' + GL] - model.capacity_wind_ons_recon[PDD, GL] for PDD in pds[:ind])

windofscaplimit(model, GL)

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets.

required
GL

The geographical location identifier for the offshore wind facility.

required

Returns:

Type Description

A Pyomo Constraint expression: Limits the total installed capacity of offshore wind facilities at a specific

geographical location (GL) to not exceed a predefined maximum capacity (maxwindofs[GL]).

Details
  • This function aggregates the capacity of offshore wind facilities, both existing and reconstructed, across all planning periods to ensure it does not surpass the maximum allowable capacity defined for that location.
  • The constraint supports planning and regulatory compliance by maintaining capacity within sustainable and approved limits.
Source code in COPPER.py
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
def windofscaplimit(model, GL):
    """
    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets.
        GL: The geographical location identifier for the offshore wind facility.

    Returns:
        A Pyomo Constraint expression: Limits the total installed capacity of offshore wind facilities at a specific 
        geographical location (GL) to not exceed a predefined maximum capacity (`maxwindofs[GL]`).

    Details:
        - This function aggregates the capacity of offshore wind facilities, both existing and reconstructed, across all 
          planning periods to ensure it does not surpass the maximum allowable capacity defined for that location.
        - The constraint supports planning and regulatory compliance by maintaining capacity within sustainable and approved limits.
    """
    return quicksum(model.capacity_wind_ofs[PD, GL] + model.capacity_wind_ofs_recon[PD, GL] for PD in pds) <= \
        maxwindofs[GL]

windofsg(model, PD, H, ABA)

see windonsg() same as above but for offshore wind generation

Source code in COPPER.py
2448
2449
2450
2451
2452
2453
def windofsg(model, PD, H, ABA):
    """see windonsg() same as above but for offshore wind generation"""
    ind = pds.index(PD)
    return model.windofsout[PD, H, ABA] <= quicksum(
        (model.capacity_wind_ofs[PDD, GL] + model.capacity_wind_ofs_recon[PDD, GL]) * windcf_df.loc[H, int(GL)] for PDD
        in pds[:ind + 1] for GL in gl if ABA == map_gl_to_ba[int(GL)]) + extant_wind_ofs_gen.loc[(PD, H), ABA]

windonscaplimit(model, GL)

same as windofscaplimit but for wind ons plants

Source code in COPPER.py
3799
3800
3801
3802
def windonscaplimit(model, GL):
    """same as windofscaplimit but for wind ons plants"""
    return quicksum(model.capacity_wind_ons[PD, GL] + model.capacity_wind_ons_recon[PD, GL] for PD in pds) <= \
        maxwindons[GL]

windonsg(model, PD, H, ABA)

Constrains the onshore wind power output in a specific balancing area for each hour of simulation and planning period. This function limits the power output to the sum of the potential generation from all relevant wind facilities within the area, adjusted for capacity factor, plus any existing generation capacity.

Parameters:

Name Type Description Default
model

A Pyomo model instance containing all relevant decision variables, parameters, and sets for the energy model.

required
PD

The planning period as a string (e.g., '2025') during which the constraint is applied.

required
H

The specific hour within the simulation and planning period.

required
ABA

The balancing area as a string, indicating where the wind power generation is being considered.

required

Returns:

Type Description

A Pyomo Constraint expression: This ensures that the wind power output (model.windonsout) for the given

hour H, planning period PD, and balancing area ABA does not exceed the theoretical maximum based on installed

capacity (both existing and newly constructed) and the capacity factors for that hour as stored in extant_wind_solar.csv input sheet.

Details
  • The function calculates the maximum output by aggregating the potential generation from all wind turbines (considering both existing and newly constructed capacities) within the specified balancing area. The aggregation takes into account the capacity factors (windcf_df) which vary by hour and location.
  • The function also includes extant wind generation capacities (extant_wind_ons_gen) that are predefined for the specific hour and balancing area.
  • The capacity factor for each generator is accessed from a dataframe (windcf_df) using the hour and generator location identifier, which adjusts the maximum generation based on typical resource availability during that hour.
Source code in COPPER.py
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
def windonsg(model, PD, H, ABA):
    """
    Constrains the onshore wind power output in a specific balancing area for each hour of simulation and planning period. 
    This function limits the power output to the sum of the potential generation from all relevant wind facilities within 
    the area, adjusted for capacity factor, plus any existing generation capacity.

    Parameters:
        model: A Pyomo model instance containing all relevant decision variables, parameters, and sets for the energy model.
        PD: The planning period as a string (e.g., '2025') during which the constraint is applied.
        H: The specific hour within the simulation and planning period.
        ABA: The balancing area as a string, indicating where the wind power generation is being considered.

    Returns:
        A Pyomo Constraint expression: This ensures that the wind power output (`model.windonsout`) for the given 
        hour H, planning period PD, and balancing area ABA does not exceed the theoretical maximum based on installed 
        capacity (both existing and newly constructed) and the capacity factors for that hour as stored in extant_wind_solar.csv input sheet.

    Details:
        - The function calculates the maximum output by aggregating the potential generation from all wind turbines
          (considering both existing and newly constructed capacities) within the specified balancing area. 
          The aggregation takes into account the capacity factors (`windcf_df`) which vary by hour and location.
        - The function also includes extant wind generation capacities (`extant_wind_ons_gen`) that are predefined 
          for the specific hour and balancing area.
        - The capacity factor for each generator is accessed from a dataframe (`windcf_df`) using the hour and generator
          location identifier, which adjusts the maximum generation based on typical resource availability during that hour.
    """

    ind = pds.index(PD)
    return model.windonsout[PD, H, ABA] <= quicksum(
        (model.capacity_wind_ons[PDD, GL] + model.capacity_wind_ons_recon[PDD, GL]) * windcf_df.loc[H, int(GL)] for PDD
        in pds[:ind + 1] for GL in gl if ABA == map_gl_to_ba[int(GL)]) + extant_wind_ons_gen.loc[(PD, H), ABA]