Source code for calliope.constraints.optional

"""
Copyright (C) 2013-2017 Calliope contributors listed in AUTHORS.
Licensed under the Apache 2.0 License (see LICENSE file).

optional.py
~~~~~~~~~~~

Optionally loaded constraints.

"""

import pyomo.core as po  # pylint: disable=import-error


[docs]def ramping_rate(model): """ Ramping rate constraints. Depends on: node_energy_balance, node_constraints_build """ m = model.m time_res = model.data['_time_res'].to_series() # Constraint rules def _ramping_rule(m, y, x, t, direction): # e_ramping: Ramping rate [fraction of installed capacity per hour] ramping_rate_value = model.get_option(y + '.constraints.e_ramping') if ramping_rate_value is False: # If the technology defines no `e_ramping`, we don't build a # ramping constraint for it! return po.Constraint.NoConstraint else: # No constraint for first timestep # NB: From Pyomo 3.5 to 3.6, order_dict became zero-indexed if m.t.order_dict[t] == 0: return po.Constraint.NoConstraint else: carrier = model.get_option(y + '.carrier') diff = ((m.c_prod[carrier, y, x, t] + m.c_con[carrier, y, x, t]) / time_res.at[t] - (m.c_prod[carrier, y, x, model.prev_t(t)] + m.c_con[carrier, y, x, model.prev_t(t)]) / time_res.at[model.prev_t(t)]) max_ramping_rate = ramping_rate_value * m.e_cap[y, x] if direction == 'up': return diff <= max_ramping_rate else: return -1 * max_ramping_rate <= diff def c_ramping_up_rule(m, y, x, t): return _ramping_rule(m, y, x, t, direction='up') def c_ramping_down_rule(m, y, x, t): return _ramping_rule(m, y, x, t, direction='down') # Constraints m.c_ramping_up = po.Constraint(m.y, m.x, m.t, rule=c_ramping_up_rule) m.c_ramping_down = po.Constraint(m.y, m.x, m.t, rule=c_ramping_down_rule)
[docs]def group_fraction(model): """ Constrain groups of technologies to reach given fractions of e_prod. """ m = model.m def sign_fraction(group, group_type): o = model.config_model sign, fraction = o.group_fraction[group_type].get_key(group) return sign, fraction def group_set(group_type): try: group = model.config_model.group_fraction[group_type].keys() except (TypeError, KeyError): group = [] return po.Set(initialize=group) def techs_to_consider(supply_techs, group_type): # Remove ignored techs if any defined gfc = model.config_model.group_fraction if 'ignored_techs' in gfc and group_type in gfc.ignored_techs: return [i for i in supply_techs if i not in gfc.ignored_techs[group_type]] else: return supply_techs def equalizer(lhs, rhs, sign): if sign == '<=': return lhs <= rhs elif sign == '>=': return lhs >= rhs elif sign == '==': return lhs == rhs else: raise ValueError('Invalid sign: {}'.format(sign)) supply_techs = (model.get_group_members('supply') + model.get_group_members('conversion')) # Sets m.output_group = group_set('output') m.capacity_group = group_set('capacity') m.demand_power_peak_group = group_set('demand_power_peak') # Constraint rules def c_group_fraction_output_rule(m, c, output_group): sign, fraction = sign_fraction(output_group, 'output') techs = techs_to_consider(supply_techs, 'output') rhs = (fraction * sum(m.c_prod[c, y, x, t] for y in techs for x in m.x for t in m.t)) lhs = sum(m.c_prod[c, y, x, t] for y in model.get_group_members(output_group) for x in m.x for t in m.t) return equalizer(lhs, rhs, sign) def c_group_fraction_capacity_rule(m, c, capacity_group): # pylint: disable=unused-argument sign, fraction = sign_fraction(capacity_group, 'capacity') techs = techs_to_consider(supply_techs, 'capacity') rhs = (fraction * sum(m.e_cap[y, x] for y in techs for x in m.x)) lhs = sum(m.e_cap[y, x] for y in model.get_group_members(capacity_group) for x in m.x) return equalizer(lhs, rhs, sign) def c_group_fraction_demand_power_peak_rule(m, c, demand_power_peak_group): sign, fraction = sign_fraction(demand_power_peak_group, 'demand_power_peak') margin = model.config_model.system_margin.get_key(c, default=0) peak_timestep = model.t_max_demand.power y = 'demand_power' # Calculate demand peak taking into account both r_scale and time_res peak = (float(sum(model.m.r[y, x, peak_timestep] * model.get_option(y + '.constraints.r_scale', x=x) for x in model.m.x)) / model.data.time_res_series.at[peak_timestep]) rhs = fraction * (-1 - margin) * peak lhs = sum(m.e_cap[y, x] for y in model.get_group_members(demand_power_peak_group) for x in m.x) return equalizer(lhs, rhs, sign) # Constraints m.c_group_fraction_output = \ po.Constraint(m.c, m.output_group, rule=c_group_fraction_output_rule) m.c_group_fraction_capacity = \ po.Constraint(m.c, m.capacity_group, rule=c_group_fraction_capacity_rule) grp = m.demand_power_peak_group m.c_group_fraction_demand_power_peak = \ po.Constraint(m.c, grp, rule=c_group_fraction_demand_power_peak_rule)
[docs]def max_r_area_per_loc(model): """ ``r_area`` of all technologies requiring physical space cannot exceed the available area of a location. Available area defined for parent locations (in which there are locations defined as being 'within' it) will set the available area limit for the sum of all the family (parent + all desecendants). To define, assign a value to ``available_area`` for a given location, e.g.:: locations: r1: techs: ['csp'] available_area: 100000 To avoid including descendants in area limitation, ``ignore_descendants`` can be specified for the location, in the same way as ``available_area``. """ m = model.m locations = model._locations def _get_descendants(x): """ Returns all locations in the family tree of location x, in one list. """ descendants = [] children = list(locations[locations._within == x].index) if not children: return [] for child in children: descendants += _get_descendants(child) descendants += children return descendants def c_available_r_area_rule(m, x): available_area = model.config_model.get_key( 'locations.{}.available_area'.format(x), default=None) if not available_area: return po.Constraint.Skip if model.config_model.get_key( 'locations.{}.ignore_descendants'.format(x), default=False): descendants = [] else: descendants = _get_descendants(x) all_x = descendants + [x] r_area_sum = sum(m.r_area[y, _x] for y in m.y_r_area for _x in all_x if model.get_option(y + '.constraints.r_area.max', x=_x) is not False) # r_area_sum will be ``0`` if either ``m.y_r_area`` or ``all_x`` are empty if not isinstance(r_area_sum, int): return (available_area >= r_area_sum) m.c_available_r_area = po.Constraint(m.x, rule=c_available_r_area_rule)