def carrier_production_min_conversion_plus_milp_constraint_rule( backend_model, loc_tech, timestep): """ Set minimum carrier production of conversion_plus MILP techs .. container:: scrolling-wrapper .. math:: \sum_{loc::tech::carrier \\in loc::tech::carriers_{out}} \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\geq energy_{cap, per unit}(loc::tech) \\times timestep\_resolution(timestep) \\times \\boldsymbol{operating\_units}(loc::tech, timestep) \\times energy_{cap, min use}(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{milp, conversion^{+}}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] timestep_resolution = backend_model.timestep_resolution[timestep] energy_cap = get_param(backend_model, 'energy_cap_per_unit', loc_tech) min_use = get_param(backend_model, 'energy_cap_min_use', (loc_tech, timestep)) loc_tech_carriers_out = (split_comma_list( model_data_dict['lookup_loc_techs_conversion_plus']['out', loc_tech])) carrier_prod = sum(backend_model.carrier_prod[loc_tech_carrier, timestep] for loc_tech_carrier in loc_tech_carriers_out) return carrier_prod >= (backend_model.operating_units[loc_tech, timestep] * timestep_resolution * energy_cap * min_use)
def storage_capacity_max_purchase_constraint_rule(backend_model, loc_tech): """ Set maximum storage capacity. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage_{cap}}(loc::tech) \\begin{cases} = storage_{cap, equals}(loc::tech) \\times \\boldsymbol{purchased},& \\text{if } storage_{cap, equals} \\\\ \\leq storage_{cap, max}(loc::tech) \\times \\boldsymbol{purchased},& \\text{if } storage_{cap, max}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall loc::tech \\in loc::techs_{purchase, store} """ storage_cap_max = get_param(backend_model, 'storage_cap_max', loc_tech) storage_cap_equals = get_param(backend_model, 'storage_cap_equals', loc_tech) if po.value(storage_cap_equals): return backend_model.storage_cap[loc_tech] == ( storage_cap_equals * backend_model.purchased[loc_tech]) elif po.value(storage_cap_max): return backend_model.storage_cap[loc_tech] <= ( storage_cap_max * backend_model.purchased[loc_tech]) else: return po.Constraint.Skip
def energy_capacity_systemwide_constraint_rule(backend_model, tech): """ Set constraints to limit the capacity of a single technology type across all locations in the model. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\sum_{loc}\\boldsymbol{energy_{cap}}(loc::tech) \\begin{cases} = energy_{cap, equals, systemwide}(loc::tech),& \\text{if } energy_{cap, equals, systemwide}(loc::tech)\\\\ \\leq energy_{cap, max, systemwide}(loc::tech),& \\text{if } energy_{cap, max, systemwide}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall tech \\in techs """ max_systemwide = get_param(backend_model, "energy_cap_max_systemwide", tech) equals_systemwide = get_param(backend_model, "energy_cap_equals_systemwide", tech) energy_cap = po.quicksum( backend_model.energy_cap[node, tech] for node in backend_model.nodes if [node, tech] in backend_model.energy_cap._index) if not invalid(equals_systemwide): return energy_cap == equals_systemwide else: return energy_cap <= max_systemwide
def carrier_production_max_milp_constraint_rule(backend_model, loc_tech_carrier, timestep): """ Set maximum carrier production of MILP techs that aren't conversion plus .. container:: scrolling-wrapper .. math:: \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\leq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep) \\times \\boldsymbol{operating\\_units}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep) \\quad \\forall loc::tech \\in loc::techs_{milp}, \\forall timestep \\in timesteps :math:`\\eta_{parasitic}` is only activated for `supply_plus` technologies """ loc_tech = get_loc_tech(loc_tech_carrier) carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep] timestep_resolution = backend_model.timestep_resolution[timestep] parasitic_eff = get_param(backend_model, 'parasitic_eff', (loc_tech, timestep)) energy_cap = get_param(backend_model, 'energy_cap_per_unit', loc_tech) return carrier_prod <= ( backend_model.operating_units[loc_tech, timestep] * timestep_resolution * energy_cap * parasitic_eff )
def get_capacity_constraint( backend_model, parameter, loc_tech, _equals=None, _max=None, _min=None, scale=None ): decision_variable = getattr(backend_model, parameter) if not _equals: _equals = get_param(backend_model, parameter + "_equals", loc_tech) if not _max: _max = get_param(backend_model, parameter + "_max", loc_tech) if not _min: _min = get_param(backend_model, parameter + "_min", loc_tech) if po.value(_equals) is not False and po.value(_equals) is not None: if np.isinf(po.value(_equals)): e = exceptions.ModelError raise e( "Cannot use inf for {}_equals for loc:tech `{}`".format( parameter, loc_tech ) ) if scale: _equals *= scale return decision_variable[loc_tech] == _equals else: if po.value(_min) == 0 and np.isinf(po.value(_max)): return po.Constraint.NoConstraint else: if scale: _max *= scale _min *= scale return (_min, decision_variable[loc_tech], _max)
def balance_storage_constraint_rule(backend_model, carrier, node, tech, timestep): """ Balance carrier production and consumption of storage technologies, alongside any use of the stored volume. .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{resolution(timestep)} - \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{storage}, \\forall timestep \\in timesteps """ run_config = backend_model.__calliope_run_config energy_eff = get_param(backend_model, "energy_eff", (node, tech, timestep)) if po.value(energy_eff) == 0: carrier_prod = 0 else: carrier_prod = ( backend_model.carrier_prod[carrier, node, tech, timestep] / energy_eff) carrier_con = backend_model.carrier_con[carrier, node, tech, timestep] * energy_eff # Pyomo returns the order 1-indexed, but we want 0-indexing current_timestep = backend_model.timesteps.ord(timestep) - 1 if current_timestep == 0 and not run_config["cyclic_storage"]: storage_previous_step = (get_param(backend_model, "storage_initial", (node, tech)) * backend_model.storage_cap[node, tech]) elif (hasattr(backend_model, "storage_inter_cluster") and backend_model.lookup_cluster_first_timestep[timestep]): storage_previous_step = 0 else: if (hasattr(backend_model, "clusters") and backend_model.lookup_cluster_first_timestep[timestep]): previous_step = backend_model.lookup_cluster_last_timestep[ timestep].value elif current_timestep == 0 and run_config["cyclic_storage"]: previous_step = backend_model.timesteps[-1] else: previous_step = get_previous_timestep(backend_model.timesteps, timestep) storage_loss = get_param(backend_model, "storage_loss", (node, tech)) time_resolution = backend_model.timestep_resolution[previous_step] storage_previous_step = ( (1 - storage_loss)** time_resolution) * backend_model.storage[node, tech, previous_step] return (backend_model.storage[node, tech, timestep] == storage_previous_step - carrier_prod - carrier_con)
def energy_capacity_max_purchase_milp_constraint_rule(backend_model, loc_tech): """ Set maximum energy capacity decision variable upper bound as a function of binary purchase variable The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)} \\begin{cases} = energy_{cap, equals}(loc::tech) \\times \\boldsymbol{purchased}(loc::tech),& \\text{if } energy_{cap, equals}(loc::tech)\\\\ \\leq energy_{cap, max}(loc::tech) \\times \\boldsymbol{purchased}(loc::tech),& \\text{if } energy_{cap, max}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall loc::tech \\in loc::techs_{purchase} """ energy_cap_max = get_param(backend_model, "energy_cap_max", loc_tech) energy_cap_equals = get_param(backend_model, "energy_cap_equals", loc_tech) energy_cap_scale = get_param(backend_model, "energy_cap_scale", loc_tech) if po.value(energy_cap_equals): return backend_model.energy_cap[loc_tech] == ( energy_cap_equals * energy_cap_scale * backend_model.purchased[loc_tech] ) else: return backend_model.energy_cap[loc_tech] <= ( energy_cap_max * energy_cap_scale * backend_model.purchased[loc_tech] )
def carrier_production_min_conversion_plus_milp_constraint_rule( backend_model, node, tech, timestep): """ Set minimum carrier production of conversion_plus MILP techs .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}} \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\geq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep) \\times \\boldsymbol{operating\\_units}(loc::tech, timestep) \\times energy_{cap, min use}(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{milp, conversion^{+}}, \\forall timestep \\in timesteps """ timestep_resolution = backend_model.timestep_resolution[timestep] energy_cap = get_param(backend_model, "energy_cap_per_unit", (node, tech)) min_use = get_param(backend_model, "energy_cap_min_use", (node, tech, timestep)) carriers_out = backend_model.carrier["out", :, tech].index() carrier_prod = po.quicksum(backend_model.carrier_prod[idx[1], node, tech, timestep] for idx in carriers_out) return carrier_prod >= (backend_model.operating_units[node, tech, timestep] * timestep_resolution * energy_cap * min_use)
def get_capacity_constraint(backend_model, parameter, loc_tech, _equals=None, _max=None, _min=None, scale=None): decision_variable = getattr(backend_model, parameter) if not _equals: _equals = get_param(backend_model, parameter + '_equals', loc_tech) if not _max: _max = get_param(backend_model, parameter + '_max', loc_tech) if not _min: _min = get_param(backend_model, parameter + '_min', loc_tech) if po.value(_equals) is not False and po.value(_equals) is not None: if np.isinf(po.value(_equals)): e = exceptions.ModelError raise e('Cannot use inf for {}_equals for loc:tech `{}`'.format(parameter, loc_tech)) if scale: _equals *= scale return decision_variable[loc_tech] == _equals else: if po.value(_min) == 0 and np.isinf(po.value(_max)): return po.Constraint.NoConstraint else: if scale: _max *= scale _min *= scale return (_min, decision_variable[loc_tech], _max)
def balance_transmission_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and consumption of transmission technologies .. container:: scrolling-wrapper .. math:: -1 * \\boldsymbol{carrier_{con}}(loc_{from}::tech:loc_{to}::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) = \\boldsymbol{carrier_{prod}}(loc_{to}::tech:loc_{from}::carrier, timestep) \\quad \\forall loc::tech:loc \in locs::techs:locs_{transmission}, \\forall timestep \in timesteps Where a link is the connection between :math:`loc_{from}::tech:loc_{to}` and :math:`loc_{to}::tech:loc_{from}` for locations `to` and `from`. """ model_data_dict = backend_model.__calliope_model_data__['data'] energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech] remote_loc_tech = model_data_dict['lookup_remotes'][loc_tech] remote_loc_tech_carrier = model_data_dict['lookup_loc_techs'][ remote_loc_tech] if (loc_tech_is_in(backend_model, remote_loc_tech, 'loc_techs_transmission') and get_param(backend_model, 'energy_prod', (loc_tech)) == 1): return (backend_model.carrier_prod[loc_tech_carrier, timestep] == -1 * backend_model.carrier_con[remote_loc_tech_carrier, timestep] * energy_eff) else: return po.Constraint.NoConstraint
def carrier_production_min_milp_constraint_rule( backend_model, loc_tech_carrier, timestep ): """ Set minimum carrier production of MILP techs that aren't conversion plus .. container:: scrolling-wrapper .. math:: \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\geq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep) \\times \\boldsymbol{operating\\_units}(loc::tech, timestep) \\times energy_{cap, min use}(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{milp}, \\forall timestep \\in timesteps """ loc_tech = get_loc_tech(loc_tech_carrier) carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep] timestep_resolution = backend_model.timestep_resolution[timestep] min_use = get_param(backend_model, "energy_cap_min_use", (loc_tech, timestep)) energy_cap = get_param(backend_model, "energy_cap_per_unit", loc_tech) return carrier_prod >= ( backend_model.operating_units[loc_tech, timestep] * timestep_resolution * energy_cap * min_use )
def get_capacity_constraint(backend_model, parameter, loc_tech, _equals=None, _max=None, _min=None, scale=None): decision_variable = getattr(backend_model, parameter) if not _equals: _equals = get_param(backend_model, parameter + '_equals', loc_tech) if not _max: _max = get_param(backend_model, parameter + '_max', loc_tech) if not _min: _min = get_param(backend_model, parameter + '_min', loc_tech) if scale: _equals = scale * _equals _min = scale * _min _max = scale * _max if po.value(_equals) is not False and po.value(_equals) is not None: if np.isinf(po.value(_equals)): e = exceptions.ModelError raise e('Cannot use inf for {}_equals for loc:tech `{}`'.format(parameter, loc_tech)) return decision_variable[loc_tech] == _equals else: if np.isinf(po.value(_max)): _max = None # to disable upper bound if po.value(_min) == 0 and po.value(_max) is None: return po.Constraint.NoConstraint else: return (_min, decision_variable[loc_tech], _max)
def energy_capacity_max_purchase_constraint_rule(backend_model, loc_tech): """ Set maximum energy capacity decision variable upper bound as a function of binary purchase variable The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)} \\begin{cases} = energy_{cap, equals}(loc::tech) \\times \\boldsymbol{purchased}(loc::tech),& \\text{if } energy_{cap, equals}(loc::tech)\\\\ \\leq energy_{cap, max}(loc::tech) \\times \\boldsymbol{purchased}(loc::tech),& \\text{if } energy_{cap, max}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall loc::tech \\in loc::techs_{purchase} """ energy_cap_max = get_param(backend_model, 'energy_cap_max', loc_tech) energy_cap_equals = get_param(backend_model, 'energy_cap_equals', loc_tech) energy_cap_scale = get_param(backend_model, 'energy_cap_scale', loc_tech) if po.value(energy_cap_equals): return backend_model.energy_cap[loc_tech] == ( energy_cap_equals * energy_cap_scale * backend_model.purchased[loc_tech] ) else: return backend_model.energy_cap[loc_tech] <= ( energy_cap_max * energy_cap_scale * backend_model.purchased[loc_tech] )
def carrier_production_min_conversion_plus_milp_constraint_rule(backend_model, loc_tech, timestep): """ Set minimum carrier production of conversion_plus MILP techs .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}} \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\geq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep) \\times \\boldsymbol{operating\\_units}(loc::tech, timestep) \\times energy_{cap, min use}(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{milp, conversion^{+}}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] timestep_resolution = backend_model.timestep_resolution[timestep] energy_cap = get_param(backend_model, 'energy_cap_per_unit', loc_tech) min_use = get_param(backend_model, 'energy_cap_min_use', (loc_tech, timestep)) loc_tech_carriers_out = ( split_comma_list(model_data_dict['lookup_loc_techs_conversion_plus']['out', loc_tech]) ) carrier_prod = sum(backend_model.carrier_prod[loc_tech_carrier, timestep] for loc_tech_carrier in loc_tech_carriers_out) return carrier_prod >= ( backend_model.operating_units[loc_tech, timestep] * timestep_resolution * energy_cap * min_use )
def balance_transmission_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and consumption of transmission technologies .. container:: scrolling-wrapper .. math:: -1 * \\boldsymbol{carrier_{con}}(loc_{from}::tech:loc_{to}::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) = \\boldsymbol{carrier_{prod}}(loc_{to}::tech:loc_{from}::carrier, timestep) \\quad \\forall loc::tech:loc \\in locs::techs:locs_{transmission}, \\forall timestep \\in timesteps Where a link is the connection between :math:`loc_{from}::tech:loc_{to}` and :math:`loc_{to}::tech:loc_{from}` for locations `to` and `from`. """ model_data_dict = backend_model.__calliope_model_data__['data'] energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech] remote_loc_tech = model_data_dict['lookup_remotes'][loc_tech] remote_loc_tech_carrier = model_data_dict['lookup_loc_techs'][remote_loc_tech] if (loc_tech_is_in(backend_model, remote_loc_tech, 'loc_techs_transmission') and get_param(backend_model, 'energy_prod', (loc_tech)) == 1): return ( backend_model.carrier_prod[loc_tech_carrier, timestep] == -1 * backend_model.carrier_con[remote_loc_tech_carrier, timestep] * energy_eff ) else: return po.Constraint.NoConstraint
def carrier_production_max_milp_constraint_rule(backend_model, carrier, node, tech, timestep): """ Set maximum carrier production of MILP techs that aren't conversion plus .. container:: scrolling-wrapper .. math:: \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\leq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep) \\times \\boldsymbol{operating\\_units}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep) \\quad \\forall loc::tech \\in loc::techs_{milp}, \\forall timestep \\in timesteps :math:`\\eta_{parasitic}` is only activated for `supply_plus` technologies """ carrier_prod = backend_model.carrier_prod[carrier, node, tech, timestep] timestep_resolution = backend_model.timestep_resolution[timestep] parasitic_eff = get_param(backend_model, "parasitic_eff", (node, tech, timestep)) energy_cap = get_param(backend_model, "energy_cap_per_unit", (node, tech)) return carrier_prod <= (backend_model.operating_units[node, tech, timestep] * timestep_resolution * energy_cap * parasitic_eff)
def resource_availability_supply_plus_constraint_rule(backend_model, loc_tech, timestep): """ Limit production from supply_plus techs to their available resource. .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{con}}(loc::tech, timestep) \\leq available\_resource(loc::tech, timestep) \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps If :math:`force\_resource(loc::tech)` is set: .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{con}}(loc::tech, timestep) = available\_resource(loc::tech, timestep) \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps Where: .. container:: scrolling-wrapper .. math:: available\_resource(loc::tech, timestep) = resource(loc::tech, timestep) \\times resource_{scale}(loc::tech) if :math:`loc::tech` is in :math:`loc::techs_{area}`: .. container:: scrolling-wrapper .. math:: available\_resource(loc::tech, timestep) = resource(loc::tech, timestep) \\times resource_{scale}(loc::tech) \\times resource_{area}(loc::tech) """ resource = get_param(backend_model, 'resource', (loc_tech, timestep)) resource_scale = get_param(backend_model, 'resource_scale', loc_tech) force_resource = get_param(backend_model, 'force_resource', loc_tech) if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_area'): available_resource = resource * resource_scale * backend_model.resource_area[ loc_tech] else: available_resource = resource * resource_scale if po.value(force_resource): return backend_model.resource_con[loc_tech, timestep] == available_resource else: return backend_model.resource_con[loc_tech, timestep] <= available_resource
def unit_capacity_systemwide_milp_constraint_rule(backend_model, tech): """ Set constraints to limit the number of purchased units of a single technology type across all locations in the model. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\sum_{loc}\\boldsymbol{units}(loc::tech) + \\boldsymbol{purchased}(loc::tech) \\begin{cases} = units_{equals, systemwide}(tech),& \\text{if } units_{equals, systemwide}(tech)\\\\ \\leq units_{max, systemwide}(tech),& \\text{if } units_{max, systemwide}(tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall tech \\in techs """ if tech in getattr(backend_model, 'techs_transmission_names', []): all_loc_techs = [ i for i in backend_model.loc_techs_transmission if i.split('::')[1].split(':')[0] == tech ] multiplier = 2 # there are always two technologies associated with one link else: all_loc_techs = [ i for i in backend_model.loc_techs if i.split('::')[1] == tech ] multiplier = 1 max_systemwide = get_param(backend_model, 'units_max_systemwide', tech) equals_systemwide = get_param(backend_model, 'units_equals_systemwide', tech) if np.isinf(po.value(max_systemwide)) and not equals_systemwide: return po.Constraint.NoConstraint elif equals_systemwide and np.isinf(po.value(equals_systemwide)): raise ValueError( 'Cannot use inf for energy_cap_equals_systemwide for tech `{}`'.format(tech) ) sum_expr_units = sum( backend_model.units[loc_tech] for loc_tech in all_loc_techs if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_milp') ) sum_expr_purchase = sum( backend_model.purchased[loc_tech] for loc_tech in all_loc_techs if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_purchase') ) if equals_systemwide: return sum_expr_units + sum_expr_purchase == equals_systemwide * multiplier else: return sum_expr_units + sum_expr_purchase <= max_systemwide * multiplier
def unit_capacity_systemwide_constraint_rule(backend_model, tech): """ Set constraints to limit the number of purchased units of a single technology type across all locations in the model. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\sum_{loc}\\boldsymbol{units}(loc::tech) + \\boldsymbol{purchased}(loc::tech) \\begin{cases} = units_{equals, systemwide}(tech),& \\text{if } units_{equals, systemwide}(tech)\\\\ \\leq units_{max, systemwide}(tech),& \\text{if } units_{max, systemwide}(tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall tech \\in techs """ if tech in backend_model.techs_transmission_names: all_loc_techs = [ i for i in backend_model.loc_techs_transmission if i.split('::')[1].split(':')[0] == tech ] multiplier = 2 # there are always two technologies associated with one link else: all_loc_techs = [ i for i in backend_model.loc_techs if i.split('::')[1] == tech ] multiplier = 1 max_systemwide = get_param(backend_model, 'units_max_systemwide', tech) equals_systemwide = get_param(backend_model, 'units_equals_systemwide', tech) if np.isinf(po.value(max_systemwide)) and not equals_systemwide: return po.Constraint.NoConstraint elif equals_systemwide and np.isinf(po.value(equals_systemwide)): raise ValueError( 'Cannot use inf for energy_cap_equals_systemwide for tech `{}`'.format(tech) ) sum_expr_units = sum( backend_model.units[loc_tech] for loc_tech in all_loc_techs if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_milp') ) sum_expr_purchase = sum( backend_model.purchased[loc_tech] for loc_tech in all_loc_techs if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_purchase') ) if equals_systemwide: return sum_expr_units + sum_expr_purchase == equals_systemwide * multiplier else: return sum_expr_units + sum_expr_purchase <= max_systemwide * multiplier
def balance_storage_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and consumption of storage technologies, alongside any use of the stored volume. .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{resolution(timestep)} - \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{storage}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data['data'] run_config = backend_model.__calliope_run_config energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) if po.value(energy_eff) == 0: carrier_prod = 0 else: loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech] carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff carrier_con = backend_model.carrier_con[loc_tech_carrier, timestep] * energy_eff current_timestep = backend_model.timesteps.order_dict[timestep] if current_timestep == 0 and not run_config['cyclic_storage']: storage_previous_step = ( get_param(backend_model, 'storage_initial', loc_tech) * backend_model.storage_cap[loc_tech]) elif (hasattr(backend_model, 'storage_inter_cluster') and model_data_dict['lookup_cluster_first_timestep'][timestep]): storage_previous_step = 0 else: if (hasattr(backend_model, 'clusters') and model_data_dict['lookup_cluster_first_timestep'][timestep]): previous_step = model_data_dict['lookup_cluster_last_timestep'][ timestep] elif current_timestep == 0 and run_config['cyclic_storage']: previous_step = backend_model.timesteps[-1] else: previous_step = get_previous_timestep(backend_model.timesteps, timestep) storage_loss = get_param(backend_model, 'storage_loss', loc_tech) time_resolution = backend_model.timestep_resolution[previous_step] storage_previous_step = ( ((1 - storage_loss)**time_resolution) * backend_model.storage[loc_tech, previous_step]) return (backend_model.storage[loc_tech, timestep] == storage_previous_step - carrier_prod - carrier_con)
def resource_availability_supply_plus_constraint_rule(backend_model, loc_tech, timestep): """ Limit production from supply_plus techs to their available resource. .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{con}}(loc::tech, timestep) \\leq available\\_resource(loc::tech, timestep) \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps If :math:`force\\_resource(loc::tech)` is set: .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{con}}(loc::tech, timestep) = available\\_resource(loc::tech, timestep) \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps Where: .. container:: scrolling-wrapper .. math:: available\\_resource(loc::tech, timestep) = resource(loc::tech, timestep) \\times resource_{scale}(loc::tech) if :math:`loc::tech` is in :math:`loc::techs_{area}`: .. container:: scrolling-wrapper .. math:: available\\_resource(loc::tech, timestep) = resource(loc::tech, timestep) \\times resource_{scale}(loc::tech) \\times resource_{area}(loc::tech) """ resource = get_param(backend_model, 'resource', (loc_tech, timestep)) resource_scale = get_param(backend_model, 'resource_scale', loc_tech) force_resource = get_param(backend_model, 'force_resource', loc_tech) resource_unit = get_param(backend_model, 'resource_unit', loc_tech) if po.value(resource_unit) == 'energy_per_area': available_resource = resource * resource_scale * backend_model.resource_area[loc_tech] elif po.value(resource_unit) == 'energy_per_cap': available_resource = resource * resource_scale * backend_model.energy_cap[loc_tech] else: available_resource = resource * resource_scale if po.value(force_resource): return backend_model.resource_con[loc_tech, timestep] == available_resource else: return backend_model.resource_con[loc_tech, timestep] <= available_resource
def cost_var_constraint_rule(backend_model, cost, loc_tech, timestep): """ Calculate costs from time-varying decision variables .. container:: scrolling-wrapper .. math:: """ model_data_dict = backend_model.__calliope_model_data__ cost_om_prod = get_param(backend_model, 'cost_om_prod', (cost, loc_tech, timestep)) cost_om_con = get_param(backend_model, 'cost_om_con', (cost, loc_tech, timestep)) weight = backend_model.timestep_weights[timestep] loc_tech_carrier = model_data_dict['data']['lookup_loc_techs'][loc_tech] if po.value(cost_om_prod): cost_prod = cost_om_prod * weight * backend_model.carrier_prod[ loc_tech_carrier, timestep] else: cost_prod = 0 if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_supply_plus') and cost_om_con: resource_eff = get_param(backend_model, 'resource_eff', (loc_tech, timestep)) if po.value( resource_eff ) > 0: # In case resource_eff is zero, to avoid an infinite value # Dividing by r_eff here so we get the actual r used, not the r # moved into storage... cost_con = cost_om_con * weight * ( backend_model.resource_con[loc_tech, timestep] / resource_eff) else: cost_con = 0 elif loc_tech_is_in(backend_model, loc_tech, 'loc_techs_supply') and cost_om_con: energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) if po.value( energy_eff ) > 0: # in case energy_eff is zero, to avoid an infinite value cost_con = cost_om_con * weight * ( backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff) else: cost_con = 0 else: cost_con = 0 backend_model.cost_var_rhs[cost, loc_tech, timestep].expr = cost_prod + cost_con return (backend_model.cost_var[cost, loc_tech, timestep] == backend_model.cost_var_rhs[cost, loc_tech, timestep])
def balance_conversion_plus_tiers_constraint_rule(backend_model, tier, loc_tech, timestep): """ Force all carrier_in_2/carrier_in_3 and carrier_out_2/carrier_out_3 to follow carrier_in and carrier_out (respectively). If `tier` in ['out_2', 'out_3']: .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}} ( \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{ carrier\\_ratio(loc::tech::carrier, `out')} = \\sum_{loc::tech::carrier \\in loc::tech::carriers_{tier}} ( \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{ carrier\\_ratio(loc::tech::carrier, tier)} \\quad \\forall \\text { tier } \\in [`out_2', `out_3'], \\forall loc::tech \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps If `tier` in ['in_2', 'in_3']: .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech::carrier \\in loc::tech::carriers_{in}} \\frac{\\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)}{ carrier\\_ratio(loc::tech::carrier, `in')} = \\sum_{loc::tech::carrier \\in loc::tech::carriers_{tier}} \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{ carrier\\_ratio(loc::tech::carrier, tier)} \\quad \\forall \\text{ tier } \\in [`in_2', `in_3'], \\forall loc::tech \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps """ primary_tier, decision_variable = get_conversion_plus_io( backend_model, tier) model_data_dict = backend_model.__calliope_model_data['data'] loc_tech_carriers_1 = split_comma_list( model_data_dict['lookup_loc_techs_conversion_plus'][primary_tier, loc_tech]) loc_tech_carriers_2 = split_comma_list( model_data_dict['lookup_loc_techs_conversion_plus'][tier, loc_tech]) c_1 = sum(decision_variable[loc_tech_carrier, timestep] / get_param(backend_model, 'carrier_ratios', (primary_tier, loc_tech_carrier)) for loc_tech_carrier in loc_tech_carriers_1) c_2 = sum( decision_variable[loc_tech_carrier, timestep] / get_param(backend_model, 'carrier_ratios', (tier, loc_tech_carrier)) for loc_tech_carrier in loc_tech_carriers_2) return c_1 == c_2
def balance_storage_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and consumption of storage technologies, alongside any use of the stored volume. .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{resolution(timestep)} - \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{storage}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] model_attrs = backend_model.__calliope_model_data__['attrs'] energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) if po.value(energy_eff) == 0: carrier_prod = 0 else: loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech] carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff carrier_con = backend_model.carrier_con[loc_tech_carrier, timestep] * energy_eff current_timestep = backend_model.timesteps.order_dict[timestep] if current_timestep == 0 and not model_attrs['run.cyclic_storage']: storage_previous_step = get_param(backend_model, 'storage_initial', loc_tech) elif (hasattr(backend_model, 'storage_inter_cluster') and model_data_dict['lookup_cluster_first_timestep'][timestep]): storage_previous_step = 0 else: if (hasattr(backend_model, 'clusters') and model_data_dict['lookup_cluster_first_timestep'][timestep]): previous_step = model_data_dict['lookup_cluster_last_timestep'][timestep] elif current_timestep == 0 and model_attrs['run.cyclic_storage']: previous_step = backend_model.timesteps[-1] else: previous_step = get_previous_timestep(backend_model.timesteps, timestep) storage_loss = get_param(backend_model, 'storage_loss', loc_tech) time_resolution = backend_model.timestep_resolution[previous_step] storage_previous_step = ( ((1 - storage_loss) ** time_resolution) * backend_model.storage[loc_tech, previous_step] ) return ( backend_model.storage[loc_tech, timestep] == storage_previous_step - carrier_prod - carrier_con )
def test_get_param_no_timestep_possible(self): """ """ m = build_model({}, "simple_supply,two_hours,investment_costs") m.run() param = get_param(m._backend_model, "energy_cap_max", ("b", "test_supply_elec")) assert po.value(param) == 10 # see test model.yaml param = get_param(m._backend_model, "cost_energy_cap", ("monetary", "a", "test_supply_elec")) assert po.value(param) == 10
def cost_var_conversion_constraint_rule(backend_model, cost, loc_tech, timestep): """ Add time-varying conversion technology costs .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{var}}(loc::tech, cost, timestep) = \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\times timestep_{weight}(timestep) \\times cost_{om, prod}(loc::tech, cost, timestep) + \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times timestep_{weight}(timestep) \\times cost_{om, con}(loc::tech, cost, timestep) \\quad \\forall loc::tech \\in loc::techs_{cost_{var}, conversion} """ model_data_dict = backend_model.__calliope_model_data weight = backend_model.timestep_weights[timestep] loc_tech_carrier_in = model_data_dict["data"]["lookup_loc_techs_conversion"][ ("in", loc_tech) ] loc_tech_carrier_out = model_data_dict["data"]["lookup_loc_techs_conversion"][ ("out", loc_tech) ] cost_om_prod = get_param(backend_model, "cost_om_prod", (cost, loc_tech, timestep)) cost_om_con = get_param(backend_model, "cost_om_con", (cost, loc_tech, timestep)) if po.value(cost_om_prod): cost_prod = ( cost_om_prod * weight * backend_model.carrier_prod[loc_tech_carrier_out, timestep] ) else: cost_prod = 0 if po.value(cost_om_con): cost_con = ( cost_om_con * weight * -1 * backend_model.carrier_con[loc_tech_carrier_in, timestep] ) else: cost_con = 0 backend_model.cost_var_rhs[cost, loc_tech, timestep] = cost_prod + cost_con return ( backend_model.cost_var[cost, loc_tech, timestep] == backend_model.cost_var_rhs[cost, loc_tech, timestep] )
def energy_capacity_systemwide_constraint_rule(backend_model, tech): """ Set constraints to limit the capacity of a single technology type across all locations in the model. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\sum_{loc}\\boldsymbol{energy_{cap}}(loc::tech) \\begin{cases} = energy_{cap, equals, systemwide}(loc::tech),& \\text{if } energy_{cap, equals, systemwide}(loc::tech)\\\\ \\leq energy_{cap, max, systemwide}(loc::tech),& \\text{if } energy_{cap, max, systemwide}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall tech \\in techs """ if tech in backend_model.techs_transmission_names: all_loc_techs = [ i for i in backend_model.loc_techs_transmission if i.split('::')[1].split(':')[0] == tech ] multiplier = 2 # there are always two technologies associated with one link else: all_loc_techs = [ i for i in backend_model.loc_techs if i.split('::')[1] == tech ] multiplier = 1 max_systemwide = get_param(backend_model, 'energy_cap_max_systemwide', tech) equals_systemwide = get_param(backend_model, 'energy_cap_equals_systemwide', tech) if np.isinf(po.value(max_systemwide)) and not equals_systemwide: return po.Constraint.NoConstraint elif equals_systemwide and np.isinf(po.value(equals_systemwide)): raise exceptions.ModelError( 'Cannot use inf for energy_cap_equals_systemwide for tech `{}`'. format(tech)) sum_expr = sum(backend_model.energy_cap[loc_tech] for loc_tech in all_loc_techs) if equals_systemwide: return sum_expr == equals_systemwide * multiplier else: return sum_expr <= max_systemwide * multiplier
def balance_conversion_plus_tiers_constraint_rule(backend_model, tier, loc_tech, timestep): """ Force all carrier_in_2/carrier_in_3 and carrier_out_2/carrier_out_3 to follow carrier_in and carrier_out (respectively). If `tier` in ['out_2', 'out_3']: .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}} ( \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{ carrier\\_ratio(loc::tech::carrier, `out')} = \\sum_{loc::tech::carrier \\in loc::tech::carriers_{tier}} ( \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{ carrier\\_ratio(loc::tech::carrier, tier)} \\quad \\forall \\text { tier } \\in [`out_2', `out_3'], \\forall loc::tech \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps If `tier` in ['in_2', 'in_3']: .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech::carrier \\in loc::tech::carriers_{in}} \\frac{\\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)}{ carrier\\_ratio(loc::tech::carrier, `in')} = \\sum_{loc::tech::carrier \\in loc::tech::carriers_{tier}} \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{ carrier\\_ratio(loc::tech::carrier, tier)} \\quad \\forall \\text{ tier } \\in [`in_2', `in_3'], \\forall loc::tech \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps """ primary_tier, decision_variable = get_conversion_plus_io(backend_model, tier) model_data_dict = backend_model.__calliope_model_data__['data'] loc_tech_carriers_1 = split_comma_list( model_data_dict['lookup_loc_techs_conversion_plus'][primary_tier, loc_tech] ) loc_tech_carriers_2 = split_comma_list( model_data_dict['lookup_loc_techs_conversion_plus'][tier, loc_tech] ) c_1 = sum(decision_variable[loc_tech_carrier, timestep] / get_param(backend_model, 'carrier_ratios', (primary_tier, loc_tech_carrier)) for loc_tech_carrier in loc_tech_carriers_1) c_2 = sum(decision_variable[loc_tech_carrier, timestep] / get_param(backend_model, 'carrier_ratios', (tier, loc_tech_carrier)) for loc_tech_carrier in loc_tech_carriers_2) return c_1 == c_2
def energy_capacity_systemwide_constraint_rule(backend_model, tech): """ Set constraints to limit the capacity of a single technology type across all locations in the model. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\sum_{loc}\\boldsymbol{energy_{cap}}(loc::tech) \\begin{cases} = energy_{cap, equals, systemwide}(loc::tech),& \\text{if } energy_{cap, equals, systemwide}(loc::tech)\\\\ \\leq energy_{cap, max, systemwide}(loc::tech),& \\text{if } energy_{cap, max, systemwide}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall tech \\in techs """ if tech in backend_model.techs_transmission_names: all_loc_techs = [ i for i in backend_model.loc_techs_transmission if i.split('::')[1].split(':')[0] == tech ] multiplier = 2 # there are always two technologies associated with one link else: all_loc_techs = [ i for i in backend_model.loc_techs if i.split('::')[1] == tech ] multiplier = 1 max_systemwide = get_param(backend_model, 'energy_cap_max_systemwide', tech) equals_systemwide = get_param(backend_model, 'energy_cap_equals_systemwide', tech) if np.isinf(po.value(max_systemwide)) and not equals_systemwide: return po.Constraint.NoConstraint elif equals_systemwide and np.isinf(po.value(equals_systemwide)): raise exceptions.ModelError( 'Cannot use inf for energy_cap_equals_systemwide for tech `{}`'.format(tech) ) sum_expr = sum(backend_model.energy_cap[loc_tech] for loc_tech in all_loc_techs) if equals_systemwide: return sum_expr == equals_systemwide * multiplier else: return sum_expr <= max_systemwide * multiplier
def test_get_param_no_default_defined(self): """ If a default is not defined, raise KeyError """ m = build_model({}, "simple_supply,two_hours,investment_costs") m.run() with pytest.raises(KeyError): get_param( m._backend_model, "random_param", ("1::test_demand_elec", m._backend_model.timesteps[1]), ) get_param(m._backend_model, "random_param", ("1::test_supply_elec"))
def cost_var_constraint_rule(backend_model, cost, loc_tech, timestep): """ Calculate costs from time-varying decision variables .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{var}}(cost, loc::tech, timestep) = cost_{prod}(cost, loc::tech, timestep) + cost_{con}(cost, loc::tech, timestep) cost_{prod}(cost, loc::tech, timestep) = cost_{om\\_prod}(cost, loc::tech, timestep) \\times weight(timestep) \\times \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) prod\\_con\\_eff = \\begin{cases} = \\boldsymbol{resource_{con}}(loc::tech, timestep),& \\text{if } loc::tech \\in loc\\_techs\\_supply\\_plus \\\\ = \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{energy_eff(loc::tech, timestep)},& \\text{if } loc::tech \\in loc\\_techs\\_supply \\\\ \\end{cases} cost_{con}(cost, loc::tech, timestep) = cost_{om\\_con}(cost, loc::tech, timestep) \\times weight(timestep) \\times prod\\_con\\_eff """ model_data_dict = backend_model.__calliope_model_data__ cost_om_prod = get_param(backend_model, 'cost_om_prod', (cost, loc_tech, timestep)) cost_om_con = get_param(backend_model, 'cost_om_con', (cost, loc_tech, timestep)) weight = backend_model.timestep_weights[timestep] loc_tech_carrier = model_data_dict['data']['lookup_loc_techs'][loc_tech] if po.value(cost_om_prod): cost_prod = cost_om_prod * weight * backend_model.carrier_prod[loc_tech_carrier, timestep] else: cost_prod = 0 if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_supply_plus') and cost_om_con: cost_con = cost_om_con * weight * backend_model.resource_con[loc_tech, timestep] elif loc_tech_is_in(backend_model, loc_tech, 'loc_techs_supply') and cost_om_con: energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) if po.value(energy_eff) > 0: # in case energy_eff is zero, to avoid an infinite value cost_con = cost_om_con * weight * (backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff) else: cost_con = 0 else: cost_con = 0 backend_model.cost_var_rhs[cost, loc_tech, timestep].expr = cost_prod + cost_con return (backend_model.cost_var[cost, loc_tech, timestep] == backend_model.cost_var_rhs[cost, loc_tech, timestep])
def cost_var_constraint_rule(backend_model, cost, loc_tech, timestep): """ Calculate costs from time-varying decision variables .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{var}}(cost, loc::tech, timestep) = cost_{prod}(cost, loc::tech, timestep) + cost_{con}(cost, loc::tech, timestep) cost_{prod}(cost, loc::tech, timestep) = cost_{om\\_prod}(cost, loc::tech, timestep) \\times weight(timestep) \\times \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) prod\\_con\\_eff = \\begin{cases} = \\boldsymbol{resource_{con}}(loc::tech, timestep),& \\text{if } loc::tech \\in loc\\_techs\\_supply\\_plus \\\\ = \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{energy_eff(loc::tech, timestep)},& \\text{if } loc::tech \\in loc\\_techs\\_supply \\\\ \\end{cases} cost_{con}(cost, loc::tech, timestep) = cost_{om\\_con}(cost, loc::tech, timestep) \\times weight(timestep) \\times prod\\_con\\_eff """ model_data_dict = backend_model.__calliope_model_data cost_om_prod = get_param(backend_model, 'cost_om_prod', (cost, loc_tech, timestep)) cost_om_con = get_param(backend_model, 'cost_om_con', (cost, loc_tech, timestep)) weight = backend_model.timestep_weights[timestep] loc_tech_carrier = model_data_dict['data']['lookup_loc_techs'][loc_tech] if po.value(cost_om_prod): cost_prod = cost_om_prod * weight * backend_model.carrier_prod[loc_tech_carrier, timestep] else: cost_prod = 0 if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_supply_plus') and cost_om_con: cost_con = cost_om_con * weight * backend_model.resource_con[loc_tech, timestep] elif loc_tech_is_in(backend_model, loc_tech, 'loc_techs_supply') and cost_om_con: energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) if po.value(energy_eff) > 0: # in case energy_eff is zero, to avoid an infinite value cost_con = cost_om_con * weight * (backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff) else: cost_con = 0 else: cost_con = 0 backend_model.cost_var_rhs[cost, loc_tech, timestep].expr = cost_prod + cost_con return (backend_model.cost_var[cost, loc_tech, timestep] == backend_model.cost_var_rhs[cost, loc_tech, timestep])
def cost_var_conversion_plus_constraint_rule(backend_model, cost, loc_tech, timestep): """ Add time-varying conversion_plus technology costs .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{var}}(loc::tech, cost, timestep) = \\boldsymbol{carrier_{prod}}(loc::tech::carrier_{primary}, timestep) \\times timestep_{weight}(timestep) \\times cost_{om, prod}(loc::tech, cost, timestep) + \\boldsymbol{carrier_{con}}(loc::tech::carrier_{primary}, timestep) \\times timestep_{weight}(timestep) \\times cost_{om, con}(loc::tech, cost, timestep) \\quad \\forall loc::tech \\in loc::techs_{cost_{var}, conversion^{+}} """ model_data_dict = backend_model.__calliope_model_data['data'] weight = backend_model.timestep_weights[timestep] loc_tech_carrier_con = ( model_data_dict['lookup_primary_loc_tech_carriers_in'][loc_tech] ) loc_tech_carrier_prod = ( model_data_dict['lookup_primary_loc_tech_carriers_out'][loc_tech] ) var_cost = 0 if loc_tech_carrier_prod in backend_model.loc_tech_carriers_prod: cost_om_prod = get_param(backend_model, 'cost_om_prod', (cost, loc_tech, timestep)) if cost_om_prod: var_cost += ( cost_om_prod * weight * backend_model.carrier_prod[loc_tech_carrier_prod, timestep] ) if loc_tech_carrier_con in backend_model.loc_tech_carriers_con: cost_om_con = get_param(backend_model, 'cost_om_con', (cost, loc_tech, timestep)) if cost_om_con: var_cost += ( cost_om_con * weight * -1 * backend_model.carrier_con[loc_tech_carrier_con, timestep] ) backend_model.cost_var_rhs[cost, loc_tech, timestep] = var_cost return (backend_model.cost_var[cost, loc_tech, timestep] == backend_model.cost_var_rhs[cost, loc_tech, timestep])
def cost_var_conversion_plus_constraint_rule(backend_model, cost, loc_tech, timestep): """ Add time-varying conversion_plus technology costs .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{var}}(loc::tech, cost, timestep) = \\boldsymbol{carrier_{prod}}(loc::tech::carrier_{primary}, timestep) \\times timestep_{weight}(timestep) \\times cost_{om, prod}(loc::tech, cost, timestep) + \\boldsymbol{carrier_{con}}(loc::tech::carrier_{primary}, timestep) \\times timestep_{weight}(timestep) \\times cost_{om, con}(loc::tech, cost, timestep) \\quad \\forall loc::tech \\in loc::techs_{cost_{var}, conversion^{+}} """ model_data_dict = backend_model.__calliope_model_data__['data'] weight = backend_model.timestep_weights[timestep] loc_tech_carrier_con = ( model_data_dict['lookup_primary_loc_tech_carriers_in'][loc_tech] ) loc_tech_carrier_prod = ( model_data_dict['lookup_primary_loc_tech_carriers_out'][loc_tech] ) var_cost = 0 if loc_tech_carrier_prod in backend_model.loc_tech_carriers_prod: cost_om_prod = get_param(backend_model, 'cost_om_prod', (cost, loc_tech, timestep)) if cost_om_prod: var_cost += ( cost_om_prod * weight * backend_model.carrier_prod[loc_tech_carrier_prod, timestep] ) if loc_tech_carrier_con in backend_model.loc_tech_carriers_con: cost_om_con = get_param(backend_model, 'cost_om_con', (cost, loc_tech, timestep)) if cost_om_con: var_cost += ( cost_om_con * weight * -1 * backend_model.carrier_con[loc_tech_carrier_con, timestep] ) backend_model.cost_var_rhs[cost, loc_tech, timestep] = var_cost return (backend_model.cost_var[cost, loc_tech, timestep] == backend_model.cost_var_rhs[cost, loc_tech, timestep])
def balance_conversion_constraint_rule(backend_model, loc_tech, timestep): """ Balance energy carrier consumption and production .. container:: scrolling-wrapper .. math:: -1 * \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) = \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) \\quad \\forall loc::tech \\in locs::techs_{conversion}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] loc_tech_carrier_out = model_data_dict['lookup_loc_techs_conversion'][('out', loc_tech)] loc_tech_carrier_in = model_data_dict['lookup_loc_techs_conversion'][('in', loc_tech)] energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) return ( backend_model.carrier_prod[loc_tech_carrier_out, timestep] == -1 * backend_model.carrier_con[loc_tech_carrier_in, timestep] * energy_eff )
def energy_capacity_constraint_rule(backend_model, loc_tech): """ Set upper and lower bounds for energy_cap. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)} \\begin{cases} = energy_{cap, equals}(loc::tech),& \\text{if } energy_{cap, equals}(loc::tech)\\\\ \\leq energy_{cap, max}(loc::tech),& \\text{if } energy_{cap, max}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall loc::tech \\in loc::techs and (if ``equals`` not enforced): .. container:: scrolling-wrapper .. math:: \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)} \\geq energy_{cap, min}(loc::tech) \\quad \\forall loc::tech \\in loc::techs """ scale = get_param(backend_model, 'energy_cap_scale', loc_tech) return get_capacity_constraint(backend_model, 'energy_cap', loc_tech, scale=scale)
def storage_inter_min_rule(backend_model, loc_tech, datestep): """ When clustering days, to reduce the timeseries length, set minimum limit on the intra-cluster and inter-date stored energy. intra-cluster = all timesteps in a single cluster datesteps = all dates in the unclustered timeseries (each has a corresponding cluster) `Ref: DOI 10.1016/j.apenergy.2018.01.023 <https://doi.org/10.1016/j.apenergy.2018.01.023>`_ .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage_{inter\\_cluster}}(loc::tech, datestep) \\times (1 - storage\\_loss(loc::tech, timestep))^{24} + \\boldsymbol{storage_{intra\\_cluster, min}}(loc::tech, cluster(datestep)) \\geq 0 \\quad \\forall loc::tech \\in loc::techs_{store}, \\forall datestep \\in datesteps Where :math:`cluster(datestep)` is the cluster number in which the datestep is located. """ cluster = backend_model.__calliope_model_data__['data']['lookup_datestep_cluster'][datestep] storage_loss = get_param(backend_model, 'storage_loss', loc_tech) return ( backend_model.storage_inter_cluster[loc_tech, datestep] * ((1 - storage_loss) ** 24) + backend_model.storage_intra_cluster_min[loc_tech, cluster] >= 0 )
def resource_area_constraint_rule(backend_model, constraint_group, what): """ Enforce upper and lower bounds of resource_area for groups of technologies and locations. .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{area}}(loc::tech) \\leq group\\_resource\\_area\\_max\\\\ \\boldsymbol{resource_{area}}(loc::tech) \\geq group\\_resource\\_area\\_min """ threshold = get_param( backend_model, "group_resource_area_{}".format(what), (constraint_group) ) if check_value(threshold): return return_noconstraint("resource_area", constraint_group) else: lhs_loc_techs = getattr( backend_model, "group_constraint_loc_techs_{}".format(constraint_group) ) lhs = sum(backend_model.resource_area[loc_tech] for loc_tech in lhs_loc_techs) rhs = threshold return equalizer(lhs, rhs, what)
def update_costs_investment_purchase_constraint(backend_model, cost, loc_tech): """ Add binary investment costs (cost * binary_purchased_unit) .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{investment}}(cost, loc::tech) += \\boldsymbol{purchased}(loc::tech) \\times cost_{purchase}(cost, loc::tech) * timestep_{weight} * depreciation \\quad \\forall cost \\in costs, \\forall loc::tech \\in loc::techs_{cost_{investment}, purchase} """ model_data_dict = backend_model.__calliope_model_data__ ts_weight = get_timestep_weight(backend_model) depreciation_rate = model_data_dict['data']['cost_depreciation_rate'][( cost, loc_tech)] cost_purchase = get_param(backend_model, 'cost_purchase', (cost, loc_tech)) cost_of_purchase = (backend_model.purchased[loc_tech] * cost_purchase * ts_weight * depreciation_rate) backend_model.cost_investment_rhs[cost, loc_tech].expr += cost_of_purchase return None
def update_costs_var_constraint(backend_model, cost, loc_tech, timestep): """ Update time varying cost constraint (from costs.py) to include export .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{var}}(cost, loc::tech, timestep) += cost_{export}(cost, loc::tech, timestep) \\times \\boldsymbol{carrier_{export}}(loc::tech::carrier, timestep) * timestep_{weight} \\quad \\forall cost \\in costs, \\forall loc::tech \\in loc::techs_{cost_{var}, export}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] loc_tech_carrier = model_data_dict['lookup_loc_techs_export'][(loc_tech)] weight = backend_model.timestep_weights[timestep] cost_export = ( get_param(backend_model, 'cost_export', (cost, loc_tech, timestep)) * backend_model.carrier_export[loc_tech_carrier, timestep] * weight ) backend_model.cost_var_rhs[cost, loc_tech, timestep].expr += cost_export
def carrier_prod_constraint_rule(backend_model, constraint_group, carrier, what): """ Enforces carrier_prod for groups of technologies and locations, as a sum over the entire model period. .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech::carrier \\in given\\_group, timestep \\in timesteps} carrier_{prod}(loc::tech::carrier, timestep) \\leq supply_max """ limit = get_param( backend_model, "group_carrier_prod_{}".format(what), (carrier, constraint_group) ) if check_value(limit): return return_noconstraint("carrier_prod", constraint_group) else: # We won't actually use the rhs techs lhs_loc_techs, rhs_loc_techs = get_carrier_prod_share_lhs_and_rhs_loc_techs( backend_model, constraint_group ) lhs = sum( backend_model.carrier_prod[loc_tech + "::" + carrier, timestep] for loc_tech in lhs_loc_techs for timestep in backend_model.timesteps if loc_tech + "::" + carrier in backend_model.loc_tech_carriers_prod ) return equalizer(lhs, limit, what)
def carrier_production_min_conversion_plus_constraint_rule( backend_model, loc_tech, timestep): """ Set minimum conversion_plus carrier production. .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}} \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\leq \\boldsymbol{energy_{cap}}(loc::tech) \\times timestep\\_resolution(timestep) \\times energy_{cap, min use}(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data["data"] timestep_resolution = backend_model.timestep_resolution[timestep] min_use = get_param(backend_model, "energy_cap_min_use", (loc_tech, timestep)) loc_tech_carriers_out = split_comma_list( model_data_dict["lookup_loc_techs_conversion_plus"]["out", loc_tech]) carrier_prod = sum(backend_model.carrier_prod[loc_tech_carrier, timestep] for loc_tech_carrier in loc_tech_carriers_out) return carrier_prod >= (timestep_resolution * backend_model.energy_cap[loc_tech] * min_use)
def update_costs_investment_units_constraint(backend_model, cost, loc_tech): """ Add MILP investment costs (cost * number of units purchased) .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{investment}}(cost, loc::tech) += \\boldsymbol{units}(loc::tech) \\times cost_{purchase}(cost, loc::tech) * timestep_{weight} * depreciation \\quad \\forall cost \\in costs, \\forall loc::tech \\in loc::techs_{cost_{investment}, milp} """ model_data_dict = backend_model.__calliope_model_data__ ts_weight = get_timestep_weight(backend_model) depreciation_rate = model_data_dict['data']['cost_depreciation_rate'][( cost, loc_tech)] cost_purchase = get_param(backend_model, 'cost_purchase', (cost, loc_tech)) cost_of_purchase = (backend_model.units[loc_tech] * cost_purchase * ts_weight * depreciation_rate) if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_transmission'): cost_of_purchase = cost_of_purchase / 2 backend_model.cost_investment_rhs[cost, loc_tech].expr += cost_of_purchase return None
def energy_cap_share_constraint_rule(backend_model, constraint_group, what): """ Enforces shares of energy_cap for groups of technologies and locations. The share is relative to ``supply`` and ``supply_plus`` technologies only. .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech \\in given\\_group} energy_{cap}(loc::tech) \\leq share \\times \\sum_{loc::tech \\in loc\\_tech\\_supply\\_all \\in given\\_locations} energy_{cap}(loc::tech) """ share = get_param( backend_model, "group_energy_cap_share_{}".format(what), (constraint_group) ) if check_value(share): return return_noconstraint("energy_cap_share", constraint_group) else: lhs_loc_techs = getattr( backend_model, "group_constraint_loc_techs_{}".format(constraint_group) ) lhs_locs = [loc_tech.split("::")[0] for loc_tech in lhs_loc_techs] rhs_loc_techs = [ i for i in backend_model.loc_techs_supply_conversion_all if i.split("::")[0] in lhs_locs ] lhs = sum(backend_model.energy_cap[loc_tech] for loc_tech in lhs_loc_techs) rhs = share * sum( backend_model.energy_cap[loc_tech] for loc_tech in rhs_loc_techs ) return equalizer(lhs, rhs, what)
def update_costs_investment_purchase_constraint(backend_model, cost, loc_tech): """ Add binary investment costs (cost * binary_purchased_unit) .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{investment}}(cost, loc::tech) += \\boldsymbol{purchased}(loc::tech) \\times cost_{purchase}(cost, loc::tech) * timestep_{weight} * depreciation \\quad \\forall cost \\in costs, \\forall loc::tech \\in loc::techs_{cost_{investment}, purchase} """ model_data_dict = backend_model.__calliope_model_data__ ts_weight = get_timestep_weight(backend_model) depreciation_rate = model_data_dict['data']['cost_depreciation_rate'][(cost, loc_tech)] cost_purchase = get_param(backend_model, 'cost_purchase', (cost, loc_tech)) cost_of_purchase = ( backend_model.purchased[loc_tech] * cost_purchase * ts_weight * depreciation_rate ) if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_transmission'): cost_of_purchase = cost_of_purchase / 2 backend_model.cost_investment_rhs[cost, loc_tech].expr += cost_of_purchase return None
def carrier_production_min_conversion_plus_constraint_rule(backend_model, loc_tech, timestep): """ Set minimum conversion_plus carrier production. .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}} \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\leq \\boldsymbol{energy_{cap}}(loc::tech) \\times timestep\\_resolution(timestep) \\times energy_{cap, min use}(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] timestep_resolution = backend_model.timestep_resolution[timestep] min_use = get_param(backend_model, 'energy_cap_min_use', (loc_tech, timestep)) loc_tech_carriers_out = split_comma_list( model_data_dict['lookup_loc_techs_conversion_plus']['out', loc_tech] ) carrier_prod = sum(backend_model.carrier_prod[loc_tech_carrier, timestep] for loc_tech_carrier in loc_tech_carriers_out) return carrier_prod >= ( timestep_resolution * backend_model.energy_cap[loc_tech] * min_use )
def cost_var_conversion_constraint_rule(backend_model, cost, loc_tech, timestep): """ Add time-varying conversion technology costs .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{var}}(loc::tech, cost, timestep) = \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\times timestep_{weight}(timestep) \\times cost_{om, prod}(loc::tech, cost, timestep) + \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times timestep_{weight}(timestep) \\times cost_{om, con}(loc::tech, cost, timestep) \\quad \\forall loc::tech \\in loc::techs_{cost_{var}, conversion} """ model_data_dict = backend_model.__calliope_model_data__ weight = backend_model.timestep_weights[timestep] loc_tech_carrier_in = ( model_data_dict['data']['lookup_loc_techs_conversion'][('in', loc_tech)] ) loc_tech_carrier_out = ( model_data_dict['data']['lookup_loc_techs_conversion'][('out', loc_tech)] ) cost_om_prod = get_param(backend_model, 'cost_om_prod', (cost, loc_tech, timestep)) cost_om_con = get_param(backend_model, 'cost_om_con', (cost, loc_tech, timestep)) if po.value(cost_om_prod): cost_prod = (cost_om_prod * weight * backend_model.carrier_prod[loc_tech_carrier_out, timestep]) else: cost_prod = 0 if po.value(cost_om_con): cost_con = (cost_om_con * weight * -1 * backend_model.carrier_con[loc_tech_carrier_in, timestep]) else: cost_con = 0 backend_model.cost_var_rhs[cost, loc_tech, timestep] = cost_prod + cost_con return (backend_model.cost_var[cost, loc_tech, timestep] == backend_model.cost_var_rhs[cost, loc_tech, timestep])
def storage_initial_rule(backend_model, loc_tech): """ If storage is cyclic, allow an initial storage to still be set. This is applied to the storage of the final timestep/datestep of the series as that, in cyclic storage, is the 'storage_previous_step' for the first timestep/datestep. If clustering and ``storage_inter_cluster`` exists: .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage_{inter\\_cluster}}(loc::tech, datestep_{final}) \\times ((1 - storage_loss) ** 24) = storage_{initial}(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{store}, \\forall datestep \\in datesteps Where :math:`datestep_{final}` is the last datestep of the timeseries Else: .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep_{final}) \\times ((1 - storage_loss) ** 24) = storage_{initial}(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{store}, \\forall timestep \\in timesteps Where :math:`timestep_{final}` is the last timestep of the timeseries """ storage_initial = get_param(backend_model, 'storage_initial', loc_tech) storage_loss = get_param(backend_model, 'storage_loss', loc_tech) if hasattr(backend_model, 'storage_inter_cluster'): storage = backend_model.storage_inter_cluster final_step = backend_model.datesteps[-1] time_resolution = 24 else: storage = backend_model.storage final_step = backend_model.timesteps[-1] time_resolution = backend_model.timestep_resolution[final_step] return ( storage[loc_tech, final_step] * ((1 - storage_loss) ** time_resolution) == storage_initial )
def balance_storage_inter_cluster_rule(backend_model, loc_tech, datestep): """ When clustering days, to reduce the timeseries length, balance the daily stored energy across all days of the original timeseries. `Ref: DOI 10.1016/j.apenergy.2018.01.023 <https://doi.org/10.1016/j.apenergy.2018.01.023>`_ .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage_{inter\\_cluster}}(loc::tech, datestep) = \\boldsymbol{storage_{inter\\_cluster}}(loc::tech, datestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{24} + \\boldsymbol{storage}(loc::tech, timestep_{final, cluster(datestep))}) \\quad \\forall loc::tech \\in loc::techs_{store}, \\forall datestep \\in datesteps Where :math:`timestep_{final, cluster(datestep_{previous}))}` is the final timestep of the cluster in the clustered timeseries corresponding to the previous day """ model_attrs = backend_model.__calliope_model_data__['attrs'] current_datestep = backend_model.datesteps.order_dict[datestep] if current_datestep == 0 and not model_attrs['run.cyclic_storage']: storage_previous_step = get_param(backend_model, 'storage_initial', loc_tech) storage_intra = 0 else: if current_datestep == 0 and model_attrs['run.cyclic_storage']: previous_step = backend_model.datesteps[-1] else: previous_step = get_previous_timestep(backend_model.datesteps, datestep) storage_loss = get_param(backend_model, 'storage_loss', loc_tech) storage_previous_step = ( ((1 - storage_loss) ** 24) * backend_model.storage_inter_cluster[loc_tech, previous_step] ) final_timestep = ( backend_model.__calliope_model_data__ ['data']['lookup_datestep_last_cluster_timestep'][previous_step] ) storage_intra = backend_model.storage[loc_tech, final_timestep] return ( backend_model.storage_inter_cluster[loc_tech, datestep] == storage_previous_step + storage_intra )
def ramping_constraint(backend_model, loc_tech_carrier, timestep, direction=0): """ Ramping rate constraints. Direction: 0 is up, 1 is down. .. container:: scrolling-wrapper .. math:: \\boldsymbol{max\\_ramping\\_rate}(loc::tech::carrier, timestep) = energy_{ramping}(loc::tech, timestep) \\times energy_{cap}(loc::tech) \\boldsymbol{diff}(loc::tech::carrier, timestep) = (carrier_{prod}(loc::tech::carrier, timestep) + carrier_{con}(loc::tech::carrier, timestep)) / timestep\\_resolution(timestep) - (carrier_{prod}(loc::tech::carrier, timestep-1) + carrier_{con}(loc::tech::carrier, timestep-1)) / timestep\\_resolution(timestep-1) """ # No constraint for first timestep if backend_model.timesteps.order_dict[timestep] == 0: return po.Constraint.NoConstraint else: previous_step = get_previous_timestep(backend_model.timesteps, timestep) time_res = backend_model.timestep_resolution[timestep] time_res_prev = backend_model.timestep_resolution[previous_step] loc_tech = loc_tech_carrier.rsplit('::', 1)[0] # Ramping rate (fraction of installed capacity per hour) ramping_rate = get_param(backend_model, 'energy_ramping', (loc_tech, timestep)) try: prod_this = backend_model.carrier_prod[loc_tech_carrier, timestep] prod_prev = backend_model.carrier_prod[loc_tech_carrier, previous_step] except KeyError: prod_this = 0 prod_prev = 0 try: con_this = backend_model.carrier_con[loc_tech_carrier, timestep] con_prev = backend_model.carrier_con[loc_tech_carrier, previous_step] except KeyError: con_this = 0 con_prev = 0 diff = ( (prod_this + con_this) / time_res - (prod_prev + con_prev) / time_res_prev ) max_ramping_rate = ramping_rate * backend_model.energy_cap[loc_tech] if direction == 0: return diff <= max_ramping_rate else: return -1 * max_ramping_rate <= diff
def _get_investment_cost(capacity_decision_variable, calliope_set): """ Conditionally add investment costs, if the relevant set of technologies exists. Both inputs are strings. """ if loc_tech_is_in(backend_model, loc_tech, calliope_set): _cost = (getattr(backend_model, capacity_decision_variable)[loc_tech] * get_param(backend_model, 'cost_' + capacity_decision_variable, (cost, loc_tech))) return _cost else: return 0
def energy_capacity_min_purchase_constraint_rule(backend_model, loc_tech): """ Set minimum energy capacity decision variable upper bound as a function of binary purchase variable and (if ``equals`` not enforced): .. container:: scrolling-wrapper .. math:: \\frac{\\boldsymbol{energy_{cap}}(loc::tech)}{energy_{cap, scale}(loc::tech)} \\geq energy_{cap, min}(loc::tech) \\times \\boldsymbol{purchased}(loc::tech) \\quad \\forall loc::tech \\in loc::techs """ energy_cap_min = get_param(backend_model, 'energy_cap_min', loc_tech) energy_cap_scale = get_param(backend_model, 'energy_cap_scale', loc_tech) return backend_model.energy_cap[loc_tech] >= ( energy_cap_min * energy_cap_scale * backend_model.purchased[loc_tech] )
def balance_conversion_plus_primary_constraint_rule(backend_model, loc_tech, timestep): """ Balance energy carrier consumption and production for carrier_in and carrier_out .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}} \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{ carrier\\_ratio(loc::tech::carrier, `out')} = -1 * \\sum_{loc::tech::carrier \\in loc::tech::carriers_{in}} ( \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) * carrier\\_ratio(loc::tech::carrier, `in') * \\eta_{energy}(loc::tech, timestep)) \\quad \\forall loc::tech \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] loc_tech_carriers_out = split_comma_list( model_data_dict['lookup_loc_techs_conversion_plus']['out', loc_tech] ) loc_tech_carriers_in = split_comma_list( model_data_dict['lookup_loc_techs_conversion_plus']['in', loc_tech] ) energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) carrier_prod = sum( backend_model.carrier_prod[loc_tech_carrier, timestep] / get_param(backend_model, 'carrier_ratios', ('out', loc_tech_carrier)) for loc_tech_carrier in loc_tech_carriers_out ) carrier_con = sum( backend_model.carrier_con[loc_tech_carrier, timestep] * get_param(backend_model, 'carrier_ratios', ('in', loc_tech_carrier)) for loc_tech_carrier in loc_tech_carriers_in ) return carrier_prod == -1 * carrier_con * energy_eff
def carrier_production_min_milp_constraint_rule(backend_model, loc_tech_carrier, timestep): """ Set minimum carrier production of MILP techs that aren't conversion plus .. container:: scrolling-wrapper .. math:: \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep) \\geq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep) \\times \\boldsymbol{operating\\_units}(loc::tech, timestep) \\times energy_{cap, min use}(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{milp}, \\forall timestep \\in timesteps """ loc_tech = get_loc_tech(loc_tech_carrier) carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep] timestep_resolution = backend_model.timestep_resolution[timestep] min_use = get_param(backend_model, 'energy_cap_min_use', (loc_tech, timestep)) energy_cap = get_param(backend_model, 'energy_cap_per_unit', loc_tech) return carrier_prod >= ( backend_model.operating_units[loc_tech, timestep] * timestep_resolution * energy_cap * min_use )
def resource_area_constraint_rule(backend_model, loc_tech): """ Set upper and lower bounds for resource_area. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{area}}(loc::tech) \\begin{cases} = resource_{area, equals}(loc::tech),& \\text{if } resource_{area, equals}(loc::tech)\\\\ \\leq resource_{area, max}(loc::tech),& \\text{if } resource_{area, max}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall loc::tech \\in loc::techs_{area} and (if ``equals`` not enforced): .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{area}}(loc::tech) \\geq resource_{area, min}(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{area} """ energy_cap_max = get_param(backend_model, 'energy_cap_max', loc_tech) area_per_energy_cap = get_param(backend_model, 'resource_area_per_energy_cap', loc_tech) if po.value(energy_cap_max) == 0 and not po.value(area_per_energy_cap): # If a technology has no energy_cap here, we force resource_area to zero, # so as not to accrue spurious costs return backend_model.resource_area[loc_tech] == 0 else: return get_capacity_constraint(backend_model, 'resource_area', loc_tech)
def storage_capacity_max_purchase_constraint_rule(backend_model, loc_tech): """ Set maximum storage capacity. The first valid case is applied: .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage_{cap}}(loc::tech) \\begin{cases} = storage_{cap, equals}(loc::tech) \\times \\boldsymbol{purchased},& \\text{if } storage_{cap, equals} \\\\ \\leq storage_{cap, max}(loc::tech) \\times \\boldsymbol{purchased},& \\text{if } storage_{cap, max}(loc::tech)\\\\ \\text{unconstrained},& \\text{otherwise} \\end{cases} \\forall loc::tech \\in loc::techs_{purchase, store} """ storage_cap_max = get_param(backend_model, 'storage_cap_max', loc_tech) storage_cap_equals = get_param(backend_model, 'storage_cap_equals', loc_tech) if po.value(storage_cap_equals): return backend_model.storage_cap[loc_tech] == ( storage_cap_equals * backend_model.purchased[loc_tech] ) elif po.value(storage_cap_max): return backend_model.storage_cap[loc_tech] <= ( storage_cap_max * backend_model.purchased[loc_tech] ) else: return po.Constraint.Skip
def resource_area_per_energy_capacity_constraint_rule(backend_model, loc_tech): """ Add equality constraint for resource_area to equal a percentage of energy_cap, for any technologies which have defined resource_area_per_energy_cap .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{area}}(loc::tech) = \\boldsymbol{energy_{cap}}(loc::tech) \\times area\\_per\\_energy\\_cap(loc::tech) \\quad \\forall loc::tech \\in locs::techs_{area} \\text{ if } area\\_per\\_energy\\_cap(loc::tech) """ area_per_energy_cap = get_param(backend_model, 'resource_area_per_energy_cap', loc_tech) return (backend_model.resource_area[loc_tech] == backend_model.energy_cap[loc_tech] * area_per_energy_cap)
def storage_capacity_units_constraint_rule(backend_model, loc_tech): """ Set storage capacity decision variable as a function of purchased units .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage_{cap}}(loc::tech) = \\boldsymbol{units}(loc::tech) \\times storage_{cap, per unit}(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{milp, store} """ return backend_model.storage_cap[loc_tech] == ( backend_model.units[loc_tech] * get_param(backend_model, 'storage_cap_per_unit', loc_tech) )
def energy_capacity_storage_constraint_rule(backend_model, loc_tech): """ Set an additional energy capacity constraint on storage technologies, based on their use of `charge_rate`. .. container:: scrolling-wrapper .. math:: \\boldsymbol{energy_{cap}}(loc::tech) \\leq \\boldsymbol{storage_{cap}}(loc::tech) \\times charge\\_rate(loc::tech) \\quad \\forall loc::tech \\in loc::techs_{store} """ charge_rate = get_param(backend_model, 'charge_rate', loc_tech) return backend_model.energy_cap[loc_tech] <= ( backend_model.storage_cap[loc_tech] * charge_rate )