Source code for prepshot.model

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""This module defines the PREP-SHOT model. The model is created using 
the pyoptinterface library.
"""

from prepshot.utils import cartesian_product
from prepshot._model.demand import AddDemandConstraints
from prepshot._model.generation import AddGenerationConstraints
from prepshot._model.cost import AddCostObjective
from prepshot._model.co2 import AddCo2EmissionConstraints
from prepshot._model.hydro import AddHydropowerConstraints
from prepshot._model.storage import AddStorageConstraints
from prepshot._model.transmission import AddTransmissionConstraints
from prepshot._model.investment import AddInvestmentConstraints
from prepshot._model.finance import AddFinanceConstraints
from prepshot.logs import timer
from prepshot.solver import get_solver
from prepshot.solver import set_solver_parameters

[docs]def define_model( params : dict ) -> object: """This function creates the model class depending on predefined solver. Parameters ---------- params : dict parameters for the model Returns ------- object A pyoptinterface Model object depending on the solver Raises ------ ValueError Unsupported or undefined solver """ solver = get_solver(params) model = solver.Model() model.params = params set_solver_parameters(model) return model
[docs]def define_basic_sets(model : object) -> None: """Define sets for the model. Parameters ---------- model : object Model object to be solved. """ params = model.params basic_sets = ["year", "zone", "tech", "hour", "month"] for set_name in basic_sets: setattr(model, set_name, params[set_name]) # TODO: Generate the hour_p set based on the hour set model.hour_p = [0] + params['hour'] # PyPSA-style: a free-form `carrier` string plus per-tech behavior # flags. Hydropower is a special-cased carrier (carrier == 'hydro') # so the existing reservoir / water-flow constraints continue to # apply unchanged. Other behaviors (VRE, storage, must-run) are # driven by the boolean flag columns rather than a fixed type enum. techs_df = params['technologies'] def _tech_filter(mask): return techs_df.loc[mask, 'tech'].tolist() model.hydro_tech = _tech_filter(techs_df['carrier'] == 'hydro') model.storage_tech = _tech_filter(techs_df['is_storage'].astype(bool)) # PyPSA-style: variable / must-run / curtailable behaviors are no # longer flag-driven. Any tech can have a time-varying max/min # generation profile via tech_max_gen_profile.csv / # tech_min_gen_profile.csv. The unified per-tech generation bound # constraint lives in AddGenerationConstraints. # `dispatchable_tech` = anything that's not hydro and not storage # (i.e. its generation is bounded by cap × p_max_pu). special_mask = ( (techs_df['carrier'] == 'hydro') | techs_df['is_storage'].astype(bool) ) model.dispatchable_tech = _tech_filter(~special_mask) model.tech_types = ['dispatchable_tech', 'hydro_tech', 'storage_tech'] if params['isinflow']: # Hydro plants are first-class techs (carrier='hydro'); # model.station is the list of those tech names for # backwards-compat with the hydro module. model.station = model.hydro_tech
[docs]def define_complex_sets(model : object) -> None: """Create complex sets based on simple sets and some conditations. The existing capacity between two zones is set to empty (i.e., No value is filled in the Excel cell), which means that these two zones cannot have newly built transmission lines. If you want to enable two zones which do not have any existing transmission lines, to build new transmission lines in the planning horizon, you need to set their capacity as zero explicitly. Parameters ---------- model : object Model to be solved. """ # transmission_existing is keyed by (zone1, zone2, commission_year); # auto-pad missing zone pairs (e.g. self-loops) with a sentinel # commission_year=0 entry of value=0. Efficiency padding uses the # 2-tuple key it always had. trans_pairs = {(z1, z2) for (z1, z2, _cy) in model.params['transmission_existing'].keys()} for z_i, z1_i in cartesian_product(model.zone, model.zone): if (z_i, z1_i) not in trans_pairs: model.params['transmission_existing'][z_i, z1_i, 0] = 0 model.params['transmission_line_efficiency'][z_i, z1_i] = 0
# TODO: Set the capacity of new transmission lines to 0
[docs]def define_variables(model : object) -> None: """Define variables for the model. Parameters ---------- model : object Model to be solved. """ model.cap_newtech = model.add_variables( model.year, model.zone, model.tech, lb=0 ) model.cap_newline = model.add_variables( model.year, model.zone, model.zone, lb=0 ) model.gen = model.add_variables( model.hour, model.month, model.year, model.zone, model.tech, lb=0 ) model.storage = model.add_variables( model.hour_p, model.month, model.year, model.zone, model.tech, lb=0 ) model.charge = model.add_variables( model.hour, model.month, model.year, model.zone, model.tech, lb=0 ) model.trans_export = model.add_variables( model.hour, model.month, model.year, model.zone, model.zone, lb=0 ) if model.params['isinflow']: model.genflow = model.add_variables( model.station, model.hour, model.month, model.year, lb=0 ) model.spillflow = model.add_variables( model.station, model.hour, model.month, model.year, lb=0 ) model.withdraw = model.add_variables( model.station, model.hour, model.month, model.year, lb=0 ) model.storage_reservoir = model.add_variables( model.station, model.hour_p, model.month, model.year, lb=0 ) model.output = model.add_variables( model.station, model.hour, model.month, model.year, lb=0 )
[docs]def define_constraints(model : object) -> None: """Define constraints for the model. Parameters ---------- model : object Model to be solved. """ AddInvestmentConstraints(model) AddGenerationConstraints(model) AddTransmissionConstraints(model) AddCo2EmissionConstraints(model) AddStorageConstraints(model) AddHydropowerConstraints(model) AddDemandConstraints(model)
[docs]@timer def create_model(params : dict) -> object: """Create the PREP-SHOT model. Parameters ---------- params : dict Dictionary of parameters for the model. Returns ------- object Model object. """ model = define_model(params) define_basic_sets(model) define_complex_sets(model) define_variables(model) define_constraints(model) AddCostObjective(model) # Optional public-debt accounting: only wired up when the user # supplies a finance dataset (public_debt_ratio + cost-of-capital # tables). Reads cost_newtech_breakdown built by AddCostObjective. if params.get('public_debt_ratio'): AddFinanceConstraints(model) return model