def test_error1(self): model = AbstractModel() try: model.a = BuildAction() self.fail("Expected ValueError") except ValueError: pass
def test_dense_param(self): # # Create model instance # model = AbstractModel() model.Z = Set(initialize=[1,3]) model.A = Param(model.Z, initialize=1.3, mutable=True) model.action2 = BuildAction(model.Z, rule=action2_fn) instance = model.create_instance() # self.assertEqual( instance.A[1].value, 2.3) self.assertEqual( value(instance.A[3]), 4.3) # buf = StringIO() instance.pprint(ostream=buf) self.assertEqual(buf.getvalue(),"""1 Set Declarations Z : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members None : 1 : Any : 2 : {1, 3} 1 Param Declarations A : Size=2, Index=Z, Domain=Any, Default=None, Mutable=True Key : Value 1 : 2.3 3 : 4.3 1 BuildAction Declarations action2 : Size=0, Index=Z, Active=True 3 Declarations: Z A action2 """)
def setUp(self): # # Create model instance # model = AbstractModel() model.A = Param(initialize=3.3, mutable=True) model.action1 = BuildAction(rule=action1_fn) self.instance = model.create_instance()
def _create(self, group=None): """ Creates the linear constraint for the class:`ElectricalLine` block. Parameters ---------- group : list List of oemof.solph.ElectricalLine (eline) objects for which the linear relation of inputs and outputs is created e.g. group = [eline1, eline2, ...]. The components inside the list need to hold a attribute `reactance` of type Sequence containing the reactance of the line. """ if group is None: return None m = self.parent_block() # create voltage angle variables self.ELECTRICAL_BUSES = Set( initialize=[n for n in m.es.nodes if isinstance(n, ElectricalBus)]) def _voltage_angle_bounds(block, b, t): return b.v_min, b.v_max self.voltage_angle = Var(self.ELECTRICAL_BUSES, m.TIMESTEPS, bounds=_voltage_angle_bounds) if True not in [b.slack for b in self.ELECTRICAL_BUSES]: # TODO: Make this robust to select the same slack bus for # the same problems bus = [b for b in self.ELECTRICAL_BUSES][0] logging.info( "No slack bus set,setting bus {0} as slack bus".format( bus.label)) bus.slack = True def _voltage_angle_relation(block): for t in m.TIMESTEPS: for n in group: if n.input.slack is True: self.voltage_angle[n.output, t].value = 0 self.voltage_angle[n.output, t].fix() try: lhs = m.flow[n.input, n.output, t] rhs = 1 / n.reactance[t] * ( self.voltage_angle[n.input, t] - self.voltage_angle[n.output, t]) except: raise ValueError("Error in constraint creation", "of node {}".format(n.label)) block.electrical_flow.add((n, t), (lhs == rhs)) self.electrical_flow = Constraint(group, m.TIMESTEPS, noruleinit=True) self.electrical_flow_build = BuildAction(rule=_voltage_angle_relation)
def test_sparse_param_default(self): # # Create model instance # model = AbstractModel() model.Z = Set(initialize=[1,3]) model.A = Param(model.Z, initialize={1:1.3}, default=0, mutable=True) model.action2 = BuildAction(model.Z, rule=action2_fn) instance = model.create_instance() tmp = value(instance.A[1]) self.assertEqual( type(tmp), float) self.assertEqual( tmp, 2.3 )
def _create(self, group=None): """ """ if group is None: return None m = self.parent_block() # create voltage angle variables self.ELECTRICAL_BUSES = Set( initialize=[n for n in m.es.nodes if isinstance(n, ElectricalBus)]) def _voltage_angle_bounds(block, b, t): return b.v_min, b.v_max self.voltage_angle = Var(self.ELECTRICAL_BUSES, m.TIMESTEPS, bounds=_voltage_angle_bounds) if True not in [b.slack for b in self.ELECTRICAL_BUSES]: # TODO: Make this robust to select the same slack bus for # the same problems bus = [b for b in self.ELECTRICAL_BUSES][0] logging.info( "No slack bus set,setting bus {0} as slack bus".format( bus.label)) bus.slack = True def _voltage_angle_relation(block): for t in m.TIMESTEPS: for n in group: if n.input.slack is True: self.voltage_angle[n.output, t].value = 0 self.voltage_angle[n.output, t].fix() try: lhs = m.flow[n.input, n.output, t] rhs = 1 / n.reactance[t] * ( self.voltage_angle[n.input, t] - self.voltage_angle[n.output, t]) except: raise ValueError("Error in constraint creation", "of node {}".format(n.label)) block.electrical_flow.add((n, t), (lhs == rhs)) self.electrical_flow = Constraint(group, m.TIMESTEPS, noruleinit=True) self.electrical_flow_build = BuildAction(rule=_voltage_angle_relation)
def _create(self, group=None): """ Creates the relation for the class:`Link`. Parameters ---------- group : list List of oemof.solph.custom.Link objects for which the relation of inputs and outputs is createdBuildAction e.g. group = [link1, link2, link3, ...]. The components inside the list need to hold an attribute `conversion_factors` of type dict containing the conversion factors for all inputs to outputs. """ if group is None: return None m = self.parent_block() all_conversions = {} for n in group: all_conversions[n] = { k: v for k, v in n.conversion_factors.items() } def _input_output_relation(block): for t in m.TIMESTEPS: for n, conversion in all_conversions.items(): for cidx, c in conversion.items(): try: expr = (m.flow[n, cidx[1], t] == c[t] * m.flow[cidx[0], n, t]) except ValueError: raise ValueError( "Error in constraint creation", "from: {0}, to: {1}, via: {3}".format( cidx[0], cidx[1], n)) block.relation.add((n, cidx[0], cidx[1], t), (expr)) self.relation = Constraint( [(n, cidx[0], cidx[1], t) for t in m.TIMESTEPS for n, conversion in all_conversions.items() for cidx, c in conversion.items()], noruleinit=True) self.relation_build = BuildAction(rule=_input_output_relation)
def define_components(m): # benders suffix should have 0 for master problem or n for each subproblem; # see https://ampl.com/products/solvers/solvers-we-sell/cplex/options/ # and https://www.ibm.com/support/knowledgecenter/en/SSSA5P_12.7.1/ilog.odms.cplex.help/CPLEX/Parameters/topics/BendersStrategy.html # solve with --solver cplexamp --solver-options-string "bendersopt='' benders_strategy=2" # but note: subproblems must be strictly continuous or you get error 2002 (bad decomposition): # https://www.ibm.com/developerworks/community/forums/html/topic?id=086bfa2b-35fd-4987-aca2-1e31b7fd2413 # https://orinanobworld.blogspot.com/2013/07/benders-decomposition-with-integer.html # https://groups.google.com/forum/#!topic/aimms/OsMHRV-HXhk m.benders = Suffix(direction=Suffix.EXPORT) def rule(m): # place all build vars in the master problem (0), others in subproblem (1) for var in m.component_objects(Var): suf = 0 if var.name in fix_vars else 1 for obj in var.values(): m.benders[obj] = suf m.Assign_Benders_Suffixes = BuildAction(rule=rule)
def _create(self, group=None): if group is None: return None m = self.parent_block() # for all DSM components get inflow from bus_elec for n in group: n.inflow = list(n.inputs)[0] # ************* SETS ********************************* # Set of DSM Components self.DSM = Set(initialize=[n for n in group]) # ************* VARIABLES ***************************** # Variable load shift down (MWh) self.DSMdo = Var(self.DSM, m.TIMESTEPS, m.TIMESTEPS, initialize=0, within=NonNegativeReals) # Variable load shift up(MWh) self.DSMup = Var(self.DSM, m.TIMESTEPS, initialize=0, within=NonNegativeReals) # ************* CONSTRAINTS ***************************** # Demand Production Relation def _input_output_relation_rule(block): """ Relation between input data and pyomo variables. The actual demand after DSM. Generator Production == Demand +- DSM """ for t in m.TIMESTEPS: for g in group: # first time steps: 0 + delay time if t <= g.delay_time: # Generator loads from bus lhs = m.flow[g.inflow, g, t] # Demand +- DSM rhs = g.demand[t] + self.DSMup[g, t] - sum( self.DSMdo[g, tt, t] for tt in range(t + g.delay_time + 1)) # add constraint block.input_output_relation.add((g, t), (lhs == rhs)) # main use case elif g.delay_time < t <= m.TIMESTEPS._bounds[1] - g.delay_time: # Generator loads from bus lhs = m.flow[g.inflow, g, t] # Demand +- DSM rhs = g.demand[t] + self.DSMup[g, t] - sum( self.DSMdo[g, tt, t] for tt in range(t - g.delay_time, t + g.delay_time + 1)) # add constraint block.input_output_relation.add((g, t), (lhs == rhs)) # last time steps: end - delay time else: # Generator loads from bus lhs = m.flow[g.inflow, g, t] # Demand +- DSM rhs = g.demand[t] + self.DSMup[g, t] - sum( self.DSMdo[g, tt, t] for tt in range(t - g.delay_time, m.TIMESTEPS._bounds[1] + 1)) # add constraint block.input_output_relation.add((g, t), (lhs == rhs)) self.input_output_relation = Constraint(group, m.TIMESTEPS, noruleinit=True) self.input_output_relation_build = BuildAction(rule=_input_output_relation_rule) # Equation 7 def dsmupdo_constraint_rule(block): ''' Equation 7 by Zerrahn, Schill: Every upward load shift has to be compensated by downward load shifts in a defined time frame. Slightly modified equations for the first and last time steps due to variable initialization. ''' for t in m.TIMESTEPS: for g in group: # first time steps: 0 + delay time if t <= g.delay_time: # DSM up lhs = self.DSMup[g, t] # DSM down rhs = sum(self.DSMdo[g, t, tt] for tt in range(t + g.delay_time + 1)) # add constraint block.dsmupdo_constraint.add((g, t), (lhs == rhs)) # main use case elif g.delay_time < t <= m.TIMESTEPS._bounds[1] - g.delay_time: # DSM up lhs = self.DSMup[g, t] # DSM down rhs = sum(self.DSMdo[g, t, tt] for tt in range(t - g.delay_time, t + g.delay_time + 1)) # add constraint block.dsmupdo_constraint.add((g, t), (lhs == rhs)) # last time steps: end - delay time else: # DSM up lhs = self.DSMup[g, t] # DSM down rhs = sum(self.DSMdo[g, t, tt] for tt in range(t - g.delay_time, m.TIMESTEPS._bounds[1] + 1)) # add constraint block.dsmupdo_constraint.add((g, t), (lhs == rhs)) self.dsmupdo_constraint = Constraint(group, m.TIMESTEPS, noruleinit=True) self.dsmupdo_constraint_build = BuildAction(rule=dsmupdo_constraint_rule) # Equation 8 def dsmup_constraint_rule(block): ''' Equation 8 by Zerrahn, Schill: Realised upward load shift at time t has to be smaller than upward DSM capacity at time t. ''' for t in m.TIMESTEPS: for g in group: # DSM up lhs = self.DSMup[g, t] # Capacity DSMup rhs = g.c_do[t] # add constraint block.dsmup_constraint.add((g, t), (lhs <= rhs)) self.dsmup_constraint = Constraint(group, m.TIMESTEPS, noruleinit=True) self.dsmup_constraint_build = BuildAction(rule=dsmup_constraint_rule) # Equation 9 def dsmdo_constraint_rule(block): ''' Equation 9 by Zerrahn, Schill: Realised downward load shift at time t has to be smaller than downward DSM capacity at time t. ''' for tt in m.TIMESTEPS: for g in group: # first times steps: 0 + delay time if tt <= g.delay_time: # DSM down lhs = sum(self.DSMdo[g, t, tt] for t in range(tt + g.delay_time + 1)) # Capacity DSM down rhs = g.c_do[tt] # add constraint block.dsmdo_constraint.add((g, tt), (lhs <= rhs)) # main use case elif g.delay_time < tt <= m.TIMESTEPS._bounds[1] - g.delay_time: # DSM down lhs = sum(self.DSMdo[g, t, tt] for t in range(tt - g.delay_time, tt + g.delay_time + 1)) # Capacity DSM down rhs = g.c_do[tt] # add constraint block.dsmdo_constraint.add((g, tt), (lhs <= rhs)) # last time steps: end - delay time else: # DSM down lhs = sum(self.DSMdo[g, t, tt] for t in range(tt - g.delay_time, m.TIMESTEPS._bounds[1] + 1)) # Capacity DSM down rhs = g.c_do[tt] # add constraint block.dsmdo_constraint.add((g, tt), (lhs <= rhs)) self.dsmdo_constraint = Constraint(group, m.TIMESTEPS, noruleinit=True) self.dsmdo_constraint_build = BuildAction(rule=dsmdo_constraint_rule) # Equation 10 def C2_constraint_rule(block): ''' Equation 10 by Zerrahn, Schill: The realised DSM up or down at time T has to be smaller than the maximum downward or upward capacity at time T. Therefore in total each DSM unit can only be shifted up OR down. ''' for tt in m.TIMESTEPS: for g in group: # first times steps: 0 + delay time if tt <= g.delay_time: # DSM up/down lhs = self.DSMup[g, tt] + sum(self.DSMdo[g, t, tt] for t in range(tt + g.delay_time + 1)) # max capacity at tt rhs = max(g.c_up[tt], g.c_do[tt]) # add constraint block.C2_constraint.add((g, tt), (lhs <= rhs)) elif g.delay_time < tt <= m.TIMESTEPS._bounds[1] - g.delay_time: # DSM up/down lhs = self.DSMup[g, tt] + sum( self.DSMdo[g, t, tt] for t in range(tt - g.delay_time, tt + g.delay_time + 1)) # max capacity at tt rhs = max(g.c_up[tt], g.c_do[tt]) # add constraint block.C2_constraint.add((g, tt), (lhs <= rhs)) else: # DSM up/down lhs = self.DSMup[g, tt] + sum( self.DSMdo[g, t, tt] for t in range(tt - g.delay_time, m.TIMESTEPS._bounds[1] + 1)) # max capacity at tt rhs = max(g.c_up[tt], g.c_do[tt]) # add constraint block.C2_constraint.add((g, tt), (lhs <= rhs)) self.C2_constraint = Constraint(group, m.TIMESTEPS, noruleinit=True) self.C2_constraint_build = BuildAction(rule=C2_constraint_rule) def equivalent_power_constraint_rule(block): ''' new rule: inactive and doesnt work yet''' for t in m.TIMESTEPS: for g in group: # first time steps: 0 + delay time if t <= g.delay_time: lhs = 0 rhs = value(min(self.DSMup[g, t], sum(self.DSMdo[g, t, tt] for tt in range(t + g.delay_time + 1)))) block.input_output_relation.add((g, t), (lhs == rhs)) # main use case elif g.delay_time < t <= m.TIMESTEPS._bounds[1] - g.delay_time: lhs = 0 rhs = value(min(self.DSMup[g, t], sum(self.DSMdo[g, t, tt] for tt in range(t - g.delay_time, t + g.delay_time + 1)))) # add constraint block.input_output_relation.add((g, t), (lhs == rhs)) # last time steps: end - delay time else: lhs = 0 rhs = value(min(self.DSMup[g, t], sum(self.DSMdo[g, t, tt] for tt in range(t - g.delay_time, m.TIMESTEPS._bounds[1] + 1)))) block.input_output_relation.add((g, t), (lhs == rhs))
initialize=check_inistageon) amodel.geninion = Param(amodel.generator, within=NonNegativeReals, initialize=enforce_inistageon) amodel.geninioff = Param(amodel.generator, within=NonNegativeReals, initialize=enforce_inistageoff) #%% generation cost function amodel.pgenK0 = Param(amodel.generator, within=NonNegativeReals) amodel.PCost0 = Param(amodel.generator, within=NonNegativeReals) amodel.PCost1 = Param(amodel.generator, within=NonNegativeReals) amodel.PCost2 = Param(amodel.generator, within=NonNegativeReals) amodel.numcurve = Param(Within=PositiveIntegers, default=3) amodel.createpoints = {} amodel.CreatePowerGenerationPiecewisePoints = BuildAction( amodel.generator * amodel.nperiods, rule=gen_piecewise_curve) amodel.ShutdownCostCoefficient = Param(amodel.generator, within=NonNegativeReals, default=0.0) #%% vars amodel.UnitOn = Var(amodel.generator, amodel.nperiods, within=Binary) amodel.PowerGenerated = Var(amodel.generator, amodel.nperiods, within=NonNegativeReals) amodel.MaximumPowerAvailable = Var(amodel.generator, amodel.nperiods, within=NonNegativeReals) amodel.ProductionCost = Var(amodel.generator, amodel.nperiods,
def _create(self, group=None): """ Creates the linear constraint for the :class:`oemof.solph.Transformer` block. Parameters ---------- group : list List of :class:`oemof.solph.ExtractionTurbineCHP` (trsf) objects for which the linear relation of inputs and outputs is created e.g. group = [trsf1, trsf2, trsf3, ...]. Note that the relation is created for all existing relations of the inputs and all outputs of the transformer. The components inside the list need to hold all needed attributes. """ if group is None: return None m = self.parent_block() for n in group: n.inflow = list(n.inputs)[0] n.label_main_flow = str([ k for k, v in n.conversion_factor_full_condensation.items() ][0]) n.main_output = [ o for o in n.outputs if n.label_main_flow == o.label ][0] n.tapped_output = [ o for o in n.outputs if n.label_main_flow != o.label ][0] n.conversion_factor_full_condensation_sq = ( n.conversion_factor_full_condensation[m.es.groups[ n.main_output.label]]) n.flow_relation_index = [ n.conversion_factors[m.es.groups[n.main_output.label]][t] / n.conversion_factors[m.es.groups[n.tapped_output.label]][t] for t in m.TIMESTEPS ] n.main_flow_loss_index = [ (n.conversion_factor_full_condensation_sq[t] - n.conversion_factors[m.es.groups[n.main_output.label]][t]) / n.conversion_factors[m.es.groups[n.tapped_output.label]][t] for t in m.TIMESTEPS ] def _input_output_relation_rule(block): """Connection between input, main output and tapped output. """ for t in m.TIMESTEPS: for g in group: lhs = m.flow[g.inflow, g, t] rhs = ((m.flow[g, g.main_output, t] + m.flow[g, g.tapped_output, t] * g.main_flow_loss_index[t]) / g.conversion_factor_full_condensation_sq[t]) block.input_output_relation.add((g, t), (lhs == rhs)) self.input_output_relation = Constraint(group, m.TIMESTEPS, noruleinit=True) self.input_output_relation_build = BuildAction( rule=_input_output_relation_rule) def _out_flow_relation_rule(block): """Relation between main and tapped output in full chp mode. """ for t in m.TIMESTEPS: for g in group: lhs = m.flow[g, g.main_output, t] rhs = (m.flow[g, g.tapped_output, t] * g.flow_relation_index[t]) block.out_flow_relation.add((g, t), (lhs >= rhs)) self.out_flow_relation = Constraint(group, m.TIMESTEPS, noruleinit=True) self.out_flow_relation_build = BuildAction( rule=_out_flow_relation_rule)
def create_model(namestr, unslim=False, emitlimit=False, nem_ret_ratio=False, nem_ret_gwh=False, region_ret_ratio=False, nem_disp_ratio=False, nem_re_disp_ratio=False): """Creates an instance of the pyomo definition of openCEM""" m = AbstractModel(name=namestr) # Sets m.regions = Set(initialize=cemo.const.REGION.keys()) # Set of NEM regions m.zones = Set( initialize=cemo.const.ZONE.keys()) # Set of NTNDP planning zones m.all_tech = Set( initialize=cemo.const.ALL_TECH) # Set of generation technologies # set of fuel based gen technologies m.fuel_gen_tech = Set(initialize=cemo.const.FUEL_TECH) & m.all_tech # set of gen techs that obey linearised unit commitment constraints m.commit_gen_tech = Set(initialize=cemo.const.COMMIT_TECH) & m.all_tech # set of retireable technologies m.retire_gen_tech = Set(initialize=cemo.const.RETIRE_TECH) & m.all_tech # set of retireable technologies m.nobuild_gen_tech = Set(initialize=cemo.const.NOBUILD_TECH) & m.all_tech # Set of storage technologies m.stor_tech = Set(initialize=cemo.const.STOR_TECH) & m.all_tech # set of hybrid (gen+storage) technologies m.hyb_tech = Set(initialize=cemo.const.HYB_TECH) & m.all_tech # Set of dispatch intervals m.t = Set(ordered=True) # Sparse set of zones per region m.zones_in_regions = Set(dimen=2, initialize=init_zones_in_regions) # Set listing technologies avaialable per zone (like a sparsity pattern) m.gen_tech_in_zones = Set(dimen=2) # Retirable technologies avaialable per zone (like a sparsity pattern) m.retire_gen_tech_in_zones = Set(dimen=2) # Fuel/emmitting technologies avaialable per zone (like a sparsity pattern) m.fuel_gen_tech_in_zones = Set(dimen=2) # Fuel/emmitting technologies avaialable per zone (like a sparsity pattern) m.commit_gen_tech_in_zones = Set(dimen=2) # Renewable technologies avaialable per zone (like a sparsity pattern) m.re_gen_tech_in_zones = Set(dimen=2) # Dispatchable technologies avaialable per zone (like a sparsity pattern) m.disp_gen_tech_in_zones = Set(dimen=2) # Renewable Dispatchable technologies avaialable per zone (like a sparsity pattern) m.re_disp_gen_tech_in_zones = Set(dimen=2) # Set listing storage avaialable per zone (like a sparsity pattern) m.hyb_tech_in_zones = Set(dimen=2) # Set listing storage avaialable per zone (like a sparsity pattern) m.stor_tech_in_zones = Set(dimen=2) # Set listing transmission lines to other regions in each region m.intercons_in_zones = Set(dimen=2, initialize=init_intercons_in_zones) # sparse sets built by build actions # Returns a list of planning zones for each region in R m.zones_per_region = Set(m.regions, within=m.zones, initialize=[]) # Returns a tuple with generating techs in each zone m.gen_tech_per_zone = Set(m.zones, within=m.all_tech, initialize=[]) # Returns a tuple with emitting techs in each zone m.fuel_gen_tech_per_zone = Set(m.zones, within=m.all_tech, initialize=[]) # tuple for techs that obey linearised unit commitment constraints m.commit_gen_tech_per_zone = Set(m.zones, within=m.all_tech, initialize=[]) m.re_gen_tech_per_zone = Set(m.zones, within=m.all_tech, initialize=[]) m.disp_gen_tech_per_zone = Set(m.zones, within=m.all_tech, initialize=[]) m.re_disp_gen_tech_per_zone = Set(m.zones, within=m.all_tech, initialize=[]) # Returns a tuple with retirable techs in each zone m.retire_gen_tech_per_zone = Set(m.zones, within=m.all_tech, initialize=[]) # Returns a tuple with storage techs in each zone m.stor_tech_per_zone = Set(m.zones, within=m.stor_tech, initialize=[]) # Returns a tuple with emitting techs in each zone m.hyb_tech_per_zone = Set(m.zones, within=m.all_tech, initialize=[]) # returns a tuple with transmission links in each region m.intercon_per_zone = Set(m.zones, initialize=[]) # @@ Build actions # Scan TechinZones and populate ?_gen_tech_per_zone m.TpZ_build = BuildAction(rule=ScanForTechperZone) # Scan HybTechinZones and populate hyb_tech_per_zone m.HpZ_build = BuildAction(rule=ScanForHybridperZone) # Scan ZinR and populate ZperR m.ZpR_build = BuildAction(rule=ScanForZoneperRegion) # Scan TransLines and populate intercon_per_zone m.intercon_build = BuildAction(rule=build_intercon_per_zone) # Scan StorinZones and populate stor_tech_per_zone m.SpZ_build = BuildAction(rule=ScanForStorageperZone) # @@ Parameters # Capital costs generators # Build costs for generators m.cost_gen_build = Param(m.gen_tech_in_zones, default=9e7) m.cost_stor_build = Param(m.stor_tech_in_zones) # Capital costs storage m.cost_hyb_build = Param(m.hyb_tech_in_zones) # Capital costs hybrid # Capital costs $/MW/km trans m.cost_intercon_build = Param(m.intercons_in_zones, initialize=init_intercon_build_cost) m.cost_fuel = Param(m.fuel_gen_tech_in_zones, initialize=init_default_fuel_price) # Fuel cost # Fixed operating costs generators m.cost_gen_fom = Param(m.all_tech) # Variable operating costs generators m.cost_gen_vom = Param(m.all_tech) # Fixed operating costs storage m.cost_stor_fom = Param(m.stor_tech) # Variable operating costs storage m.cost_stor_vom = Param(m.stor_tech) # Fixed operating costs hybrid m.cost_hyb_fom = Param(m.hyb_tech) # Variable operating costs hybrid m.cost_hyb_vom = Param(m.hyb_tech) # Technology lifetime in years m.all_tech_lifetime = Param(m.all_tech, initialize=init_default_lifetime) # Project discount rate m.all_tech_discount_rate = Param(default=0.05) # Generator tech fixed charge rate m.fixed_charge_rate = Param(m.all_tech, initialize=init_fcr) # Transmission tech fixed charge rate m.intercon_fixed_charge_rate = Param(initialize=init_intercon_fcr) # Per year cost adjustment for sims shorter than 1 year of dispatch m.year_correction_factor = Param(initialize=init_year_correction_factor) m.cost_retire = Param(m.retire_gen_tech, initialize=init_cost_retire) m.cost_unserved = Param(initialize=cemo.const.DEFAULT_COSTS["unserved"] ) # cost of unserved power # cost in $/kg of total emissions m.cost_emit = Param(initialize=cemo.const.DEFAULT_COSTS["emit"]) m.cost_trans = Param( initialize=cemo.const.DEFAULT_COSTS["trans"]) # cost of transmission # Round trip efficiency of storage technology m.stor_rt_eff = Param(m.stor_tech, initialize=init_stor_rt_eff) # Number of hours of storage technology m.stor_charge_hours = Param(m.stor_tech, initialize=init_stor_charge_hours) # Collector multiple of hybrid technology m.hyb_col_mult = Param(m.hyb_tech, initialize=init_hyb_col_mult) # Number of hours of storage technology m.hyb_charge_hours = Param(m.hyb_tech, initialize=init_hyb_charge_hours) m.fuel_heat_rate = Param(m.fuel_gen_tech_in_zones, initialize=init_default_heat_rate) # Emission rates m.fuel_emit_rate = Param(m.fuel_gen_tech, initialize=init_default_fuel_emit_rate) # proportioning factors for notional interconnectors m.intercon_loss_factor = Param(m.intercons_in_zones, initialize=init_intercon_loss_factor) m.gen_cap_factor = Param( m.gen_tech_in_zones, m.t, initialize=init_cap_factor) # Capacity factors for generators m.hyb_cap_factor = Param( m.hyb_tech_in_zones, m.t, initialize=init_cap_factor) # Capacity factors for generators # Maximum capacity per generating technology per zone m.gen_build_limit = Param(m.gen_tech_in_zones, initialize=init_gen_build_limit) m.gen_cap_initial = Param(m.gen_tech_in_zones, default=0) # operating capacity m.stor_cap_initial = Param(m.stor_tech_in_zones, default=0) # operating capacity m.hyb_cap_initial = Param(m.hyb_tech_in_zones, default=0) # operating capacity m.intercon_cap_initial = Param( m.intercons_in_zones, initialize=init_intercon_cap_initial) # operating capacity # exogenous new capacity m.gen_cap_exo = Param(m.gen_tech_in_zones, default=0) # exogenous new storage capacity m.stor_cap_exo = Param(m.stor_tech_in_zones, default=0) # exogenous new hybrid capacity m.hyb_cap_exo = Param(m.hyb_tech_in_zones, default=0) # exogenous transmission capacity m.intercon_cap_exo = Param(m.intercons_in_zones, default=0) m.ret_gen_cap_exo = Param(m.retire_gen_tech_in_zones, default=0) # Net Electrical load (may include rooftop and EV) m.region_net_demand = Param(m.regions, m.t) # Zone load distribution factors as a pct of region demand m.zone_demand_factor = Param(m.zones, m.t, initialize=init_zone_demand_factors) # NEM operating reserve margin m.nem_operating_reserve = Param(default=0.075) # carry forward capital costs calculated m.cost_cap_carry_forward_sim = Param(m.zones, default=0) # carry forward capital costs NEM historical estimate m.cost_cap_carry_forward_hist = Param(m.zones, default=0) # carry forward capital costs total m.cost_cap_carry_forward = Param(m.zones, mutable=True) # Build action to Compute carry forward costs m.cost_carry_forward_build = BuildAction( rule=build_carry_fwd_cost_per_zone) # @@ Variables m.gen_cap_new = Var(m.gen_tech_in_zones, within=NonNegativeReals) # New capacity m.gen_cap_op = Var(m.gen_tech_in_zones, within=NonNegativeReals) # Total generation capacity m.stor_cap_new = Var(m.stor_tech_in_zones, within=NonNegativeReals) # New storage capacity m.stor_cap_op = Var(m.stor_tech_in_zones, within=NonNegativeReals) # Total storage capacity m.hyb_cap_new = Var(m.hyb_tech_in_zones, within=NonNegativeReals) m.hyb_cap_op = Var(m.hyb_tech_in_zones, within=NonNegativeReals) m.intercon_cap_new = Var(m.intercons_in_zones, within=NonNegativeReals) m.intercon_cap_op = Var(m.intercons_in_zones, within=NonNegativeReals) m.gen_cap_ret = Var(m.retire_gen_tech_in_zones, within=NonNegativeReals) # retireable capacity m.gen_cap_ret_neg = Var(m.retire_gen_tech_in_zones, within=NonNegativeReals ) # slack for exogenous retires beyond gen_op_cap m.gen_cap_exo_neg = Var( m.gen_tech_in_zones, within=NonNegativeReals ) # slack for exogenous builds exceeding gen_build_limit m.gen_disp = Var(m.gen_tech_in_zones, m.t, within=NonNegativeReals) # dispatched power # Variables for committed power constraints m.gen_disp_com = Var(m.commit_gen_tech_in_zones, m.t, within=NonNegativeReals) m.gen_disp_com_p = Var(m.commit_gen_tech_in_zones, m.t, within=NonNegativeReals) m.gen_disp_com_m = Var(m.commit_gen_tech_in_zones, m.t, within=NonNegativeReals) m.gen_disp_com_s = Var(m.commit_gen_tech_in_zones, m.t, within=NonNegativeReals) m.stor_disp = Var(m.stor_tech_in_zones, m.t, within=NonNegativeReals) # dispatched power from storage m.stor_reserve = Var( m.stor_tech_in_zones, m.t, within=NonNegativeReals) # dispatched power from storage m.stor_charge = Var(m.stor_tech_in_zones, m.t, within=NonNegativeReals) # power to charge storage m.hyb_disp = Var(m.hyb_tech_in_zones, m.t, within=NonNegativeReals) # dispatched power from hybrid m.hyb_reserve = Var( m.hyb_tech_in_zones, m.t, within=NonNegativeReals) # reserve capacity for hybrids m.hyb_charge = Var(m.hyb_tech_in_zones, m.t, within=NonNegativeReals) # charging power from hybrid m.stor_level = Var(m.stor_tech_in_zones, m.t, within=NonNegativeReals) # Charge level for storage m.hyb_level = Var(m.hyb_tech_in_zones, m.t, within=NonNegativeReals) # Charge level for storage m.unserved = Var(m.zones, m.t, within=NonNegativeReals) # unserved power m.surplus = Var(m.zones, m.t, within=NonNegativeReals) # surplus power (if any) # Interconnector flow m.intercon_disp = Var(m.intercons_in_zones, m.t, within=NonNegativeReals) # @@ Constraints # Transmission limits m.con_max_trans = Constraint(m.intercons_in_zones, m.t, rule=con_max_trans) # Transmission capacity balance m.con_intercon_cap = Constraint(m.intercons_in_zones, rule=con_intercon_cap) # Load balance m.ldbal = Constraint(m.zones, m.t, rule=con_ldbal) # Dispatch to be within capacity, RE have variable capacity factors m.caplim = Constraint(m.gen_tech_in_zones, m.t, rule=con_caplim) # Limit maximum capacity to be built in each region and each technology m.maxcap = Constraint(m.gen_tech_in_zones, rule=con_maxcap) # gen_cap_op in existing period is previous gen_cap_op plus gen_cap_new m.con_gen_cap = Constraint(m.gen_tech_in_zones, rule=con_gen_cap) # MaxMWh limit m.con_max_mwh_per_zone = Constraint(m.gen_tech_in_zones, rule=con_max_mhw_per_zone) # MaxMWh limit (currently only for hydro) m.con_max_mwh_nem_wide = Constraint(m.all_tech, rule=con_max_mwh_nem_wide) # Slack constraint on exogenous retirement to prevent it to go nevative m.con_slackretire = Constraint(m.retire_gen_tech_in_zones, rule=con_slackretire) # Slack constraint on exogenous retirement to prevent it to go nevative m.con_slackbuild = Constraint(m.gen_tech_in_zones, rule=con_slackbuild) # linearised unit commitment constraints m.con_min_load_commit = Constraint(m.commit_gen_tech_in_zones, m.t, rule=con_min_load_commit) m.con_disp_ramp_down = Constraint(m.commit_gen_tech_in_zones, m.t, rule=con_disp_ramp_down) m.con_disp_ramp_up = Constraint(m.commit_gen_tech_in_zones, m.t, rule=con_disp_ramp_up) m.con_ramp_down_uptime = Constraint(m.commit_gen_tech_in_zones, m.t, rule=con_ramp_down_uptime) m.con_uptime_commitment = Constraint(m.commit_gen_tech_in_zones, m.t, rule=con_uptime_commitment) m.con_committed_cap = Constraint(m.commit_gen_tech_in_zones, m.t, rule=con_committed_cap) # NEM operating reserve constraint m.con_operating_reserve = Constraint(m.regions, m.t, rule=con_operating_reserve) # Hard constraint on unserved energy if unslim: m.con_uns = Constraint(m.regions, rule=con_uns) # Emmissions constraint if emitlimit: m.con_emissions = Constraint(rule=con_emissions) # maximum kg/MWh rate of total emissions m.nem_year_emit_limit = Param() # NEM wide RET constraint as a ratio if nem_ret_ratio: # NEM wide renewable energy target for current year m.nem_ret_ratio = Param(default=0) # NEM wide renewable energy constraint m.con_nem_ret_ratio = Constraint(rule=con_nem_ret_ratio) # NEM wide RET constraint as a ratio if nem_ret_gwh: # NEM wide renewable energy target for current year m.nem_ret_gwh = Param(default=0) # NEM wide renewable energy constraint m.con_nem_ret_gwh = Constraint(rule=con_nem_ret_gwh) if region_ret_ratio: # Regional RET targets for current year m.region_ret_ratio = Param(m.regions, default=0) # Regional RET constraint m.con_region_ret = Constraint(m.regions, rule=con_region_ret_ratio) if nem_re_disp_ratio: # NEM wide minimum hour by our generation from "dispatchable" sources m.nem_re_disp_ratio = Param(default=0) # NEM wide minimum hourly dispatch from dispatchable sources constraint m.con_nem_re_disp_ratio = Constraint(m.regions, m.t, rule=con_nem_re_disp_ratio) # Storage charge/discharge dynamic m.StCharDis = Constraint(m.stor_tech_in_zones, m.t, rule=con_storcharge) # Maxiumum rate of storage charge m.con_stor_flow_lim = Constraint(m.stor_tech_in_zones, m.t, rule=con_stor_flow_lim) # Maxiumum rate of storage discharge m.con_stor_reserve_lim = Constraint(m.stor_tech_in_zones, m.t, rule=con_stor_reserve_lim) # Maxiumum charge capacity of storage m.MaxCharge = Constraint(m.stor_tech_in_zones, m.t, rule=con_maxcharge) # StCap in existing period is previous stor_cap_op plus stor_cap_new m.con_stor_cap = Constraint(m.stor_tech_in_zones, rule=con_stor_cap) # Hybrid charge/discharge dynamic m.HybCharDis = Constraint(m.hyb_tech_in_zones, m.t, rule=con_hybcharge) # Maxiumum level of hybrid storage discharge m.con_hyb_level_max = Constraint(m.hyb_tech_in_zones, m.t, rule=con_hyb_level_max) # Maxiumum rate of hybrid storage charge/discharge m.con_hyb_flow_lim = Constraint(m.hyb_tech_in_zones, m.t, rule=con_hyb_flow_lim) # Limit hybrid reserve capacity to be within storage level m.con_hyb_reserve_lim = Constraint(m.hyb_tech_in_zones, m.t, rule=con_hyb_reserve_lim) # Maxiumum charge capacity of storage m.MaxChargehy = Constraint(m.hyb_tech_in_zones, m.t, rule=con_maxchargehy) # HyCap in existing period is previous stor_cap_op plus stor_cap_new m.con_hyb_cap = Constraint(m.hyb_tech_in_zones, rule=con_hyb_cap) # @@ Objective # Minimise capital, variable and fixed costs of system m.FSCost = Expression(expr=0) m.SSCost = Expression(rule=obj_cost) # objective: minimise all other objectives m.Obj = Objective(expr=m.FSCost + m.SSCost) # Short run marginal prices m.dual = Suffix(direction=Suffix.IMPORT) return m
def create_sets(self): # Sets # Set of NEM regions self.m.regions = Set(initialize=cemo.const.REGION.keys()) self.m.zones = Set( initialize=cemo.const.ZONE.keys()) # Set of NTNDP planning zones self.m.all_tech = Set( initialize=cemo.const.ALL_TECH) # Set of generation technologies # set of fuel based gen technologies self.m.fuel_gen_tech = Set( initialize=cemo.const.FUEL_TECH) & self.m.all_tech # set of gen techs that obey linearised unit commitment constraints self.m.commit_gen_tech = Set( initialize=cemo.const.COMMIT_TECH) & self.m.all_tech # set of retireable technologies self.m.retire_gen_tech = Set( initialize=cemo.const.RETIRE_TECH) & self.m.all_tech # set of retireable technologies self.m.nobuild_gen_tech = Set( initialize=cemo.const.NOBUILD_TECH) & self.m.all_tech # Set of storage technologies self.m.stor_tech = Set( initialize=cemo.const.STOR_TECH) & self.m.all_tech # set of hybrid (gen+storage) technologies self.m.hyb_tech = Set(initialize=cemo.const.HYB_TECH) & self.m.all_tech self.m.ev_tech = Set(initialize=cemo.const.EV_TECH) & self.m.all_tech # Set of dispatch intervals self.m.t = Set(ordered=True) # Sparse set of zones per region self.m.zones_in_regions = Set( dimen=2, initialize=init_zones_in_regions) # Set listing technologies avaialable per zone (like a sparsity pattern) self.m.gen_tech_in_zones = Set(dimen=2) # Retirable technologies avaialable per zone (like a sparsity pattern) self.m.retire_gen_tech_in_zones = Set(dimen=2) # Fuel/emmitting technologies avaialable per zone (like a sparsity pattern) self.m.fuel_gen_tech_in_zones = Set(dimen=2) # Fuel/emmitting technologies avaialable per zone (like a sparsity pattern) self.m.commit_gen_tech_in_zones = Set(dimen=2) # Renewable technologies avaialable per zone (like a sparsity pattern) self.m.re_gen_tech_in_zones = Set(dimen=2) # Dispatchable technologies avaialable per zone (like a sparsity pattern) self.m.disp_gen_tech_in_zones = Set(dimen=2) # Renewable Dispatchable technologies avaialable per zone (like a sparsity pattern) self.m.re_disp_gen_tech_in_zones = Set(dimen=2) # Set listing storage avaialable per zone (like a sparsity pattern) self.m.hyb_tech_in_zones = Set(dimen=2) # Set listing ev avaialable per zone (like a sparsity pattern) #KP_ADDED self.m.ev_tech_in_zones = Set(dimen=2) # Set listing storage avaialable per zone (like a sparsity pattern) self.m.stor_tech_in_zones = Set(dimen=2) # Set listing transmission lines to other regions in each region self.m.intercons_in_zones = Set( dimen=2, initialize=init_intercons_in_zones) # sparse sets built by build actions # Returns a list of planning zones for each region in R self.m.zones_per_region = Set( self.m.regions, within=self.m.zones, initialize=[]) # Returns a tuple with generating techs in each zone self.m.gen_tech_per_zone = Set( self.m.zones, within=self.m.all_tech, initialize=[]) # Returns a tuple with emitting techs in each zone self.m.fuel_gen_tech_per_zone = Set( self.m.zones, within=self.m.all_tech, initialize=[]) # tuple for techs that obey linearised unit commitment constraints self.m.commit_gen_tech_per_zone = Set( self.m.zones, within=self.m.all_tech, initialize=[]) self.m.re_gen_tech_per_zone = Set( self.m.zones, within=self.m.all_tech, initialize=[]) self.m.disp_gen_tech_per_zone = Set( self.m.zones, within=self.m.all_tech, initialize=[]) self.m.re_disp_gen_tech_per_zone = Set( self.m.zones, within=self.m.all_tech, initialize=[]) # Returns a tuple with retirable techs in each zone self.m.retire_gen_tech_per_zone = Set( self.m.zones, within=self.m.all_tech, initialize=[]) # Returns a tuple with storage techs in each zone self.m.stor_tech_per_zone = Set( self.m.zones, within=self.m.stor_tech, initialize=[]) # Returns a tuple with emitting techs in each zone self.m.hyb_tech_per_zone = Set( self.m.zones, within=self.m.all_tech, initialize=[]) # Returns a tuple with emitting techs in each zone self.m.ev_tech_per_zone = Set( self.m.zones, within=self.m.all_tech, initialize=[]) # returns a tuple with transmission links in each region self.m.intercon_per_zone = Set(self.m.zones, initialize=[]) self.m.smart_charge_tech = Set(initialize=cemo.const.SMART_CHARGE_EV_TECH) & self.m.all_tech # set of ev tech WITHOUT V2G enabled self.m.v2g_tech = Set(initialize=cemo.const.V2G_EV_TECH) & self.m.all_tech # @@ Build actions # Scan TechinZones and populate ?_gen_tech_per_zone self.m.TpZ_build = BuildAction(rule=ScanForTechperZone) # Scan HybTechinZones and populate hyb_tech_per_zone self.m.HpZ_build = BuildAction(rule=ScanForHybridperZone) # Scan EVTechinZones and populate ev_tech_per_zone self.m.EpZ_build = BuildAction(rule=ScanForEVperZone) # Scan ZinR and populate ZperR self.m.ZpR_build = BuildAction(rule=ScanForZoneperRegion) # Scan TransLines and populate intercon_per_zone self.m.intercon_build = BuildAction(rule=build_intercon_per_zone) # Scan StorinZones and populate stor_tech_per_zone self.m.SpZ_build = BuildAction(rule=ScanForStorageperZone)
def create_params(self): # @@ Parameters # Capital costs generators # Build costs for generators self.m.cost_gen_build = Param( self.m.gen_tech_in_zones, initialize=init_default_capex) self.m.cost_stor_build = Param( self.m.stor_tech_in_zones) # Capital costs storage self.m.cost_hyb_build = Param( self.m.hyb_tech_in_zones) # Capital costs hybrid self.m.cost_ev_build = Param( self.m.ev_tech_in_zones) #, initialize=init_default_capex) # Capital costs $/MW/km trans self.m.cost_intercon_build = Param( self.m.intercons_in_zones, initialize=init_intercon_build_cost) self.m.cost_fuel = Param( self.m.fuel_gen_tech_in_zones, initialize=init_default_fuel_price) # Fuel cost # Fixed operating costs generators self.m.cost_gen_fom = Param(self.m.all_tech) # Variable operating costs generators self.m.cost_gen_vom = Param(self.m.all_tech) # Fixed operating costs storage self.m.cost_stor_fom = Param(self.m.stor_tech) # Variable operating costs storage self.m.cost_stor_vom = Param(self.m.stor_tech) # Fixed operating costs hybrid self.m.cost_hyb_fom = Param(self.m.hyb_tech) # Variable operating costs hybrid self.m.cost_hyb_vom = Param(self.m.hyb_tech) # Fixed operating costs ev self.m.cost_ev_fom = Param(self.m.ev_tech) # Variable operating costs ev self.m.cost_ev_vom = Param(self.m.ev_tech) # Technology lifetime in years self.m.all_tech_lifetime = Param( self.m.all_tech, initialize=init_default_lifetime) # Project discount rate self.m.all_tech_discount_rate = Param(default=0.05) # % Fleet V2G EV Discharging enabled self.m.percent_v2g_enabled = Param(default=0) # % Fleet Smart EV Charging enabled self.m.percent_smart_enabled = Param(default=0.5) # EV Fleet Batt Level Floor self.m.ev_level_floor = Param(default=0.2) # Generator tech fixed charge rate self.m.fixed_charge_rate = Param(self.m.all_tech, initialize=init_fcr) # Transmission tech fixed charge rate self.m.intercon_fixed_charge_rate = Param(initialize=init_intercon_fcr) # Per year cost adjustment for sims shorter than 1 year of dispatch self.m.year_correction_factor = Param( initialize=init_year_correction_factor) self.m.cost_retire = Param( self.m.retire_gen_tech, initialize=init_cost_retire) self.m.cost_unserved = Param( initialize=cemo.const. DEFAULT_COSTS["unserved"]) # cost of unserved power # cost in $/kg of total emissions self.m.cost_emit = Param(initialize=cemo.const.DEFAULT_COSTS["emit"]) self.m.cost_trans = Param( initialize=cemo.const.DEFAULT_COSTS["trans"]) # cost of transmission # Round trip efficiency of storage technology self.m.stor_rt_eff = Param( self.m.stor_tech, initialize=init_stor_rt_eff) # Number of hours of storage technology self.m.stor_charge_hours = Param( self.m.stor_tech, initialize=init_stor_charge_hours) # Round trip efficiency of ev technology self.m.ev_rt_eff = Param(self.m.ev_tech, initialize=init_ev_rt_eff) self.m.ev_connected = Param(self.m.ev_tech_in_zones, self.m.t, default=0) #KP_MODIFIED_180820_2 from ev_tech_in_zones #KP_MODIFIED_030920 changed back to ev_tech_in_zones from ev_tech # Max charge rate & v2g discharge rate self.m.ev_max_charge_rate = Param(self.m.ev_tech, initialize=init_ev_charge_rate) # Battery size per vehicle self.m.ev_batt_size = Param(self.m.ev_tech, initialize=init_ev_batt_size) self.m.ev_trans_trace = Param(self.m.ev_tech_in_zones, self.m.t, default=0) # Trnasport energy trace read in #KP_MODIFIED_180820_2 from self.m.ev_tech_in_zones #KP_MODIFIED_030920 changed back to ev_tech_in_zones from ev_tech self.m.ev_charge_dumb_trace = Param(self.m.ev_tech_in_zones, self.m.t, default=0) # Collector multiple of hybrid technology self.m.hyb_col_mult = Param( self.m.hyb_tech, initialize=init_hyb_col_mult) # Number of hours of storage technology self.m.hyb_charge_hours = Param( self.m.hyb_tech, initialize=init_hyb_charge_hours) self.m.fuel_heat_rate = Param( self.m.fuel_gen_tech_in_zones, initialize=init_default_heat_rate) # Emission rates self.m.fuel_emit_rate = Param( self.m.fuel_gen_tech, initialize=init_default_fuel_emit_rate) # proportioning factors for notional interconnectors self.m.intercon_loss_factor = Param( self.m.intercons_in_zones, initialize=init_intercon_loss_factor) self.m.gen_cap_factor = Param( self.m.gen_tech_in_zones, self.m.t, initialize=init_cap_factor, mutable=True) # Capacity factors for generators self.m.hyb_cap_factor = Param( self.m.hyb_tech_in_zones, self.m.t, initialize=init_cap_factor, mutable=True) # Capacity factors for generators self.m.ev_cap_factor = Param( self.m.ev_tech_in_zones, self.m.t, initialize=init_cap_factor, mutable=True) # Revise cap factors for numbers below threshold of 1e-5 self.m.build_cap_factor_thres = BuildAction(rule=build_cap_factor_thres) # Maximum capacity per generating technology per zone self.m.gen_build_limit = Param( self.m.gen_tech_in_zones, initialize=init_gen_build_limit) self.m.gen_cap_initial = Param( self.m.gen_tech_in_zones, default=0, mutable=True) # operating capacity self.m.stor_cap_initial = Param( self.m.stor_tech_in_zones, default=0) # operating capacity self.m.hyb_cap_initial = Param( self.m.hyb_tech_in_zones, default=0) # operating capacity self.m.ev_cap_initial = Param( self.m.ev_tech_in_zones, default=0) self.m.intercon_cap_initial = Param( self.m.intercons_in_zones, initialize=init_intercon_cap_initial) # operating capacity # exogenous new capacity self.m.gen_cap_exo = Param(self.m.gen_tech_in_zones, default=0, mutable=True) # exogenous new storage capacity self.m.stor_cap_exo = Param(self.m.stor_tech_in_zones, default=0) # exogenous new hybrid capacity self.m.hyb_cap_exo = Param(self.m.hyb_tech_in_zones, default=0) self.m.ev_cap_exo = Param(self.m.ev_tech_in_zones, default=0) # exogenous transmission capacity self.m.intercon_cap_exo = Param(self.m.intercons_in_zones, default=0) self.m.ret_gen_cap_exo = Param( self.m.retire_gen_tech_in_zones, default=0, mutable=True) # Net Electrical load (may include rooftop and EV) self.m.region_net_demand = Param(self.m.regions, self.m.t) # Zone load distribution factors as a pct of region demand self.m.zone_demand_factor = Param( self.m.zones, self.m.t, initialize=init_zone_demand_factors) # carry forward capital costs calculated self.m.cost_cap_carry_forward_sim = Param(self.m.zones, default=0) # carry forward capital costs NEM historical estimate self.m.cost_cap_carry_forward_hist = Param(self.m.zones, default=0) # carry forward capital costs total self.m.cost_cap_carry_forward = Param(self.m.zones, mutable=True) # Build action to Compute carry forward costs self.m.cost_carry_forward_build = BuildAction( rule=build_carry_fwd_cost_per_zone) # Create params for model options for option in self.model_options._fields: if getattr(self.model_options, option): if cemo.const.DEFAULT_MODEL_OPT.get(option, {}).get("index", None) is None: setattr(self.m, option, Param(default=cemo.const.DEFAULT_MODEL_OPT.get(option, {}).get('value', 0)) ) else: setattr(self.m, option, Param(eval(cemo.const.DEFAULT_MODEL_OPT[option].get("index", None)), default=cemo.const.DEFAULT_MODEL_OPT.get(option, {}).get('value', 0)) ) # Build action to prevent exogenous buids to exceed build limits self.m.build_adjust_exo_cap = BuildAction(rule=build_adjust_exo_cap) # Build action to prevent exogenous retires to make capacity negative self.m.build_adjust_exo_ret = BuildAction(rule=build_adjust_exo_ret)
def _create(self, group=None): if group is None: return None m = self.parent_block() # for all DSM components get inflow from bus_elec for n in group: n.inflow = list(n.inputs)[0] # ************* SETS ********************************* # Set of DSM Components self.DSM = Set(initialize=[n for n in group]) # ************* VARIABLES ***************************** def dsm_capacity_bound_rule(block): """Rule definition for bounds(capacity) of DSM - Variable g in timestep t""" for t in m.TIMESTEPS: for g in group: bounds = (-g.c_do[t], g.c_up[t]) return bounds # Variable load shift down (MWh) self.DSMupdown = Var(self.DSM, m.TIMESTEPS, initialize=0, within=Reals, bounds=dsm_capacity_bound_rule) # ************* CONSTRAINTS ***************************** # Demand Production Relation def _input_output_relation_rule(block): """ Relation between input data and pyomo variables. The actual demand after DSM. Generator Production == Demand_el +- DSM """ for t in m.TIMESTEPS: for g in group: # Generator loads directly from bus lhs = m.flow[g.inflow, g, t] # Demand +- DSM rhs = g.demand[t] + self.DSMupdown[g, t] # add constraint block.input_output_relation.add((g, t), (lhs == rhs)) self.input_output_relation = Constraint(group, m.TIMESTEPS, noruleinit=True) self.input_output_relation_build = BuildAction( rule=_input_output_relation_rule) # Equation 7 def dsm_sum_constraint_rule(block): """ Relation to compensate the total amount of positive and negative DSM in between the shift_interval. 2 Cases: A full interval is optimised or an incomplete one. """ for t in m.TIMESTEPS: for g in group: shft_intvl = g.shift_interval # full interval if (t // shft_intvl) < (m.TIMESTEPS._bounds[1] // shft_intvl): # DSM up/down lhs = sum( self.DSMupdown[g, tt] for tt in range((t // shft_intvl) * shft_intvl, (t // shft_intvl + 1) * shft_intvl, 1)) # value rhs = 0 # add constraint block.dsm_sum_constraint.add((g, t), (lhs == rhs)) # incomplete interval else: # DSM up/down lhs = sum( self.DSMupdown[g, tt] for tt in range((t // shft_intvl) * shft_intvl, m.TIMESTEPS._bounds[1] + 1, 1)) # value rhs = 0 # add constraint block.dsm_sum_constraint.add((g, t), (lhs == rhs)) self.dsm_sum_constraint = Constraint(group, m.TIMESTEPS, noruleinit=True) self.dsm_sum_constraint_build = BuildAction( rule=dsm_sum_constraint_rule)