Source code for calliope.backend.pyomo.constraints.group
"""
Copyright (C) 2013-2019 Calliope contributors listed in AUTHORS.
Licensed under the Apache 2.0 License (see LICENSE file).
group.py
~~~~~~~~
Group constraints.
"""
import logging
import numpy as np
import pyomo.core as po # pylint: disable=import-error
from calliope.backend.pyomo.util import loc_tech_is_in, get_param
logger = logging.getLogger(__name__)
ORDER = 20 # order in which to invoke constraints relative to other constraint files
def return_noconstraint(*args):
logger.debug('group constraint returned NoConstraint: {}'.format(','.join(args)))
return po.Constraint.NoConstraint
def load_constraints(backend_model):
model_data_dict = backend_model.__calliope_model_data['data']
for sense in ['min', 'max', 'equals']:
if 'group_energy_cap_share_{}'.format(sense) in model_data_dict:
setattr(
backend_model, 'group_energy_cap_share_{}_constraint'.format(sense),
po.Constraint(
getattr(backend_model, 'group_names_energy_cap_share_{}'.format(sense)),
[sense], rule=energy_cap_share_constraint_rule
)
)
if 'group_energy_cap_{}'.format(sense) in model_data_dict:
setattr(
backend_model, 'group_energy_cap_{}_constraint'.format(sense),
po.Constraint(
getattr(backend_model, 'group_names_energy_cap_{}'.format(sense)),
[sense], rule=energy_cap_constraint_rule
)
)
if 'group_resource_area_{}'.format(sense) in model_data_dict:
setattr(
backend_model, 'group_resource_area_{}_constraint'.format(sense),
po.Constraint(
getattr(backend_model, 'group_names_resource_area_{}'.format(sense)),
[sense], rule=resource_area_constraint_rule
)
)
if 'group_carrier_prod_{}'.format(sense) in model_data_dict:
setattr(
backend_model, 'group_carrier_prod_{}_constraint'.format(sense),
po.Constraint(
getattr(backend_model, 'group_names_carrier_prod_{}'.format(sense)),
backend_model.carriers, [sense], rule=carrier_prod_constraint_rule
)
)
if 'group_demand_share_{}'.format(sense) in model_data_dict:
setattr(
backend_model, 'group_demand_share_{}_constraint'.format(sense),
po.Constraint(getattr(backend_model, 'group_names_demand_share_{}'.format(sense)),
backend_model.carriers, [sense], rule=demand_share_constraint_rule)
)
if 'group_demand_share_per_timestep_{}'.format(sense) in model_data_dict:
setattr(
backend_model, 'group_demand_share_per_timestep_{}_constraint'.format(sense),
po.Constraint(
getattr(backend_model, 'group_names_demand_share_per_timestep_{}'.format(sense)),
backend_model.carriers, backend_model.timesteps,
[sense], rule=demand_share_per_timestep_constraint_rule
)
)
if 'group_carrier_prod_share_{}'.format(sense) in model_data_dict:
setattr(
backend_model, 'group_carrier_prod_share_{}_constraint'.format(sense),
po.Constraint(
getattr(backend_model, 'group_names_carrier_prod_share_{}'.format(sense)),
backend_model.carriers, [sense], rule=carrier_prod_share_constraint_rule
)
)
if 'group_carrier_prod_share_per_timestep_{}'.format(sense) in model_data_dict:
setattr(
backend_model, 'group_carrier_prod_share_per_timestep_{}_constraint'.format(sense),
po.Constraint(
getattr(backend_model, 'group_names_carrier_prod_share_per_timestep_{}'.format(sense)),
backend_model.carriers, backend_model.timesteps,
[sense], rule=carrier_prod_share_per_timestep_constraint_rule
)
)
if 'group_net_import_share_{}'.format(sense) in model_data_dict:
setattr(
backend_model, 'group_net_import_share_{}_constraint'.format(sense),
po.Constraint(
getattr(backend_model, 'group_names_net_import_share_{}'.format(sense)),
backend_model.carriers, [sense], rule=net_import_share_constraint_rule
)
)
if 'group_cost_{}'.format(sense) in model_data_dict:
setattr(
backend_model, 'group_cost_{}_constraint'.format(sense),
po.Constraint(
getattr(backend_model, 'group_names_cost_{}'.format(sense)),
backend_model.costs, [sense], rule=cost_cap_constraint_rule
)
)
if 'group_cost_var_{}'.format(sense) in model_data_dict:
setattr(
backend_model, 'group_cost_var_{}_constraint'.format(sense),
po.Constraint(getattr(backend_model, 'group_names_cost_var_{}'.format(sense)),
backend_model.costs, [sense], rule=cost_var_cap_constraint_rule)
)
if 'group_cost_investment_{}'.format(sense) in model_data_dict:
setattr(
backend_model, 'group_cost_investment_{}_constraint'.format(sense),
po.Constraint(
getattr(backend_model, 'group_names_cost_investment_{}'.format(sense)),
backend_model.costs, [sense], rule=cost_investment_cap_constraint_rule
)
)
if 'group_demand_share_per_timestep_decision' in model_data_dict:
backend_model.group_demand_share_per_timestep_decision_main_constraint = po.Constraint(
backend_model.group_names_demand_share_per_timestep_decision,
backend_model.carriers,
backend_model.techs,
backend_model.timesteps,
rule=demand_share_per_timestep_decision_main_constraint_rule
)
backend_model.group_demand_share_per_timestep_decision_sum_constraint = po.Constraint(
backend_model.group_names_demand_share_per_timestep_decision,
backend_model.carriers,
rule=demand_share_per_timestep_decision_sum_constraint_rule
)
def equalizer(lhs, rhs, sign):
if sign == 'max':
return lhs <= rhs
elif sign == 'min':
return lhs >= rhs
elif sign == 'equals':
return lhs == rhs
else:
raise ValueError('Invalid sign: {}'.format(sign))
def get_carrier_prod_share_lhs_and_rhs_loc_techs(backend_model, group_name):
lhs_loc_techs = getattr(
backend_model,
'group_constraint_loc_techs_{}'.format(group_name)
)
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
]
return (lhs_loc_techs, rhs_loc_techs)
[docs]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 limit is None:
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)
[docs]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))
if threshold is None:
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 = []
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)
[docs]def cost_cap_constraint_rule(backend_model, group_name, cost, what):
"""
Limit cost for a specific cost class to a certain value,
i.e. Ɛ-constrained costs,
for groups of technologies and locations.
.. container:: scrolling-wrapper
.. math::
\\sum{loc::tech \\in loc\\_techs_{group\\_name}, timestep \\in timesteps}
\\boldsymbol{cost}(cost, loc::tech, timestep)
\\begin{cases}
\\leq cost\\_max(cost)
\\geq cost\\_min(cost)
= cost\\_equals(cost)
\\end{cases}
"""
cost_cap = get_param(backend_model, 'group_cost_{}'.format(what), (cost, group_name))
if cost_cap is None:
return return_noconstraint('cost_cap', group_name)
else:
loc_techs = [i for i in getattr(
backend_model,
'group_constraint_loc_techs_{}'.format(group_name)
) if i in backend_model.loc_techs_cost]
sum_cost = sum(backend_model.cost[cost, loc_tech] for loc_tech in loc_techs)
return equalizer(sum_cost, cost_cap, what)
[docs]def cost_investment_cap_constraint_rule(backend_model, group_name, cost, what):
"""
Limit investment costs specific to a cost class to a
certain value, i.e. Ɛ-constrained costs,
for groups of technologies and locations.
.. container:: scrolling-wrapper
.. math::
\\sum{loc::tech \\in loc\\_techs_{group\\_name}, timestep \\in timesteps}
\\boldsymbol{cost\\_{investment}}(cost, loc::tech, timestep)
\\begin{cases}
\\leq cost\\_investment\\_max(cost)
\\geq cost\\_investment\\_min(cost)
= cost\\_investment\\_equals(cost)
\\end{cases}
"""
cost_cap = get_param(backend_model, 'group_cost_investment_{}'.format(what), (cost, group_name))
if cost_cap is None:
return return_noconstraint('cost_investment_cap', group_name)
else:
loc_techs = [i for i in getattr(
backend_model,
'group_constraint_loc_techs_{}'.format(group_name)
) if i in backend_model.loc_techs_investment_cost]
sum_cost = sum(backend_model.cost_investment[cost, loc_tech] for loc_tech in loc_techs)
return equalizer(sum_cost, cost_cap, what)
[docs]def cost_var_cap_constraint_rule(backend_model, group_name, cost, what):
"""
Limit variable costs specific to a cost class
to a certain value, i.e. Ɛ-constrained costs,
for groups of technologies and locations.
.. container:: scrolling-wrapper
.. math::
\\sum{loc::tech \\in loc\\_techs_{group\\_name}, timestep \\in timesteps}
\\boldsymbol{cost\\_{var}}(cost, loc::tech, timestep)
\\begin{cases}
\\leq cost\\_var\\_max(cost)
\\geq cost\\_var\\_min(cost)
= cost\\_var\\_equals(cost)
\\end{cases}
"""
cost_cap = get_param(backend_model, 'group_cost_var_{}'.format(what), (cost, group_name))
if cost_cap is None:
return return_noconstraint('cost_var_cap', group_name)
else:
loc_techs = [i for i in getattr(
backend_model,
'group_constraint_loc_techs_{}'.format(group_name)
) if i in backend_model.loc_techs_om_cost]
sum_cost = sum(
backend_model.cost_var[cost, loc_tech, timestep]
for loc_tech in loc_techs for timestep in backend_model.timesteps
)
return equalizer(sum_cost, cost_cap, what)
[docs]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 threshold is None:
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)