def cost_constraint_rule(backend_model, cost, loc_tech): """ Combine investment and time varying costs into one cost per technology .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost}(cost, loc::tech) = \\boldsymbol{cost_{investment}}(cost, loc::tech) + \\sum_{timestep \\in timesteps} \\boldsymbol{cost_{var}}(cost, loc::tech, timestep) """ # FIXME: remove check for operate from constraint files, avoid investment costs more intelligently? if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_investment_cost') and backend_model.mode != 'operate': cost_investment = backend_model.cost_investment[cost, loc_tech] else: cost_investment = 0 if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_om_cost'): cost_var = sum(backend_model.cost_var[cost, loc_tech, timestep] for timestep in backend_model.timesteps) else: cost_var = 0 return ( backend_model.cost[cost, loc_tech] == cost_investment + cost_var )
def cost_constraint_rule(backend_model, cost, loc_tech): """ Combine investment and time varying costs into one cost per technology .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost}(cost, loc::tech) = \\boldsymbol{cost_{investment}}(cost, loc::tech) + \\sum_{timestep \\in timesteps} \\boldsymbol{cost_{var}}(cost, loc::tech, timestep) """ # FIXME: remove check for operate from constraint files, avoid investment costs more intelligently? if loc_tech_is_in( backend_model, loc_tech, 'loc_techs_investment_cost') and backend_model.mode != 'operate': cost_investment = backend_model.cost_investment[cost, loc_tech] else: cost_investment = 0 if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_om_cost'): cost_var = sum(backend_model.cost_var[cost, loc_tech, timestep] for timestep in backend_model.timesteps) else: cost_var = 0 return (backend_model.cost[cost, loc_tech] == cost_investment + cost_var)
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 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 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 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 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 balance_demand_constraint_rule(backend_model, loc_tech, timestep): """ Limit consumption from demand techs to their required resource. .. container:: scrolling-wrapper .. math:: \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) \\geq required\_resource(loc::tech, timestep) \\quad \\forall loc::tech \\in loc::techs_{demand}, \\forall timestep \\in timesteps If :math:`force\_resource(loc::tech)` is set: .. container:: scrolling-wrapper .. math:: \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) = required\_resource(loc::tech, timestep) \\quad \\forall loc::tech \\in loc::techs_{demand}, \\forall timestep \\in timesteps Where: .. container:: scrolling-wrapper .. math:: required\_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:: required\_resource(loc::tech, timestep) = resource(loc::tech, timestep) \\times resource\_scale(loc::tech) \\times \\boldsymbol{resource_{area}}(loc::tech) """ model_data_dict = backend_model.__calliope_model_data__['data'] resource = get_param(backend_model, 'resource', (loc_tech, timestep)) energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) resource_scale = get_param(backend_model, 'resource_scale', loc_tech) force_resource = get_param(backend_model, 'force_resource', loc_tech) loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech] carrier_con = backend_model.carrier_con[loc_tech_carrier, timestep] * energy_eff if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_area'): required_resource = resource * resource_scale * backend_model.resource_area[loc_tech] else: required_resource = resource * resource_scale if po.value(force_resource): return carrier_con == required_resource else: return carrier_con >= required_resource
def energy_cap_constraint_rule(backend_model, constraint_group, what): """ Enforce upper and lower bounds for energy_cap of energy_cap for groups of technologies and locations. .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech \\in given\\_group} energy_{cap}(loc::tech) \\leq energy\\_cap\\_max\\\\ \\sum_{loc::tech \\in given\\_group} energy_{cap}(loc::tech) \\geq energy\\_cap\\_min """ threshold = get_param(backend_model, 'group_energy_cap_{}'.format(what), (constraint_group)) lhs_loc_techs = getattr( backend_model, 'group_constraint_loc_techs_{}'.format(constraint_group)) # Transmission techs only contribute half their capacity in each direction lhs = [] for loc_tech in lhs_loc_techs: if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_transmission'): weight = 0.5 else: weight = 1 lhs.append(weight * backend_model.energy_cap[loc_tech]) rhs = threshold return equalizer(sum(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 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 update_costs_investment_units_milp_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 export_max_constraint_rule(backend_model, carrier, node, tech, timestep): """ Set maximum export. All exporting technologies. .. container:: scrolling-wrapper .. math:: \\boldsymbol{carrier_{export}}(loc::tech::carrier, timestep) \\leq export_{cap}(loc::tech) \\quad \\forall loc::tech::carrier \\in locs::tech::carriers_{export}, \\forall timestep \\in timesteps If the technology is defined by integer units, not a continuous capacity, this constraint becomes: .. container:: scrolling-wrapper .. math:: \\boldsymbol{carrier_{export}}(loc::tech::carrier, timestep) \\leq export_{cap}(loc::tech) \\times \\boldsymbol{operating_{units}}(loc::tech, timestep) """ if loc_tech_is_in(backend_model, (node, tech), "operating_units_index"): operating_units = backend_model.operating_units[node, tech, timestep] else: operating_units = 1 export_max = get_param(backend_model, "export_max", (node, tech)) return (backend_model.carrier_export[carrier, node, tech, timestep] <= export_max * operating_units)
def cost_expression_rule(backend_model, cost, node, tech): """ Combine investment and time varying costs into one cost per technology. .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost}(cost, loc::tech) = \\boldsymbol{cost_{investment}}(cost, loc::tech) + \\sum_{timestep \\in timesteps} \\boldsymbol{cost_{var}}(cost, loc::tech, timestep) """ if loc_tech_is_in(backend_model, (cost, node, tech), "cost_investment_index"): cost_investment = backend_model.cost_investment[cost, node, tech] else: cost_investment = 0 if hasattr(backend_model, "cost_var"): cost_var = po.quicksum( backend_model.cost_var[cost, node, tech, timestep] for timestep in backend_model.timesteps if [cost, node, tech, timestep] in backend_model.cost_var._index) else: cost_var = 0 return cost_investment + cost_var
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 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 cost_constraint_rule(backend_model, cost, loc_tech): """ Combine investment and time varying costs into one cost per technology """ # FIXME: remove check for operate from constraint files, avoid investment costs more intelligently? if loc_tech_is_in( backend_model, loc_tech, 'loc_techs_investment_cost') and backend_model.mode != 'operate': cost_investment = backend_model.cost_investment[cost, loc_tech] else: cost_investment = 0 if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_om_cost'): cost_var = sum(backend_model.cost_var[cost, loc_tech, timestep] for timestep in backend_model.timesteps) else: cost_var = 0 return (backend_model.cost[cost, loc_tech] == cost_investment + cost_var)
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 _get_investment_cost(capacity_decision_variable): """ Conditionally add investment costs, if the relevant set of technologies exists. Both inputs are strings. """ if loc_tech_is_in(backend_model, (node, tech), capacity_decision_variable + "_index"): _cost = getattr( backend_model, capacity_decision_variable)[node, tech] * get_param( backend_model, "cost_" + capacity_decision_variable, (cost, node, tech)) return _cost else: return 0
def energy_cap_constraint_rule(backend_model, constraint_group, what): """ Enforce upper and lower bounds for energy_cap of energy_cap for groups of technologies and locations. .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech \\in given\\_group} energy_{cap}(loc::tech) \\leq energy\\_cap\\_max\\\\ \\sum_{loc::tech \\in given\\_group} energy_{cap}(loc::tech) \\geq energy\\_cap\\_min """ model_data_dict = backend_model.__calliope_model_data['data'] threshold = model_data_dict['group_energy_cap_{}'.format(what)][( constraint_group)] if np.isnan(threshold): return return_noconstraint('energy_cap', constraint_group) else: lhs_loc_techs = getattr( backend_model, 'group_constraint_loc_techs_{}'.format(constraint_group)) # Transmission techs only contribute half their capacity in each direction lhs = None for loc_tech in lhs_loc_techs: if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_transmission'): weight = 0.5 else: weight = 1 if lhs is not None: lhs += weight * backend_model.energy_cap[loc_tech] else: lhs = weight * backend_model.energy_cap[loc_tech] rhs = threshold return equalizer(lhs, rhs, what)
def storage_cap_constraint_rule(backend_model, constraint_group, what): """ Enforce upper and lower bounds for storage_cap of storage_cap for groups of technologies and locations. .. container:: scrolling-wrapper .. math:: \\sum_{loc::tech \\in given\\_group} storage_{cap}(loc::tech) \\leq storage\\_cap\\_max\\\\ \\sum_{loc::tech \\in given\\_group} storage_{cap}(loc::tech) \\geq storage\\_cap\\_min """ threshold = get_param( backend_model, "group_storage_cap_{}".format(what), (constraint_group) ) if check_value(threshold): return return_noconstraint("storage_cap", constraint_group) else: lhs_loc_techs = getattr( backend_model, "group_constraint_loc_techs_{}".format(constraint_group) ) # Transmission techs only contribute half their capacity in each direction lhs = [] for loc_tech in lhs_loc_techs: if loc_tech_is_in(backend_model, loc_tech, "loc_techs_transmission"): weight = 0.5 else: weight = 1 lhs.append(weight * backend_model.storage_cap[loc_tech]) rhs = threshold return equalizer(sum(lhs), rhs, what)
def balance_supply_plus_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and resource consumption of supply_plus technologies alongside any use of resource storage. .. _system_balance_constraint: .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{timestep\\_resolution(timestep)} + \\boldsymbol{resource_{con}}(loc::tech, timestep) \\times \\eta_{resource}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps If *no* storage is defined for the technology, this reduces to: .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{con}}(loc::tech, timestep) \\times \\eta_{resource}(loc::tech, timestep) = \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] model_attrs = backend_model.__calliope_model_data__['attrs'] resource_eff = get_param(backend_model, 'resource_eff', (loc_tech, timestep)) energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) parasitic_eff = get_param(backend_model, 'parasitic_eff', (loc_tech, timestep)) total_eff = energy_eff * parasitic_eff if po.value(total_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] / total_eff # A) Case where no storage allowed if not loc_tech_is_in(backend_model, loc_tech, 'loc_techs_store'): return backend_model.resource_con[loc_tech, timestep] * resource_eff == carrier_prod # B) Case where storage is allowed else: resource = backend_model.resource_con[loc_tech, timestep] * resource_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 + resource - carrier_prod )
def cost_investment_expression_rule(backend_model, cost, node, tech): """ Calculate costs from capacity decision variables. Transmission technologies "exist" at two locations, so their cost is divided by 2. .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{investment}}(cost, loc::tech) = cost_{fractional\\_om}(cost, loc::tech) + cost_{fixed\\_om}(cost, loc::tech) + cost_{cap}(cost, loc::tech) cost_{cap}(cost, loc::tech) = depreciation\\_rate * ts\\_weight * (cost_{energy\\_cap}(cost, loc::tech) \\times \\boldsymbol{energy_{cap}}(loc::tech) + cost_{storage\\_cap}(cost, loc::tech) \\times \\boldsymbol{storage_{cap}}(loc::tech) + cost_{resource\\_cap}(cost, loc::tech) \\times \\boldsymbol{resource_{cap}}(loc::tech) + cost_{resource\\_area}(cost, loc::tech)) \\times \\boldsymbol{resource_{area}}(loc::tech) depreciation\\_rate = \\begin{cases} = 1 / plant\\_life,& \\text{if } interest\\_rate = 0\\\\ = \\frac{interest\\_rate \\times (1 + interest\\_rate)^{plant\\_life}}{(1 + interest\\_rate)^{plant\\_life} - 1},& \\text{if } interest\\_rate \\gt 0\\\\ \\end{cases} ts\\_weight = \\sum_{timestep \\in timesteps} (time\\_res(timestep) \\times weight(timestep)) \\times \\frac{1}{8760} """ def _get_investment_cost(capacity_decision_variable): """ Conditionally add investment costs, if the relevant set of technologies exists. Both inputs are strings. """ if loc_tech_is_in(backend_model, (node, tech), capacity_decision_variable + "_index"): _cost = getattr( backend_model, capacity_decision_variable)[node, tech] * get_param( backend_model, "cost_" + capacity_decision_variable, (cost, node, tech)) return _cost else: return 0 cost_energy_cap = backend_model.energy_cap[node, tech] * get_param( backend_model, "cost_energy_cap", (cost, node, tech)) cost_storage_cap = _get_investment_cost("storage_cap") cost_resource_cap = _get_investment_cost("resource_cap") cost_resource_area = _get_investment_cost("resource_area") cost_om_annual_investment_fraction = get_param( backend_model, "cost_om_annual_investment_fraction", (cost, node, tech)) cost_om_annual = get_param(backend_model, "cost_om_annual", (cost, node, tech)) if loc_tech_is_in(backend_model, (node, tech), "units_index"): cost_of_purchase = (get_param(backend_model, "cost_purchase", (cost, node, tech)) * backend_model.units[node, tech]) elif loc_tech_is_in(backend_model, (node, tech), "purchased_index"): cost_of_purchase = (get_param(backend_model, "cost_purchase", (cost, node, tech)) * backend_model.purchased[node, tech]) else: cost_of_purchase = 0 ts_weight = get_timestep_weight(backend_model) depreciation_rate = get_param(backend_model, "cost_depreciation_rate", (cost, node, tech)) cost_cap = (depreciation_rate * ts_weight * (cost_energy_cap + cost_storage_cap + cost_resource_cap + cost_resource_area + cost_of_purchase)) # Transmission technologies exist at two locations, thus their cost is divided by 2 if backend_model.inheritance[tech].value.endswith("transmission"): cost_cap = cost_cap / 2 cost_fractional_om = cost_om_annual_investment_fraction * cost_cap cost_fixed_om = cost_om_annual * backend_model.energy_cap[node, tech] * ts_weight return cost_fractional_om + cost_fixed_om + cost_cap
def balance_supply_plus_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and resource consumption of supply_plus technologies alongside any use of resource storage. .. _system_balance_constraint: .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{timestep\\_resolution(timestep)} + \\boldsymbol{resource_{con}}(loc::tech, timestep) \\times \\eta_{resource}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps If *no* storage is defined for the technology, this reduces to: .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{con}}(loc::tech, timestep) \\times \\eta_{resource}(loc::tech, timestep) = \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data["data"] run_config = backend_model.__calliope_run_config resource_eff = get_param(backend_model, "resource_eff", (loc_tech, timestep)) energy_eff = get_param(backend_model, "energy_eff", (loc_tech, timestep)) parasitic_eff = get_param(backend_model, "parasitic_eff", (loc_tech, timestep)) total_eff = energy_eff * parasitic_eff if po.value(total_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] / total_eff) # A) Case where no storage allowed if not loc_tech_is_in(backend_model, loc_tech, "loc_techs_store"): return (backend_model.resource_con[loc_tech, timestep] * resource_eff == carrier_prod) # B) Case where storage is allowed else: resource = backend_model.resource_con[loc_tech, timestep] * resource_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 + resource - carrier_prod)
def cost_investment_constraint_rule(backend_model, cost, loc_tech): """ Calculate costs from capacity decision variables .. container:: scrolling-wrapper .. math:: """ model_data_dict = backend_model.__calliope_model_data__ 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 cost_energy_cap = (backend_model.energy_cap[loc_tech] * get_param(backend_model, 'cost_energy_cap', (cost, loc_tech))) cost_storage_cap = _get_investment_cost('storage_cap', 'loc_techs_store') cost_resource_cap = _get_investment_cost('resource_cap', 'loc_techs_supply_plus') cost_resource_area = _get_investment_cost('resource_area', 'loc_techs_area') cost_om_annual_investment_fraction = get_param( backend_model, 'cost_om_annual_investment_fraction', (cost, loc_tech)) cost_om_annual = get_param(backend_model, 'cost_om_annual', (cost, loc_tech)) ts_weight = get_timestep_weight(backend_model) depreciation_rate = model_data_dict['data']['cost_depreciation_rate'].get( (cost, loc_tech), 0) cost_con = (depreciation_rate * ts_weight * (cost_energy_cap + cost_storage_cap + cost_resource_cap + cost_resource_area)) # Transmission technologies exist at two locations, thus their cost is divided by 2 if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_transmission'): cost_con = cost_con / 2 cost_fractional_om = cost_om_annual_investment_fraction * cost_con cost_fixed_om = cost_om_annual * backend_model.energy_cap[ loc_tech] * ts_weight backend_model.cost_investment_rhs[cost, loc_tech].expr = (cost_fractional_om + cost_fixed_om + cost_con) return (backend_model.cost_investment[cost, loc_tech] == backend_model.cost_investment_rhs[cost, loc_tech])
def cost_var_expression_rule(backend_model, cost, node, 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 """ weight = backend_model.timestep_weights[timestep] all_costs = [] def _sum(var_name, carriers=backend_model.carriers): return po.quicksum( getattr(backend_model, var_name)[carrier, node, tech, timestep] for carrier in carriers if [carrier, node, tech, timestep] in getattr( backend_model, f"{var_name}_index")) cost_om_prod = get_param(backend_model, "cost_om_prod", (cost, node, tech, timestep)) if backend_model.inheritance[tech].value.endswith("conversion_plus"): carriers = [backend_model.primary_carrier_out[:, tech].index()[0][0]] all_costs.append(cost_om_prod * _sum("carrier_prod", carriers=carriers)) else: all_costs.append(cost_om_prod * _sum("carrier_prod")) cost_om_con = get_param(backend_model, "cost_om_con", (cost, node, tech, timestep)) if cost_om_con: if loc_tech_is_in(backend_model, (node, tech), "resource_con_index"): all_costs.append(cost_om_con * backend_model.resource_con[node, tech, timestep]) elif backend_model.inheritance[tech].value.endswith("supply"): energy_eff = get_param(backend_model, "energy_eff", (node, tech, timestep)) # in case energy_eff is zero, to avoid an infinite value if po.value(energy_eff) > 0: all_costs.append(cost_om_con * (_sum("carrier_prod") / energy_eff)) elif backend_model.inheritance[tech].value.endswith("conversion_plus"): carriers = [ backend_model.primary_carrier_in[:, tech].index()[0][0] ] all_costs.append(cost_om_con * (-1) * _sum("carrier_con", carriers=carriers)) else: all_costs.append(cost_om_con * (-1) * _sum("carrier_con")) if hasattr(backend_model, "export_carrier"): export_carrier = backend_model.export_carrier[:, node, tech].index() else: export_carrier = [] if len(export_carrier) > 0: all_costs.append( get_param(backend_model, "cost_export", (cost, node, tech, timestep)) * backend_model.carrier_export[export_carrier[0][0], node, tech, timestep]) return po.quicksum(all_costs) * weight
def cost_investment_constraint_rule(backend_model, cost, loc_tech): """ Calculate costs from capacity decision variables. Transmission technologies "exist" at two locations, so their cost is divided by 2. .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{investment}}(cost, loc::tech) = cost_{fractional\\_om}(cost, loc::tech) + cost_{fixed\\_om}(cost, loc::tech) + cost_{con}(cost, loc::tech) cost_{con}(cost, loc::tech) = depreciation\\_rate * ts\\_weight * (cost_{energy\\_cap}(cost, loc::tech) \\times \\boldsymbol{energy_{cap}}(loc::tech) + cost_{storage\\_cap}(cost, loc::tech) \\times \\boldsymbol{storage_{cap}}(loc::tech) + cost_{resource\\_cap}(cost, loc::tech) \\times \\boldsymbol{resource_{cap}}(loc::tech) + cost_{resource\\_area}(cost, loc::tech)) \\times \\boldsymbol{resource_{area}}(loc::tech) depreciation\\_rate = \\begin{cases} = 1 / plant\\_life,& \\text{if } interest\\_rate = 0\\\\ = \\frac{interest\\_rate \\times (1 + interest\\_rate)^{plant\\_life}}{(1 + interest\\_rate)^{plant\\_life} - 1},& \\text{if } interest\\_rate \\gt 0\\\\ \\end{cases} ts\\_weight = \\sum_{timestep \\in timesteps} (time\\_res(timestep) \\times weight(timestep)) \\times \\frac{1}{8760} """ model_data_dict = backend_model.__calliope_model_data__ 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 cost_energy_cap = (backend_model.energy_cap[loc_tech] * get_param(backend_model, 'cost_energy_cap', (cost, loc_tech))) cost_storage_cap = _get_investment_cost('storage_cap', 'loc_techs_store') cost_resource_cap = _get_investment_cost('resource_cap', 'loc_techs_supply_plus') cost_resource_area = _get_investment_cost('resource_area', 'loc_techs_area') cost_om_annual_investment_fraction = get_param( backend_model, 'cost_om_annual_investment_fraction', (cost, loc_tech)) cost_om_annual = get_param(backend_model, 'cost_om_annual', (cost, loc_tech)) ts_weight = get_timestep_weight(backend_model) depreciation_rate = model_data_dict['data']['cost_depreciation_rate'].get( (cost, loc_tech), 0) cost_con = (depreciation_rate * ts_weight * (cost_energy_cap + cost_storage_cap + cost_resource_cap + cost_resource_area)) # Transmission technologies exist at two locations, thus their cost is divided by 2 if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_transmission'): cost_con = cost_con / 2 cost_fractional_om = cost_om_annual_investment_fraction * cost_con cost_fixed_om = cost_om_annual * backend_model.energy_cap[ loc_tech] * ts_weight backend_model.cost_investment_rhs[cost, loc_tech].expr = (cost_fractional_om + cost_fixed_om + cost_con) return (backend_model.cost_investment[cost, loc_tech] == backend_model.cost_investment_rhs[cost, loc_tech])
def balance_supply_plus_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and resource consumption of supply_plus technologies alongside any use of resource storage. .. _system_balance_constraint: .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\_loss(loc::tech, timestep))^{timestep\_resolution(timestep)} + \\boldsymbol{resource_{con}}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps If *no* storage is defined for the technology, this reduces to: .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{con}}(loc::tech, timestep) = \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) parasitic_eff = get_param(backend_model, 'parasitic_eff', (loc_tech, timestep)) total_eff = energy_eff * parasitic_eff if po.value(total_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] / total_eff # A) Case where no storage allowed if not loc_tech_is_in(backend_model, loc_tech, 'loc_techs_store'): return backend_model.resource_con[loc_tech, timestep] == carrier_prod # B) Case where storage is allowed else: resource = backend_model.resource_con[loc_tech, timestep] if backend_model.timesteps.order_dict[timestep] == 0: storage_previous_step = get_param(backend_model, 'storage_initial', loc_tech) else: storage_loss = get_param(backend_model, 'storage_loss', loc_tech) previous_step = get_previous_timestep(backend_model, timestep) 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 + resource - carrier_prod )
def cost_investment_constraint_rule(backend_model, cost, loc_tech): """ Calculate costs from capacity decision variables. Transmission technologies "exist" at two locations, so their cost is divided by 2. .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost_{investment}}(cost, loc::tech) = cost_{fractional\\_om}(cost, loc::tech) + cost_{fixed\\_om}(cost, loc::tech) + cost_{con}(cost, loc::tech) cost_{con}(cost, loc::tech) = depreciation\\_rate * ts\\_weight * (cost_{energy\\_cap}(cost, loc::tech) \\times \\boldsymbol{energy_{cap}}(loc::tech) + cost_{storage\\_cap}(cost, loc::tech) \\times \\boldsymbol{storage_{cap}}(loc::tech) + cost_{resource\\_cap}(cost, loc::tech) \\times \\boldsymbol{resource_{cap}}(loc::tech) + cost_{resource\\_area}(cost, loc::tech)) \\times \\boldsymbol{resource_{area}}(loc::tech) depreciation\\_rate = \\begin{cases} = 1 / plant\\_life,& \\text{if } interest\\_rate = 0\\\\ = \\frac{interest\\_rate \\times (1 + interest\\_rate)^{plant\\_life}}{(1 + interest\\_rate)^{plant\\_life} - 1},& \\text{if } interest\\_rate \\gt 0\\\\ \\end{cases} ts\\_weight = \\sum_{timestep \\in timesteps} (time\\_res(timestep) \\times weight(timestep)) \\times \\frac{1}{8760} """ model_data_dict = backend_model.__calliope_model_data__ 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 cost_energy_cap = (backend_model.energy_cap[loc_tech] * get_param(backend_model, 'cost_energy_cap', (cost, loc_tech))) cost_storage_cap = _get_investment_cost('storage_cap', 'loc_techs_store') cost_resource_cap = _get_investment_cost('resource_cap', 'loc_techs_supply_plus') cost_resource_area = _get_investment_cost('resource_area', 'loc_techs_area') cost_om_annual_investment_fraction = get_param(backend_model, 'cost_om_annual_investment_fraction', (cost, loc_tech)) cost_om_annual = get_param(backend_model, 'cost_om_annual', (cost, loc_tech)) ts_weight = get_timestep_weight(backend_model) depreciation_rate = model_data_dict['data']['cost_depreciation_rate'].get((cost, loc_tech), 0) cost_con = ( depreciation_rate * ts_weight * (cost_energy_cap + cost_storage_cap + cost_resource_cap + cost_resource_area) ) # Transmission technologies exist at two locations, thus their cost is divided by 2 if loc_tech_is_in(backend_model, loc_tech, 'loc_techs_transmission'): cost_con = cost_con / 2 cost_fractional_om = cost_om_annual_investment_fraction * cost_con cost_fixed_om = cost_om_annual * backend_model.energy_cap[loc_tech] * ts_weight backend_model.cost_investment_rhs[cost, loc_tech].expr = ( cost_fractional_om + cost_fixed_om + cost_con ) return ( backend_model.cost_investment[cost, loc_tech] == backend_model.cost_investment_rhs[cost, loc_tech] )
def balance_supply_constraint_rule(backend_model, loc_tech, timestep): """ Limit production from supply techs to their available resource .. container:: scrolling-wrapper .. math:: min\_use(loc::tech) \\times available\_resource(loc::tech, timestep) \\leq \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep)} \\geq 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:: \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(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 \\boldsymbol{resource_{area}}(loc::tech) """ model_data_dict = backend_model.__calliope_model_data__['data'] resource = get_param(backend_model, 'resource', (loc_tech, timestep)) energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) resource_scale = get_param(backend_model, 'resource_scale', loc_tech) force_resource = get_param(backend_model, 'force_resource', loc_tech) loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech] min_use = get_param(backend_model, 'resource_min_use', (loc_tech, timestep)) if po.value(energy_eff) == 0: return backend_model.carrier_prod[loc_tech_carrier, timestep] == 0 else: carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff 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 carrier_prod == available_resource elif min_use: return min_use * available_resource <= carrier_prod <= available_resource else: return carrier_prod <= available_resource