def node_parasitics(model): """ Additional variables and constraints for plants with internal parasitics. Defines variables: * ec_prod: storage -> carrier after parasitics (+ production) * ec_con: storage <- carrier after parasitics (- consumption) """ m = model.m # Variables m.ec_prod = po.Var(m.c, m.y_p, m.x, m.t, within=po.NonNegativeReals) m.ec_con = po.Var(m.c, m.y_p, m.x, m.t, within=po.NegativeReals) # Constraint rules def c_ec_prod_rule(m, c, y, x, t): return (m.ec_prod[c, y, x, t] == m.es_prod[c, y, x, t] * model.get_option(y + '.constraints.c_eff', x=x)) def c_ec_con_rule(m, c, y, x, t): if y in m.y_trans or y in m.y_conv: # Ensure that transmission and conversion technologies # do not double count c_eff c_eff = 1.0 else: c_eff = model.get_option(y + '.constraints.c_eff', x=x) return (m.ec_con[c, y, x, t] == m.es_con[c, y, x, t] / c_eff) # Constraints m.c_ec_prod = po.Constraint(m.c, m.y_p, m.x, m.t, rule=c_ec_prod_rule) m.c_ec_con = po.Constraint(m.c, m.y_p, m.x, m.t, rule=c_ec_con_rule)
def load_constraints(backend_model): sets = backend_model.__calliope_model_data__['sets'] if 'loc_carriers_update_system_balance_constraint' in sets: for loc_carrier, timestep in ( backend_model.loc_carriers_update_system_balance_constraint * backend_model.timesteps): update_system_balance_constraint(backend_model, loc_carrier, timestep) if 'loc_tech_carriers_export_balance_constraint' in sets: backend_model.export_balance_constraint = po.Constraint( backend_model.loc_tech_carriers_export_balance_constraint, backend_model.timesteps, rule=export_balance_constraint_rule ) if 'loc_techs_update_costs_var_constraint' in sets: for cost, loc_tech, timestep in (backend_model.costs * backend_model.loc_techs_update_costs_var_constraint * backend_model.timesteps): update_costs_var_constraint(backend_model, cost, loc_tech, timestep) if 'loc_tech_carriers_export_max_constraint' in sets: backend_model.export_max_constraint = po.Constraint( backend_model.loc_tech_carriers_export_max_constraint, backend_model.timesteps, rule=export_max_constraint_rule )
def model_constraints(model): m = model.m @utils.memoize def get_parents(level): return list(model._locations[model._locations._level == level].index) @utils.memoize def get_children(parent, childless_only=True): """ If childless_only is True, only children that have no children themselves are returned. """ locations = model._locations children = list(locations[locations._within == parent].index) if childless_only: # FIXME childless_only param needs tests children = [i for i in children if len(get_children(i)) == 0] return children # Constraint rules def c_system_balance_rule(m, c, x, t): # Balacing takes place at top-most (level 0) locations, as well # as within any lower-level locations that contain children if (model._locations.at[x, '_level'] == 0 or len(get_children(x)) > 0): family = get_children(x) + [x] # list of children + parent balance = (sum(m.c_prod[c, y, xs, t] for xs in family for y in m.y) + sum(m.c_con[c, y, xs, t] for xs in family for y in m.y) - sum(m.export[y, xs, t] for xs in family for y in m.y_export if xs in m.x_export and c == model.get_option(y + '.export', x=xs))) return balance == 0 else: return po.Constraint.NoConstraint # Constraints m.c_system_balance = po.Constraint(m.c, m.x, m.t, rule=c_system_balance_rule) def c_export_balance_rule(m, c, y, x, t): # Ensuring no technology can 'pass' its export capability to another # technology with the same carrier_out, # by limiting its export to the capacity of its production if c == model.get_option(y + '.export', x=x): return m.c_prod[c, y, x, t] >= m.export[y, x, t] else: return po.Constraint.Skip # Constraints m.c_export_balance = po.Constraint(m.c, m.y_export, m.x_export, m.t, rule=c_export_balance_rule)
def load_constraints(backend_model): sets = backend_model.__calliope_model_data__['sets'] if 'loc_techs_storage_capacity_constraint' in sets: backend_model.storage_capacity_constraint = po.Constraint( backend_model.loc_techs_storage_capacity_constraint, rule=storage_capacity_constraint_rule ) if 'loc_techs_energy_capacity_storage_constraint' in sets: backend_model.energy_capacity_storage_constraint = po.Constraint( backend_model.loc_techs_energy_capacity_storage_constraint, rule=energy_capacity_storage_constraint_rule ) if 'loc_techs_resource_capacity_constraint' in sets: backend_model.resource_capacity_constraint = po.Constraint( backend_model.loc_techs_resource_capacity_constraint, rule=resource_capacity_constraint_rule ) if 'loc_techs_resource_capacity_equals_energy_capacity_constraint' in sets: backend_model.resource_capacity_equals_energy_capacity_constraint = po.Constraint( backend_model.loc_techs_resource_capacity_equals_energy_capacity_constraint, rule=resource_capacity_equals_energy_capacity_constraint_rule ) if 'loc_techs_resource_area_constraint' in sets: backend_model.resource_area_constraint = po.Constraint( backend_model.loc_techs_resource_area_constraint, rule=resource_area_constraint_rule ) if 'loc_techs_resource_area_per_energy_capacity_constraint' in sets: backend_model.resource_area_per_energy_capacity_constraint = po.Constraint( backend_model.loc_techs_resource_area_per_energy_capacity_constraint, rule=resource_area_per_energy_capacity_constraint_rule ) if 'locs_resource_area_capacity_per_loc_constraint' in sets: backend_model.resource_area_capacity_per_loc_constraint = po.Constraint( backend_model.locs_resource_area_capacity_per_loc_constraint, rule=resource_area_capacity_per_loc_constraint_rule ) if 'loc_techs_energy_capacity_constraint' in sets: backend_model.energy_capacity_constraint = po.Constraint( backend_model.loc_techs_energy_capacity_constraint, rule=energy_capacity_constraint_rule ) if 'techs_energy_capacity_systemwide_constraint' in sets: backend_model.energy_capacity_systemwide_constraint = po.Constraint( backend_model.techs_energy_capacity_systemwide_constraint, rule=energy_capacity_systemwide_constraint_rule )
def load_constraints(backend_model): sets = backend_model.__calliope_model_data__['sets'] backend_model.balance_conversion_constraint = po.Constraint( backend_model.loc_techs_balance_conversion_constraint, backend_model.timesteps, rule=balance_conversion_constraint_rule) if 'loc_techs_cost_var_conversion_constraint' in sets: backend_model.cost_var_conversion_constraint = po.Constraint( backend_model.costs, backend_model.loc_techs_cost_var_conversion_constraint, backend_model.timesteps, rule=cost_var_conversion_constraint_rule)
def ramping_rate(model): """ Ramping rate constraints. Depends on: node_energy_balance, node_constraints_build """ m = model.m time_res = model.data['_time_res'].to_series() # Constraint rules def _ramping_rule(m, loc_tech, t, direction): x, y = loc_tech.split(":", 1) # e_ramping: Ramping rate [fraction of installed capacity per hour] ramping_rate_value = model.get_option(y + '.constraints.e_ramping') if ramping_rate_value is False: # If the technology defines no `e_ramping`, we don't build a # ramping constraint for it! return po.Constraint.NoConstraint else: # No constraint for first timestep # NB: From Pyomo 3.5 to 3.6, order_dict became zero-indexed if m.t.order_dict[t] == 0: return po.Constraint.NoConstraint else: carrier = model.get_option(y + '.carrier', default=y + '.carrier_out') if isinstance(carrier, dict): # conversion_plus technology carrier = model.get_carrier(y, 'out', primary=True) diff = ((m.c_prod[carrier, loc_tech, t] + m.c_con[carrier, loc_tech, t]) / time_res.at[t] - (m.c_prod[carrier, loc_tech, model.prev_t(t)] + m.c_con[carrier, loc_tech, model.prev_t(t)]) / time_res.at[model.prev_t(t)]) max_ramping_rate = ramping_rate_value * m.e_cap[loc_tech] if direction == 'up': return diff <= max_ramping_rate else: return -1 * max_ramping_rate <= diff def c_ramping_up_rule(m, loc_tech, t): return _ramping_rule(m, loc_tech, t, direction='up') def c_ramping_down_rule(m, loc_tech, t): return _ramping_rule(m, loc_tech, t, direction='down') # Constraints m.c_ramping_up = po.Constraint(m.loc_tech, m.t, rule=c_ramping_up_rule) m.c_ramping_down = po.Constraint(m.loc_tech, m.t, rule=c_ramping_down_rule)
def system_margin(model): """ """ m = model.m time_res = model.data['_time_res'].to_series() def carrier(y): return model.get_option(y + '.carrier') # Constraint rules def c_system_margin_rule(m, c): # If no margin defined for a carrier, use 0 (i.e. no margin) margin = model.config_model.system_margin.get_key(c, default=0) if margin: t = model.t_max_demand[c] return (sum(m.es_prod[c, y, x, t] for y in m.y for x in m.x) * (1 + margin) <= time_res.at[t] * sum( (m.e_cap[y, x] / model.get_eff_ref('e', y, x)) for y in m.y if carrier(y) == c for x in m.x)) else: return po.Constraint.NoConstraint # Constraints m.c_system_margin = po.Constraint(m.c, rule=c_system_margin_rule)
def load_constraints(backend_model): sets = backend_model.__calliope_model_data__['sets'] if 'loc_techs_symmetric_transmission_constraint' in sets and backend_model.mode != 'operate': backend_model.symmetric_transmission_constraint = po.Constraint( backend_model.loc_techs_symmetric_transmission_constraint, rule=symmetric_transmission_constraint_rule)
def system_margin(model): """ """ m = model.m time_res = model.data['_time_res'].to_series() def carrier(y, x): if y in m.y_conversion_plus: return model.get_cp_carriers(y, x)[0] else: return model.get_option(y + '.carrier', default=y + '.carrier_out') # Constraint rules def c_system_margin_rule(m, c): # If no margin defined for a carrier, use 0 (i.e. no margin) margin = model.config_model.system_margin.get_key(c, default=0) if margin: t = model.t_max_demand[c] return (sum(m.c_prod[c, y, x, t] for y in m.y if y not in m.y_demand for x in m.x) * (1 + margin) <= time_res.at[t] * sum( (m.e_cap[y, x] / base.get_constraint_param(m, 'e_eff', y, x, t)) for y in m.y if y not in m.y_demand for x in m.x if carrier(y, x) == c)) else: return po.Constraint.NoConstraint # Constraints m.c_system_margin = po.Constraint(m.c, rule=c_system_margin_rule)
def add_hacks(model, hacks): """ add hackish features to model object This function is reserved for corner cases/features that still lack a satisfyingly general solution that could become part of create_model. Use hack features sparingly and think about how to incorporate into main model function before adding here. Otherwise, these features might become a maintenance burden. """ # Store hack data model.hacks = hacks # Global CO2 limit try: global_co2_limit = hacks.loc['Global CO2 limit', 'Value'] except KeyError: global_co2_limit = float('inf') # only add constraint if limit is finite if not math.isinf(global_co2_limit): model.res_global_co2_limit = pyomo.Constraint( rule=res_global_co2_limit_rule, doc='total co2 commodity output <= hacks.Glocal CO2 limit') return model
def node_constraints_build_total(model): """ """ m = model.m # Constraint rules def c_e_cap_total_systemwide_rule(m, y): total_max = model.get_option(y + '.constraints.e_cap.total_max') total_equals = model.get_option(y + '.constraints.e_cap.total_equals') scale = model.get_option(y + '.constraints.e_cap_scale') if np.isinf(total_max) and not total_equals: return po.Constraint.NoConstraint sum_expr = sum(m.e_cap[y, x] for x in m.x) total_expr = total_equals * scale if total_equals else total_max * scale if total_equals: return sum_expr == total_expr else: return sum_expr <= total_expr # Constraints m.c_e_cap_total_systemwide = \ po.Constraint(m.y, rule=c_e_cap_total_systemwide_rule)
def system_margin(model): """ """ m = model.m time_res = model.data['_time_res'].to_series() def carrier(loc_tech): x, y = get_y_x(loc_tech) return model.get_carrier(y, 'out', x=x, primary=True) def get_y_x(loc_tech): return loc_tech.split(":", 1) # Constraint rules def c_system_margin_rule(m, c): # If no margin defined for a carrier, use 0 (i.e. no margin) margin = model.config_model.system_margin.get_key(c, default=0) if margin: t = model.t_max_demand[c] return (sum(m.c_prod[c, loc_tech, t] for loc_tech in m.loc_tech if loc_tech not in m.loc_tech_demand) * (1 + margin) <= time_res.at[t] * sum( (m.e_cap[loc_tech] / base.get_constraint_param(model, 'e_eff', get_y_x(loc_tech)[1], get_y_x(loc_tech)[0], t)) for loc_tech in m.loc_tech if loc_tech not in m.loc_tech_demand and carrier(loc_tech) == c)) else: return po.Constraint.NoConstraint # Constraints m.c_system_margin = po.Constraint(m.c, rule=c_system_margin_rule)
def add_buy_sell_price(m): # Sets m.com_sell = pyomo.Set(within=m.com, initialize=commodity_subset(m.com_tuples, 'Sell'), doc='Commodities that can be sold') m.com_buy = pyomo.Set(within=m.com, initialize=commodity_subset(m.com_tuples, 'Buy'), doc='Commodities that can be purchased') # Variables m.e_co_sell = pyomo.Var( m.tm, m.com_tuples, within=pyomo.NonNegativeReals, doc='Use of sell commodity source (MW) per timestep') m.e_co_buy = pyomo.Var(m.tm, m.com_tuples, within=pyomo.NonNegativeReals, doc='Use of buy commodity source (MW) per timestep') # Rules m.res_sell_step = pyomo.Constraint( m.tm, m.com_tuples, rule=res_sell_step_rule, doc='sell commodity output per step <= commodity.maxperstep') m.res_sell_total = pyomo.Constraint( m.com_tuples, rule=res_sell_total_rule, doc='total sell commodity output <= commodity.max') m.res_buy_step = pyomo.Constraint( m.tm, m.com_tuples, rule=res_buy_step_rule, doc='buy commodity output per step <= commodity.maxperstep') m.res_buy_total = pyomo.Constraint( m.com_tuples, rule=res_buy_total_rule, doc='total buy commodity output <= commodity.max') m.res_sell_buy_symmetry = pyomo.Constraint( m.pro_input_tuples, rule=res_sell_buy_symmetry_rule, doc='power connection capacity must be symmetric in both directions') return m
def load_constraints(backend_model): sets = backend_model.__calliope_model_data['sets'] if 'loc_techs_balance_conversion_plus_primary_constraint' in sets: backend_model.balance_conversion_plus_primary_constraint = po.Constraint( backend_model.loc_techs_balance_conversion_plus_primary_constraint, backend_model.timesteps, rule=balance_conversion_plus_primary_constraint_rule) if 'loc_techs_carrier_production_max_conversion_plus_constraint' in sets: backend_model.carrier_production_max_conversion_plus_constraint = po.Constraint( backend_model. loc_techs_carrier_production_max_conversion_plus_constraint, backend_model.timesteps, rule=carrier_production_max_conversion_plus_constraint_rule) if 'loc_techs_carrier_production_min_conversion_plus_constraint' in sets: backend_model.carrier_production_min_conversion_plus_constraint = po.Constraint( backend_model. loc_techs_carrier_production_min_conversion_plus_constraint, backend_model.timesteps, rule=carrier_production_min_conversion_plus_constraint_rule) if 'loc_techs_cost_var_conversion_plus_constraint' in sets: backend_model.cost_var_conversion_plus_constraint = po.Constraint( backend_model.costs, backend_model.loc_techs_cost_var_conversion_plus_constraint, backend_model.timesteps, rule=cost_var_conversion_plus_constraint_rule) if 'loc_techs_balance_conversion_plus_in_2_constraint' in sets: backend_model.balance_conversion_plus_in_2_constraint = po.Constraint( ['in_2'], backend_model.loc_techs_balance_conversion_plus_in_2_constraint, backend_model.timesteps, rule=balance_conversion_plus_tiers_constraint_rule) if 'loc_techs_balance_conversion_plus_in_3_constraint' in sets: backend_model.balance_conversion_plus_in_3_constraint = po.Constraint( ['in_3'], backend_model.loc_techs_balance_conversion_plus_in_3_constraint, backend_model.timesteps, rule=balance_conversion_plus_tiers_constraint_rule) if 'loc_techs_balance_conversion_plus_out_2_constraint' in sets: backend_model.balance_conversion_plus_out_2_constraint = po.Constraint( ['out_2'], backend_model.loc_techs_balance_conversion_plus_out_2_constraint, backend_model.timesteps, rule=balance_conversion_plus_tiers_constraint_rule) if 'loc_techs_balance_conversion_plus_out_3_constraint' in sets: backend_model.balance_conversion_plus_out_3_constraint = po.Constraint( ['out_3'], backend_model.loc_techs_balance_conversion_plus_out_3_constraint, backend_model.timesteps, rule=balance_conversion_plus_tiers_constraint_rule)
def load_constraints(backend_model): sets = backend_model.__calliope_model_data['sets'] run_config = backend_model.__calliope_run_config if 'loc_techs_symmetric_transmission_constraint' in sets and run_config['mode'] != 'operate': backend_model.symmetric_transmission_constraint = po.Constraint( backend_model.loc_techs_symmetric_transmission_constraint, rule=symmetric_transmission_constraint_rule )
def node_resource(model): m = model.m # TODO reformulate c_r_rule conditionally once Pyomo supports that. # had to remove the following formulation because it is not # re-evaluated on model re-construction -- we now check for # demand/supply tech instead, which means that `r` can only # be ALL negative or ALL positive for a given tech! # Ideally we have `elif po.value(m.r[y, x, t]) > 0:` instead of # `elif y in m.y_supply or y in m.y_unmet_demand:` and `elif y in m.y_demand:` def r_available_rule(m, y, x, t): r_scale = model.get_option(y + '.constraints.r_scale', x=x) force_r = get_constraint_param(model, 'force_r', y, x, t) if y in m.y_sd: e_eff = get_constraint_param(model, 'e_eff', y, x, t) if po.value(e_eff) == 0: c_prod = 0 else: c_prod = sum(m.c_prod[c, y, x, t] for c in m.c) / e_eff # supply techs c_con = sum(m.c_con[c, y, x, t] for c in m.c) * e_eff # demand techs if y in m.y_sd_r_area: r_avail = (get_constraint_param(model, 'r', y, x, t) * r_scale * m.r_area[y, x]) else: r_avail = (get_constraint_param(model, 'r', y, x, t) * r_scale) if force_r: return c_prod + c_con == r_avail else: return c_prod - c_con <= r_avail elif y in m.y_supply_plus: r_eff = get_constraint_param(model, 'r_eff', y, x, t) if y in m.y_sp_r_area: r_avail = (get_constraint_param(model, 'r', y, x, t) * r_scale * m.r_area[y, x] * r_eff) else: r_avail = get_constraint_param(model, 'r', y, x, t) * r_scale * r_eff if force_r: return m.r[y, x, t] == r_avail else: return m.r[y, x, t] <= r_avail # Constraints m.c_r_available = po.Constraint(m.y_finite_r, m.x_r, m.t, rule=r_available_rule)
def load_constraints(backend_model): sets = backend_model.__calliope_model_data["sets"] run_config = backend_model.__calliope_run_config if "loc_techs_cost_constraint" in sets: backend_model.cost_constraint = po.Constraint( backend_model.costs, backend_model.loc_techs_cost, rule=cost_constraint_rule) # FIXME: remove check for operate from constraint files, avoid investment costs more intelligently? if ("loc_techs_cost_investment_constraint" in sets and run_config["mode"] != "operate"): # Right-hand side expression can be later updated by MILP investment costs backend_model.cost_investment_rhs = po.Expression( backend_model.costs, backend_model.loc_techs_cost_investment_constraint, initialize=0.0, ) backend_model.cost_investment_constraint = po.Constraint( backend_model.costs, backend_model.loc_techs_cost_investment_constraint, rule=cost_investment_constraint_rule, ) if "loc_techs_om_cost" in sets: # Right-hand side expression can be later updated by export costs/revenue backend_model.cost_var_rhs = po.Expression( backend_model.costs, backend_model.loc_techs_om_cost, backend_model.timesteps, initialize=0.0, ) if "loc_techs_cost_var_constraint" in sets: # Constraint is built over a different loc_techs set to expression, as # it is updated in conversion.py and conversion_plus.py constraints backend_model.cost_var_constraint = po.Constraint( backend_model.costs, backend_model.loc_techs_cost_var_constraint, backend_model.timesteps, rule=cost_var_constraint_rule, )
def load_constraints(backend_model): sets = backend_model.__calliope_model_data["sets"] run_config = backend_model.__calliope_run_config if ("loc_techs_symmetric_transmission_constraint" in sets and run_config["mode"] != "operate"): backend_model.symmetric_transmission_constraint = po.Constraint( backend_model.loc_techs_symmetric_transmission_constraint, rule=symmetric_transmission_constraint_rule, )
def load_constraints(backend_model): sets = backend_model.__calliope_model_data__['sets'] if 'loc_carriers_system_balance_constraint' in sets: backend_model.system_balance = po.Expression( backend_model.loc_carriers_system_balance_constraint, backend_model.timesteps, initialize=0.0 ) backend_model.system_balance_constraint = po.Constraint( backend_model.loc_carriers_system_balance_constraint, backend_model.timesteps, rule=system_balance_constraint_rule ) if 'loc_techs_balance_supply_constraint' in sets: backend_model.balance_supply_constraint = po.Constraint( backend_model.loc_techs_balance_supply_constraint, backend_model.timesteps, rule=balance_supply_constraint_rule ) if 'loc_techs_balance_demand_constraint' in sets: backend_model.balance_demand_constraint = po.Constraint( backend_model.loc_techs_balance_demand_constraint, backend_model.timesteps, rule=balance_demand_constraint_rule ) if 'loc_techs_balance_transmission_constraint' in sets: backend_model.balance_transmission_constraint = po.Constraint( backend_model.loc_techs_balance_transmission_constraint, backend_model.timesteps, rule=balance_transmission_constraint_rule ) if 'loc_techs_resource_availability_supply_plus_constraint' in sets: backend_model.balance_supply_plus_constraint = po.Constraint( backend_model.loc_techs_resource_availability_supply_plus_constraint, backend_model.timesteps, rule=balance_supply_plus_constraint_rule ) if 'loc_techs_balance_supply_plus_constraint' in sets: backend_model.resource_availability_supply_plus_constraint = po.Constraint( backend_model.loc_techs_balance_supply_plus_constraint, backend_model.timesteps, rule=resource_availability_supply_plus_constraint_rule ) if 'loc_techs_balance_storage_constraint' in sets: backend_model.balance_storage_constraint = po.Constraint( backend_model.loc_techs_balance_storage_constraint, backend_model.timesteps, rule=balance_storage_constraint_rule )
def max_r_area_per_loc(model): """ ``r_area`` of all technologies requiring physical space cannot exceed the available area of a location. Available area defined for parent locations (in which there are locations defined as being 'within' it) will set the available area limit for the sum of all the family (parent + all desecendants). To define, assign a value to ``available_area`` for a given location, e.g.:: locations: r1: techs: ['csp'] available_area: 100000 To avoid including descendants in area limitation, ``ignore_descendants`` can be specified for the location, in the same way as ``available_area``. """ m = model.m locations = model._locations def _get_descendants(x): """ Returns all locations in the family tree of location x, in one list. """ descendants = [] children = list(locations[locations._within == x].index) if not children: return [] for child in children: descendants += _get_descendants(child) descendants += children return descendants def c_available_r_area_rule(m, x): available_area = model.config_model.get_key( 'locations.{}.available_area'.format(x), default=None) if not available_area: return po.Constraint.Skip if model.config_model.get_key( 'locations.{}.ignore_descendants'.format(x), default=False): descendants = [] else: descendants = _get_descendants(x) all_x = descendants + [x] r_area_sum = sum( m.r_area[y, _x] for y in m.y_r_area for _x in all_x if model.get_option(y + '.constraints.r_area.max', x=_x) is not False) # r_area_sum will be ``0`` if either ``m.y_r_area`` or ``all_x`` are empty if not isinstance(r_area_sum, int): return (available_area >= r_area_sum) m.c_available_r_area = po.Constraint(m.x, rule=c_available_r_area_rule)
def load_constraints(backend_model): sets = backend_model.__calliope_model_data["sets"] run_config = backend_model.__calliope_run_config if "techlists_group_share_energy_cap_min_constraint" in sets: backend_model.group_share_energy_cap_min_constraint = po.Constraint( backend_model.techlists_group_share_energy_cap_min_constraint, ["min"], rule=group_share_energy_cap_constraint_rule, ) if "techlists_group_share_energy_cap_max_constraint" in sets: backend_model.group_share_energy_cap_max_constraint = po.Constraint( backend_model.techlists_group_share_energy_cap_max_constraint, ["max"], rule=group_share_energy_cap_constraint_rule, ) if "techlists_group_share_energy_cap_equals_constraint" in sets: backend_model.group_share_energy_cap_equals_constraint = po.Constraint( backend_model.techlists_group_share_energy_cap_equals_constraint, ["equals"], rule=group_share_energy_cap_constraint_rule, ) if "techlists_carrier_group_share_carrier_prod_min_constraint" in sets: backend_model.group_share_carrier_prod_min_constraint = po.Constraint( backend_model. techlists_carrier_group_share_carrier_prod_min_constraint, ["min"], rule=group_share_carrier_prod_constraint_rule, ) if "techlists_carrier_group_share_carrier_prod_max_constraint" in sets: backend_model.group_share_carrier_prod_max_constraint = po.Constraint( backend_model. techlists_carrier_group_share_carrier_prod_max_constraint, ["max"], rule=group_share_carrier_prod_constraint_rule, ) if "techlists_carrier_group_share_carrier_prod_equals_constraint" in sets: backend_model.group_share_carrier_prod_equals_constraint = po.Constraint( backend_model. techlists_carrier_group_share_carrier_prod_equals_constraint, ["equals"], rule=group_share_carrier_prod_constraint_rule, ) if "carriers_reserve_margin_constraint" in sets and run_config[ "mode"] != "operate": backend_model.reserve_margin_constraint = po.Constraint( backend_model.carriers_reserve_margin_constraint, rule=reserve_margin_constraint_rule, )
def build_constraints(backend_model, model_data, constraint_definitions): for constraint_name, constraint_config in constraint_definitions.items(): subset = create_valid_subset(model_data, constraint_name, constraint_config) if subset is None: continue setattr( backend_model, f"{constraint_name}_constraint", po.Constraint( subset, rule=_load_rule_function(f"{constraint_name}_constraint_rule"), ), )
def model_constraints(model): m = model.m @utils.memoize def get_parents(level): return list(model._locations[model._locations._level == level].index) @utils.memoize def get_children(parent, childless_only=True): """ If childless_only is True, only children that have no children themselves are returned. """ locations = model._locations children = list(locations[locations._within == parent].index) if childless_only: # FIXME childless_only param needs tests children = [i for i in children if len(get_children(i)) == 0] return children # Constraint rules def c_system_balance_rule(m, c, x, t): # Balacing takes place at top-most (level 0) locations, as well # as within any lower-level locations that contain children if (model._locations.at[x, '_level'] == 0 or len(get_children(x)) > 0): family = get_children(x) + [x] # list of children + parent balance = (sum(m.es_prod[c, y, xs, t] for xs in family for y in m.y_np) + sum(m.ec_prod[c, y, xs, t] for xs in family for y in m.y_p) + sum(m.es_con[c, y, xs, t] for xs in family for y in m.y_np) + sum(m.ec_con[c, y, xs, t] for xs in family for y in m.y_p)) if c == 'power': return balance == 0 else: # e.g. for heat return balance >= 0 else: return po.Constraint.NoConstraint # Constraints m.c_system_balance = po.Constraint(m.c, m.x, m.t, rule=c_system_balance_rule)
def node_constraints_transmission(model): """ Constrain e_cap symmetrically for transmission nodes. Transmission techs only. """ m = model.m # Constraint rules def c_trans_rule(m, y, x): y_remote, x_remote = transmission.get_remotes(y, x) if y_remote in m.y_transmission: return m.e_cap[y, x] == m.e_cap[y_remote, x_remote] else: return po.Constraint.NoConstraint # Constraints m.c_transmission_capacity = po.Constraint(m.y_transmission, m.x_transmission, rule=c_trans_rule)
def load_constraints(backend_model): sets = backend_model.__calliope_model_data__['sets'] if 'loc_tech_carriers_carrier_production_max_constraint' in sets: backend_model.carrier_production_max_constraint = po.Constraint( backend_model.loc_tech_carriers_carrier_production_max_constraint, backend_model.timesteps, rule=carrier_production_max_constraint_rule ) if 'loc_tech_carriers_carrier_production_min_constraint' in sets: backend_model.carrier_production_min_constraint = po.Constraint( backend_model.loc_tech_carriers_carrier_production_min_constraint, backend_model.timesteps, rule=carrier_production_min_constraint_rule ) if 'loc_tech_carriers_carrier_consumption_max_constraint' in sets: backend_model.carrier_consumption_max_constraint = po.Constraint( backend_model.loc_tech_carriers_carrier_consumption_max_constraint, backend_model.timesteps, rule=carrier_consumption_max_constraint_rule ) if 'loc_techs_resource_max_constraint' in sets: backend_model.resource_max_constraint = po.Constraint( backend_model.loc_techs_resource_max_constraint, backend_model.timesteps, rule=resource_max_constraint_rule ) if 'loc_techs_storage_max_constraint' in sets: backend_model.storage_max_constraint = po.Constraint( backend_model.loc_techs_storage_max_constraint, backend_model.timesteps, rule=storage_max_constraint_rule ) if 'loc_tech_carriers_ramping_constraint' in sets: backend_model.ramping_up_constraint = po.Constraint( backend_model.loc_tech_carriers_ramping_constraint, backend_model.timesteps, rule=ramping_up_constraint_rule ) backend_model.ramping_down_constraint = po.Constraint( backend_model.loc_tech_carriers_ramping_constraint, backend_model.timesteps, rule=ramping_down_constraint_rule )
def load_constraints(backend_model): sets = backend_model.__calliope_model_data['sets'] run_config = backend_model.__calliope_run_config if 'techlists_group_share_energy_cap_min_constraint' in sets: backend_model.group_share_energy_cap_min_constraint = po.Constraint( backend_model.techlists_group_share_energy_cap_min_constraint, ['min'], rule=group_share_energy_cap_constraint_rule) if 'techlists_group_share_energy_cap_max_constraint' in sets: backend_model.group_share_energy_cap_max_constraint = po.Constraint( backend_model.techlists_group_share_energy_cap_max_constraint, ['max'], rule=group_share_energy_cap_constraint_rule) if 'techlists_group_share_energy_cap_equals_constraint' in sets: backend_model.group_share_energy_cap_equals_constraint = po.Constraint( backend_model.techlists_group_share_energy_cap_equals_constraint, ['equals'], rule=group_share_energy_cap_constraint_rule) if 'techlists_carrier_group_share_carrier_prod_min_constraint' in sets: backend_model.group_share_carrier_prod_min_constraint = po.Constraint( backend_model. techlists_carrier_group_share_carrier_prod_min_constraint, ['min'], rule=group_share_carrier_prod_constraint_rule) if 'techlists_carrier_group_share_carrier_prod_max_constraint' in sets: backend_model.group_share_carrier_prod_max_constraint = po.Constraint( backend_model. techlists_carrier_group_share_carrier_prod_max_constraint, ['max'], rule=group_share_carrier_prod_constraint_rule) if 'techlists_carrier_group_share_carrier_prod_equals_constraint' in sets: backend_model.group_share_carrier_prod_equals_constraint = po.Constraint( backend_model. techlists_carrier_group_share_carrier_prod_equals_constraint, ['equals'], rule=group_share_carrier_prod_constraint_rule) if 'carriers_reserve_margin_constraint' in sets and run_config[ 'mode'] != 'operate': backend_model.reserve_margin_constraint = po.Constraint( backend_model.carriers_reserve_margin_constraint, rule=reserve_margin_constraint_rule)
def elec_export_rule(m, t): if m.flex_type[t] == 1: def solar_power_max(m, t): return m.solar_act[t] <= m.solar[t] m.solar_power_ma = pyen.Constraint(m.t, rule=solar_power_max) return m.elec_export[t] == m.grid_export[t] - m.flex_value[t] elif m.flex_type[t] == 2: return m.elec_export[t] == value(m.grid_export[t] + m.flex_value[t]) # elif m.flex_type[t] == 3: # if value(m.grid_export[t]) > 0 and value(m.grid_export[t]) >= value(m.flex_value[t]): # return m.elec_export[t] == abs(value(m.grid_export[t] - m.flex_value[t])) # else: # return m.elec_export[t] == 0 else: return m.elec_export[t] <= 50 * 5000
def node_resource(model): """ Defines variables: * rs: resource <-> storage (+ production, - consumption) * r_area: resource collector area * rbs: secondary resource -> storage (+ production) """ m = model.m # Variables m.rs = po.Var(m.y, m.x, m.t, within=po.Reals) m.r_area = po.Var(m.y_def_r, m.x, within=po.NonNegativeReals) m.rbs = po.Var(m.y_rb, m.x, m.t, within=po.NonNegativeReals) # Constraint rules def c_rs_rule(m, y, x, t): r_avail = (m.r[y, x, t] * model.get_option(y + '.constraints.r_scale', x=x) * m.r_area[y, x] * model.get_option(y + '.constraints.r_eff', x=x)) if model.get_option(y + '.constraints.force_r', x=x): return m.rs[y, x, t] == r_avail # TODO reformulate conditionally once Pyomo supports that: # had to remove the following formulation because it is not # re-evaluated on model re-construction -- we now check for # demand/supply tech instead, which means that `r` can only # be ALL negative or ALL positive for a given tech! # elif po.value(m.r[y, x, t]) > 0: elif (y in model.get_group_members('supply') or y in model.get_group_members('unmet_demand')): return m.rs[y, x, t] <= r_avail elif y in model.get_group_members('demand'): return m.rs[y, x, t] >= r_avail # Constraints m.c_rs = po.Constraint(m.y_def_r, m.x, m.t, rule=c_rs_rule)
def load_constraints(backend_model): model_data_dict = backend_model.__calliope_model_data["data"] for sense in ["min", "max", "equals"]: if "group_energy_cap_share_{}".format(sense) in model_data_dict: setattr( backend_model, "group_energy_cap_share_{}_constraint".format(sense), po.Constraint( getattr( backend_model, "group_names_energy_cap_share_{}".format(sense) ), [sense], rule=energy_cap_share_constraint_rule, ), ) if "group_energy_cap_{}".format(sense) in model_data_dict: setattr( backend_model, "group_energy_cap_{}_constraint".format(sense), po.Constraint( getattr(backend_model, "group_names_energy_cap_{}".format(sense)), [sense], rule=energy_cap_constraint_rule, ), ) if "group_storage_cap_{}".format(sense) in model_data_dict: setattr( backend_model, "group_storage_cap_{}_constraint".format(sense), po.Constraint( getattr(backend_model, "group_names_storage_cap_{}".format(sense)), [sense], rule=storage_cap_constraint_rule, ), ) if "group_resource_area_{}".format(sense) in model_data_dict: setattr( backend_model, "group_resource_area_{}_constraint".format(sense), po.Constraint( getattr( backend_model, "group_names_resource_area_{}".format(sense) ), [sense], rule=resource_area_constraint_rule, ), ) if "group_carrier_prod_{}".format(sense) in model_data_dict: setattr( backend_model, "group_carrier_prod_{}_constraint".format(sense), po.Constraint( getattr(backend_model, "group_names_carrier_prod_{}".format(sense)), backend_model.carriers, [sense], rule=carrier_prod_constraint_rule, ), ) if "group_demand_share_{}".format(sense) in model_data_dict: setattr( backend_model, "group_demand_share_{}_constraint".format(sense), po.Constraint( getattr(backend_model, "group_names_demand_share_{}".format(sense)), backend_model.carriers, [sense], rule=demand_share_constraint_rule, ), ) if "group_demand_share_per_timestep_{}".format(sense) in model_data_dict: setattr( backend_model, "group_demand_share_per_timestep_{}_constraint".format(sense), po.Constraint( getattr( backend_model, "group_names_demand_share_per_timestep_{}".format(sense), ), backend_model.carriers, backend_model.timesteps, [sense], rule=demand_share_per_timestep_constraint_rule, ), ) if "group_carrier_prod_share_{}".format(sense) in model_data_dict: setattr( backend_model, "group_carrier_prod_share_{}_constraint".format(sense), po.Constraint( getattr( backend_model, "group_names_carrier_prod_share_{}".format(sense) ), backend_model.carriers, [sense], rule=carrier_prod_share_constraint_rule, ), ) if "group_carrier_prod_share_per_timestep_{}".format(sense) in model_data_dict: setattr( backend_model, "group_carrier_prod_share_per_timestep_{}_constraint".format(sense), po.Constraint( getattr( backend_model, "group_names_carrier_prod_share_per_timestep_{}".format(sense), ), backend_model.carriers, backend_model.timesteps, [sense], rule=carrier_prod_share_per_timestep_constraint_rule, ), ) if "group_net_import_share_{}".format(sense) in model_data_dict: setattr( backend_model, "group_net_import_share_{}_constraint".format(sense), po.Constraint( getattr( backend_model, "group_names_net_import_share_{}".format(sense) ), backend_model.carriers, [sense], rule=net_import_share_constraint_rule, ), ) if "group_cost_{}".format(sense) in model_data_dict: setattr( backend_model, "group_cost_{}_constraint".format(sense), po.Constraint( getattr(backend_model, "group_names_cost_{}".format(sense)), backend_model.costs, [sense], rule=cost_cap_constraint_rule, ), ) if "group_cost_var_{}".format(sense) in model_data_dict: setattr( backend_model, "group_cost_var_{}_constraint".format(sense), po.Constraint( getattr(backend_model, "group_names_cost_var_{}".format(sense)), backend_model.costs, [sense], rule=cost_var_cap_constraint_rule, ), ) if "group_cost_investment_{}".format(sense) in model_data_dict: setattr( backend_model, "group_cost_investment_{}_constraint".format(sense), po.Constraint( getattr( backend_model, "group_names_cost_investment_{}".format(sense) ), backend_model.costs, [sense], rule=cost_investment_cap_constraint_rule, ), ) if "group_demand_share_per_timestep_decision" in model_data_dict: backend_model.group_demand_share_per_timestep_decision_main_constraint = po.Constraint( backend_model.group_names_demand_share_per_timestep_decision, backend_model.carriers, backend_model.techs, backend_model.timesteps, rule=demand_share_per_timestep_decision_main_constraint_rule, ) backend_model.group_demand_share_per_timestep_decision_sum_constraint = po.Constraint( backend_model.group_names_demand_share_per_timestep_decision, backend_model.carriers, rule=demand_share_per_timestep_decision_sum_constraint_rule, )
def create_model(data, dt=1, timesteps=None, dual=False): """Create a pyomo ConcreteModel urbs object from given input data. Args: data: a dict of 6 DataFrames with the keys 'commodity', 'process', 'transmission', 'storage', 'demand' and 'supim'. dt: timestep duration in hours (default: 1) timesteps: optional list of timesteps, default: demand timeseries dual: set True to add dual variables to model (slower); default: False Returns: a pyomo ConcreteModel object """ # Optional if not timesteps: timesteps = data['demand'].index.tolist() m = pyomo_model_prep(data, timesteps) # preparing pyomo model m.name = 'urbs' m.created = datetime.now().strftime('%Y%m%dT%H%M') m._data = data # Parameters # weight = length of year (hours) / length of simulation (hours) # weight scales costs and emissions from length of simulation to a full # year, making comparisons among cost types (invest is annualized, fixed # costs are annual by default, variable costs are scaled by weight) and # among different simulation durations meaningful. m.weight = pyomo.Param( initialize=float(8760) / (len(m.timesteps) * dt), doc='Pre-factor for variable costs and emissions for an annual result') # dt = spacing between timesteps. Required for storage equation that # converts between energy (storage content, e_sto_con) and power (all other # quantities that start with "e_") m.dt = pyomo.Param(initialize=dt, doc='Time step duration (in hours), default: 1') # Sets # ==== # Syntax: m.{name} = Set({domain}, initialize={values}) # where name: set name # domain: set domain for tuple sets, a cartesian set product # values: set values, a list or array of element tuples # generate ordered time step sets m.t = pyomo.Set(initialize=m.timesteps, ordered=True, doc='Set of timesteps') # modelled (i.e. excluding init time step for storage) time steps m.tm = pyomo.Set(within=m.t, initialize=m.timesteps[1:], ordered=True, doc='Set of modelled timesteps') # modelled Demand Side Management time steps (downshift): # downshift effective in tt to compensate for upshift in t m.tt = pyomo.Set(within=m.t, initialize=m.timesteps[1:], ordered=True, doc='Set of additional DSM time steps') # site (e.g. north, middle, south...) m.sit = pyomo.Set( initialize=m.commodity.index.get_level_values('Site').unique(), doc='Set of sites') # commodity (e.g. solar, wind, coal...) m.com = pyomo.Set( initialize=m.commodity.index.get_level_values('Commodity').unique(), doc='Set of commodities') # commodity type (i.e. SupIm, Demand, Stock, Env) m.com_type = pyomo.Set( initialize=m.commodity.index.get_level_values('Type').unique(), doc='Set of commodity types') # process (e.g. Wind turbine, Gas plant, Photovoltaics...) m.pro = pyomo.Set( initialize=m.process.index.get_level_values('Process').unique(), doc='Set of conversion processes') # tranmission (e.g. hvac, hvdc, pipeline...) m.tra = pyomo.Set(initialize=m.transmission.index.get_level_values( 'Transmission').unique(), doc='Set of transmission technologies') # storage (e.g. hydrogen, pump storage) m.sto = pyomo.Set( initialize=m.storage.index.get_level_values('Storage').unique(), doc='Set of storage technologies') # cost_type m.cost_type = pyomo.Set(initialize=[ 'Invest', 'Fixed', 'Variable', 'Fuel', 'Revenue', 'Purchase', 'Environmental' ], doc='Set of cost types (hard-coded)') # tuple sets m.com_tuples = pyomo.Set( within=m.sit * m.com * m.com_type, initialize=m.commodity.index, doc='Combinations of defined commodities, e.g. (Mid,Elec,Demand)') m.pro_tuples = pyomo.Set( within=m.sit * m.pro, initialize=m.process.index, doc='Combinations of possible processes, e.g. (North,Coal plant)') m.tra_tuples = pyomo.Set( within=m.sit * m.sit * m.tra * m.com, initialize=m.transmission.index, doc='Combinations of possible transmissions, e.g. ' '(South,Mid,hvac,Elec)') m.sto_tuples = pyomo.Set( within=m.sit * m.sto * m.com, initialize=m.storage.index, doc='Combinations of possible storage by site, e.g. (Mid,Bat,Elec)') m.dsm_site_tuples = pyomo.Set( within=m.sit * m.com, initialize=m.dsm.index, doc='Combinations of possible dsm by site, e.g. (Mid, Elec)') m.dsm_down_tuples = pyomo.Set( within=m.tm * m.tm * m.sit * m.com, initialize=[(t, tt, site, commodity) for (t, tt, site, commodity) in dsm_down_time_tuples( m.timesteps[1:], m.dsm_site_tuples, m)], doc='Combinations of possible dsm_down combinations, e.g. ' '(5001,5003,Mid,Elec)') # commodity type subsets m.com_supim = pyomo.Set( within=m.com, initialize=commodity_subset(m.com_tuples, 'SupIm'), doc='Commodities that have intermittent (timeseries) input') m.com_stock = pyomo.Set( within=m.com, initialize=commodity_subset(m.com_tuples, 'Stock'), doc='Commodities that can be purchased at some site(s)') m.com_sell = pyomo.Set(within=m.com, initialize=commodity_subset(m.com_tuples, 'Sell'), doc='Commodities that can be sold') m.com_buy = pyomo.Set(within=m.com, initialize=commodity_subset(m.com_tuples, 'Buy'), doc='Commodities that can be purchased') m.com_demand = pyomo.Set( within=m.com, initialize=commodity_subset(m.com_tuples, 'Demand'), doc='Commodities that have a demand (implies timeseries)') m.com_env = pyomo.Set( within=m.com, initialize=commodity_subset(m.com_tuples, 'Env'), doc='Commodities that (might) have a maximum creation limit') # process tuples for area rule m.pro_area_tuples = pyomo.Set( within=m.sit * m.pro, initialize=m.proc_area.index, doc='Processes and Sites with area Restriction') # process input/output m.pro_input_tuples = pyomo.Set( within=m.sit * m.pro * m.com, initialize=[(site, process, commodity) for (site, process) in m.pro_tuples for (pro, commodity) in m.r_in.index if process == pro], doc='Commodities consumed by process by site, e.g. (Mid,PV,Solar)') m.pro_output_tuples = pyomo.Set( within=m.sit * m.pro * m.com, initialize=[(site, process, commodity) for (site, process) in m.pro_tuples for (pro, commodity) in m.r_out.index if process == pro], doc='Commodities produced by process by site, e.g. (Mid,PV,Elec)') # process tuples for maximum gradient feature m.pro_maxgrad_tuples = pyomo.Set( within=m.sit * m.pro, initialize=[(sit, pro) for (sit, pro) in m.pro_tuples if m.process.loc[sit, pro]['max-grad'] < 1.0 / dt], doc='Processes with maximum gradient smaller than timestep length') # process tuples for partial feature m.pro_partial_tuples = pyomo.Set(within=m.sit * m.pro, initialize=[ (site, process) for (site, process) in m.pro_tuples for (pro, _) in m.r_in_min_fraction.index if process == pro ], doc='Processes with partial input') m.pro_partial_input_tuples = pyomo.Set( within=m.sit * m.pro * m.com, initialize=[(site, process, commodity) for (site, process) in m.pro_partial_tuples for (pro, commodity) in m.r_in_min_fraction.index if process == pro], doc='Commodities with partial input ratio, e.g. (Mid,Coal PP,Coal)') m.pro_partial_output_tuples = pyomo.Set( within=m.sit * m.pro * m.com, initialize=[(site, process, commodity) for (site, process) in m.pro_partial_tuples for (pro, commodity) in m.r_out_min_fraction.index if process == pro], doc='Commodities with partial input ratio, e.g. (Mid,Coal PP,CO2)') # process tuples for time variable efficiency m.pro_timevar_output_tuples = pyomo.Set( within=m.sit * m.pro * m.com, initialize=[(site, process, commodity) for (site, process) in m.eff_factor.columns.values for (pro, commodity) in m.r_out.index if process == pro], doc='Outputs of processes with time dependent efficiency') # Variables # costs m.costs = pyomo.Var(m.cost_type, within=pyomo.Reals, doc='Costs by type (EUR/a)') # commodity m.e_co_stock = pyomo.Var( m.tm, m.com_tuples, within=pyomo.NonNegativeReals, doc='Use of stock commodity source (MW) per timestep') m.e_co_sell = pyomo.Var( m.tm, m.com_tuples, within=pyomo.NonNegativeReals, doc='Use of sell commodity source (MW) per timestep') m.e_co_buy = pyomo.Var(m.tm, m.com_tuples, within=pyomo.NonNegativeReals, doc='Use of buy commodity source (MW) per timestep') # process m.cap_pro = pyomo.Var(m.pro_tuples, within=pyomo.NonNegativeReals, doc='Total process capacity (MW)') m.cap_pro_new = pyomo.Var(m.pro_tuples, within=pyomo.NonNegativeReals, doc='New process capacity (MW)') m.tau_pro = pyomo.Var(m.t, m.pro_tuples, within=pyomo.NonNegativeReals, doc='Power flow (MW) through process') m.e_pro_in = pyomo.Var( m.tm, m.pro_tuples, m.com, within=pyomo.NonNegativeReals, doc='Power flow of commodity into process (MW) per timestep') m.e_pro_out = pyomo.Var(m.tm, m.pro_tuples, m.com, within=pyomo.NonNegativeReals, doc='Power flow out of process (MW) per timestep') # transmission m.cap_tra = pyomo.Var(m.tra_tuples, within=pyomo.NonNegativeReals, doc='Total transmission capacity (MW)') m.cap_tra_new = pyomo.Var(m.tra_tuples, within=pyomo.NonNegativeReals, doc='New transmission capacity (MW)') m.e_tra_in = pyomo.Var( m.tm, m.tra_tuples, within=pyomo.NonNegativeReals, doc='Power flow into transmission line (MW) per timestep') m.e_tra_out = pyomo.Var( m.tm, m.tra_tuples, within=pyomo.NonNegativeReals, doc='Power flow out of transmission line (MW) per timestep') # storage m.cap_sto_c = pyomo.Var(m.sto_tuples, within=pyomo.NonNegativeReals, doc='Total storage size (MWh)') m.cap_sto_c_new = pyomo.Var(m.sto_tuples, within=pyomo.NonNegativeReals, doc='New storage size (MWh)') m.cap_sto_p = pyomo.Var(m.sto_tuples, within=pyomo.NonNegativeReals, doc='Total storage power (MW)') m.cap_sto_p_new = pyomo.Var(m.sto_tuples, within=pyomo.NonNegativeReals, doc='New storage power (MW)') m.e_sto_in = pyomo.Var(m.tm, m.sto_tuples, within=pyomo.NonNegativeReals, doc='Power flow into storage (MW) per timestep') m.e_sto_out = pyomo.Var(m.tm, m.sto_tuples, within=pyomo.NonNegativeReals, doc='Power flow out of storage (MW) per timestep') m.e_sto_con = pyomo.Var(m.t, m.sto_tuples, within=pyomo.NonNegativeReals, doc='Energy content of storage (MWh) in timestep') # demand side management m.dsm_up = pyomo.Var(m.tm, m.dsm_site_tuples, within=pyomo.NonNegativeReals, doc='DSM upshift') m.dsm_down = pyomo.Var(m.dsm_down_tuples, within=pyomo.NonNegativeReals, doc='DSM downshift') # Equation declarations # equation bodies are defined in separate functions, referred to here by # their name in the "rule" keyword. # commodity m.res_vertex = pyomo.Constraint( m.tm, m.com_tuples, rule=res_vertex_rule, doc='storage + transmission + process + source + buy - sell == demand') m.res_stock_step = pyomo.Constraint( m.tm, m.com_tuples, rule=res_stock_step_rule, doc='stock commodity input per step <= commodity.maxperstep') m.res_stock_total = pyomo.Constraint( m.com_tuples, rule=res_stock_total_rule, doc='total stock commodity input <= commodity.max') m.res_sell_step = pyomo.Constraint( m.tm, m.com_tuples, rule=res_sell_step_rule, doc='sell commodity output per step <= commodity.maxperstep') m.res_sell_total = pyomo.Constraint( m.com_tuples, rule=res_sell_total_rule, doc='total sell commodity output <= commodity.max') m.res_buy_step = pyomo.Constraint( m.tm, m.com_tuples, rule=res_buy_step_rule, doc='buy commodity output per step <= commodity.maxperstep') m.res_buy_total = pyomo.Constraint( m.com_tuples, rule=res_buy_total_rule, doc='total buy commodity output <= commodity.max') m.res_env_step = pyomo.Constraint( m.tm, m.com_tuples, rule=res_env_step_rule, doc='environmental output per step <= commodity.maxperstep') m.res_env_total = pyomo.Constraint( m.com_tuples, rule=res_env_total_rule, doc='total environmental commodity output <= commodity.max') # process m.def_process_capacity = pyomo.Constraint( m.pro_tuples, rule=def_process_capacity_rule, doc='total process capacity = inst-cap + new capacity') m.def_process_input = pyomo.Constraint( m.tm, m.pro_input_tuples - m.pro_partial_input_tuples, rule=def_process_input_rule, doc='process input = process throughput * input ratio') m.def_process_output = pyomo.Constraint( m.tm, (m.pro_output_tuples - m.pro_partial_output_tuples - m.pro_timevar_output_tuples), rule=def_process_output_rule, doc='process output = process throughput * output ratio') m.def_intermittent_supply = pyomo.Constraint( m.tm, m.pro_input_tuples, rule=def_intermittent_supply_rule, doc='process output = process capacity * supim timeseries') m.res_process_throughput_by_capacity = pyomo.Constraint( m.tm, m.pro_tuples, rule=res_process_throughput_by_capacity_rule, doc='process throughput <= total process capacity') m.res_process_maxgrad_lower = pyomo.Constraint( m.tm, m.pro_maxgrad_tuples, rule=res_process_maxgrad_lower_rule, doc='throughput may not decrease faster than maximal gradient') m.res_process_maxgrad_upper = pyomo.Constraint( m.tm, m.pro_maxgrad_tuples, rule=res_process_maxgrad_upper_rule, doc='throughput may not increase faster than maximal gradient') m.res_process_capacity = pyomo.Constraint( m.pro_tuples, rule=res_process_capacity_rule, doc='process.cap-lo <= total process capacity <= process.cap-up') m.res_area = pyomo.Constraint( m.sit, rule=res_area_rule, doc='used process area <= total process area') m.res_sell_buy_symmetry = pyomo.Constraint( m.pro_input_tuples, rule=res_sell_buy_symmetry_rule, doc='power connection capacity must be symmetric in both directions') m.res_throughput_by_capacity_min = pyomo.Constraint( m.tm, m.pro_partial_tuples, rule=res_throughput_by_capacity_min_rule, doc='cap_pro * min-fraction <= tau_pro') m.def_partial_process_input = pyomo.Constraint( m.tm, m.pro_partial_input_tuples, rule=def_partial_process_input_rule, doc='e_pro_in = ' ' cap_pro * min_fraction * (r - R) / (1 - min_fraction)' ' + tau_pro * (R - min_fraction * r) / (1 - min_fraction)') m.def_partial_process_output = pyomo.Constraint( m.tm, (m.pro_partial_output_tuples - (m.pro_partial_output_tuples & m.pro_timevar_output_tuples)), rule=def_partial_process_output_rule, doc='e_pro_out = ' ' cap_pro * min_fraction * (r - R) / (1 - min_fraction)' ' + tau_pro * (R - min_fraction * r) / (1 - min_fraction)') m.def_process_timevar_output = pyomo.Constraint( m.tm, (m.pro_timevar_output_tuples - (m.pro_partial_output_tuples & m.pro_timevar_output_tuples)), rule=def_pro_timevar_output_rule, doc='e_pro_out = tau_pro * r_out * eff_factor') m.def_process_partial_timevar_output = pyomo.Constraint( m.tm, m.pro_partial_output_tuples & m.pro_timevar_output_tuples, rule=def_pro_partial_timevar_output_rule, doc='e_pro_out = tau_pro * r_out * eff_factor') # transmission m.def_transmission_capacity = pyomo.Constraint( m.tra_tuples, rule=def_transmission_capacity_rule, doc='total transmission capacity = inst-cap + new capacity') m.def_transmission_output = pyomo.Constraint( m.tm, m.tra_tuples, rule=def_transmission_output_rule, doc='transmission output = transmission input * efficiency') m.res_transmission_input_by_capacity = pyomo.Constraint( m.tm, m.tra_tuples, rule=res_transmission_input_by_capacity_rule, doc='transmission input <= total transmission capacity') m.res_transmission_capacity = pyomo.Constraint( m.tra_tuples, rule=res_transmission_capacity_rule, doc='transmission.cap-lo <= total transmission capacity <= ' 'transmission.cap-up') m.res_transmission_symmetry = pyomo.Constraint( m.tra_tuples, rule=res_transmission_symmetry_rule, doc='total transmission capacity must be symmetric in both directions') # storage m.def_storage_state = pyomo.Constraint( m.tm, m.sto_tuples, rule=def_storage_state_rule, doc='storage[t] = (1 - sd) * storage[t-1] + in * eff_i - out / eff_o') m.def_storage_power = pyomo.Constraint( m.sto_tuples, rule=def_storage_power_rule, doc='storage power = inst-cap + new power') m.def_storage_capacity = pyomo.Constraint( m.sto_tuples, rule=def_storage_capacity_rule, doc='storage capacity = inst-cap + new capacity') m.res_storage_input_by_power = pyomo.Constraint( m.tm, m.sto_tuples, rule=res_storage_input_by_power_rule, doc='storage input <= storage power') m.res_storage_output_by_power = pyomo.Constraint( m.tm, m.sto_tuples, rule=res_storage_output_by_power_rule, doc='storage output <= storage power') m.res_storage_state_by_capacity = pyomo.Constraint( m.t, m.sto_tuples, rule=res_storage_state_by_capacity_rule, doc='storage content <= storage capacity') m.res_storage_power = pyomo.Constraint( m.sto_tuples, rule=res_storage_power_rule, doc='storage.cap-lo-p <= storage power <= storage.cap-up-p') m.res_storage_capacity = pyomo.Constraint( m.sto_tuples, rule=res_storage_capacity_rule, doc='storage.cap-lo-c <= storage capacity <= storage.cap-up-c') m.res_initial_and_final_storage_state = pyomo.Constraint( m.t, m.sto_tuples, rule=res_initial_and_final_storage_state_rule, doc='storage content initial == and final >= storage.init * capacity') # costs m.def_costs = pyomo.Constraint(m.cost_type, rule=def_costs_rule, doc='main cost function by cost type') m.obj = pyomo.Objective(rule=obj_rule, sense=pyomo.minimize, doc='minimize(cost = sum of all cost types)') # demand side management m.def_dsm_variables = pyomo.Constraint( m.tm, m.dsm_site_tuples, rule=def_dsm_variables_rule, doc='DSMup * efficiency factor n == DSMdo (summed)') m.res_dsm_upward = pyomo.Constraint( m.tm, m.dsm_site_tuples, rule=res_dsm_upward_rule, doc='DSMup <= Cup (threshold capacity of DSMup)') m.res_dsm_downward = pyomo.Constraint( m.tm, m.dsm_site_tuples, rule=res_dsm_downward_rule, doc='DSMdo (summed) <= Cdo (threshold capacity of DSMdo)') m.res_dsm_maximum = pyomo.Constraint( m.tm, m.dsm_site_tuples, rule=res_dsm_maximum_rule, doc='DSMup + DSMdo (summed) <= max(Cup,Cdo)') m.res_dsm_recovery = pyomo.Constraint( m.tm, m.dsm_site_tuples, rule=res_dsm_recovery_rule, doc='DSMup(t, t + recovery time R) <= Cup * delay time L') m.res_global_co2_limit = pyomo.Constraint( rule=res_global_co2_limit_rule, doc='total co2 commodity output <= Global CO2 limit') if dual: m.dual = pyomo.Suffix(direction=pyomo.Suffix.IMPORT) return m