Пример #1
0
def node_parasitics(model):
    """
    Additional variables and constraints for plants with internal parasitics.

    Defines variables:

    * ec_prod: storage -> carrier after parasitics (+ production)
    * ec_con: storage <- carrier after parasitics (- consumption)

    """
    m = model.m

    # Variables
    m.ec_prod = po.Var(m.c, m.y_p, m.x, m.t, within=po.NonNegativeReals)
    m.ec_con = po.Var(m.c, m.y_p, m.x, m.t, within=po.NegativeReals)

    # Constraint rules
    def c_ec_prod_rule(m, c, y, x, t):
        return (m.ec_prod[c, y, x, t] == m.es_prod[c, y, x, t] *
                model.get_option(y + '.constraints.c_eff', x=x))

    def c_ec_con_rule(m, c, y, x, t):
        if y in m.y_trans or y in m.y_conv:
            # Ensure that transmission and conversion technologies
            # do not double count c_eff
            c_eff = 1.0
        else:
            c_eff = model.get_option(y + '.constraints.c_eff', x=x)
        return (m.ec_con[c, y, x, t] == m.es_con[c, y, x, t] / c_eff)

    # Constraints
    m.c_ec_prod = po.Constraint(m.c, m.y_p, m.x, m.t, rule=c_ec_prod_rule)
    m.c_ec_con = po.Constraint(m.c, m.y_p, m.x, m.t, rule=c_ec_con_rule)
Пример #2
0
def load_constraints(backend_model):
    sets = backend_model.__calliope_model_data__['sets']

    if 'loc_carriers_update_system_balance_constraint' in sets:
        for loc_carrier, timestep in (
            backend_model.loc_carriers_update_system_balance_constraint
            * backend_model.timesteps):
            update_system_balance_constraint(backend_model, loc_carrier, timestep)

    if 'loc_tech_carriers_export_balance_constraint' in sets:
        backend_model.export_balance_constraint = po.Constraint(
            backend_model.loc_tech_carriers_export_balance_constraint,
            backend_model.timesteps,
            rule=export_balance_constraint_rule
        )

    if 'loc_techs_update_costs_var_constraint' in sets:
        for cost, loc_tech, timestep in (backend_model.costs
            * backend_model.loc_techs_update_costs_var_constraint
            * backend_model.timesteps):
            update_costs_var_constraint(backend_model, cost, loc_tech, timestep)

    if 'loc_tech_carriers_export_max_constraint' in sets:
        backend_model.export_max_constraint = po.Constraint(
            backend_model.loc_tech_carriers_export_max_constraint, backend_model.timesteps,
            rule=export_max_constraint_rule
        )
Пример #3
0
def model_constraints(model):
    m = model.m

    @utils.memoize
    def get_parents(level):
        return list(model._locations[model._locations._level == level].index)

    @utils.memoize
    def get_children(parent, childless_only=True):
        """
        If childless_only is True, only children that have no children
        themselves are returned.

        """
        locations = model._locations
        children = list(locations[locations._within == parent].index)
        if childless_only:  # FIXME childless_only param needs tests
            children = [i for i in children if len(get_children(i)) == 0]
        return children

    # Constraint rules
    def c_system_balance_rule(m, c, x, t):
        # Balacing takes place at top-most (level 0) locations, as well
        # as within any lower-level locations that contain children
        if (model._locations.at[x, '_level'] == 0 or len(get_children(x)) > 0):
            family = get_children(x) + [x]  # list of children + parent
            balance = (sum(m.c_prod[c, y, xs, t] for xs in family
                           for y in m.y) + sum(m.c_con[c, y, xs, t]
                                               for xs in family for y in m.y) -
                       sum(m.export[y, xs, t] for xs in family
                           for y in m.y_export if xs in m.x_export
                           and c == model.get_option(y + '.export', x=xs)))

            return balance == 0
        else:
            return po.Constraint.NoConstraint

    # Constraints
    m.c_system_balance = po.Constraint(m.c,
                                       m.x,
                                       m.t,
                                       rule=c_system_balance_rule)

    def c_export_balance_rule(m, c, y, x, t):
        # Ensuring no technology can 'pass' its export capability to another
        # technology with the same carrier_out,
        # by limiting its export to the capacity of its production
        if c == model.get_option(y + '.export', x=x):
            return m.c_prod[c, y, x, t] >= m.export[y, x, t]
        else:
            return po.Constraint.Skip

    # Constraints
    m.c_export_balance = po.Constraint(m.c,
                                       m.y_export,
                                       m.x_export,
                                       m.t,
                                       rule=c_export_balance_rule)
Пример #4
0
def load_constraints(backend_model):
    sets = backend_model.__calliope_model_data__['sets']

    if 'loc_techs_storage_capacity_constraint' in sets:
        backend_model.storage_capacity_constraint = po.Constraint(
                backend_model.loc_techs_storage_capacity_constraint,
                rule=storage_capacity_constraint_rule
        )

    if 'loc_techs_energy_capacity_storage_constraint' in sets:
        backend_model.energy_capacity_storage_constraint = po.Constraint(
                backend_model.loc_techs_energy_capacity_storage_constraint,
                rule=energy_capacity_storage_constraint_rule
        )

    if 'loc_techs_resource_capacity_constraint' in sets:
        backend_model.resource_capacity_constraint = po.Constraint(
                backend_model.loc_techs_resource_capacity_constraint,
                rule=resource_capacity_constraint_rule
        )

    if 'loc_techs_resource_capacity_equals_energy_capacity_constraint' in sets:
        backend_model.resource_capacity_equals_energy_capacity_constraint = po.Constraint(
                backend_model.loc_techs_resource_capacity_equals_energy_capacity_constraint,
                rule=resource_capacity_equals_energy_capacity_constraint_rule
        )

    if 'loc_techs_resource_area_constraint' in sets:
        backend_model.resource_area_constraint = po.Constraint(
                backend_model.loc_techs_resource_area_constraint,
                rule=resource_area_constraint_rule
        )

    if 'loc_techs_resource_area_per_energy_capacity_constraint' in sets:
        backend_model.resource_area_per_energy_capacity_constraint = po.Constraint(
                backend_model.loc_techs_resource_area_per_energy_capacity_constraint,
                rule=resource_area_per_energy_capacity_constraint_rule
        )

    if 'locs_resource_area_capacity_per_loc_constraint' in sets:
        backend_model.resource_area_capacity_per_loc_constraint = po.Constraint(
            backend_model.locs_resource_area_capacity_per_loc_constraint,
            rule=resource_area_capacity_per_loc_constraint_rule
        )

    if 'loc_techs_energy_capacity_constraint' in sets:
        backend_model.energy_capacity_constraint = po.Constraint(
            backend_model.loc_techs_energy_capacity_constraint,
            rule=energy_capacity_constraint_rule
        )

    if 'techs_energy_capacity_systemwide_constraint' in sets:
        backend_model.energy_capacity_systemwide_constraint = po.Constraint(
            backend_model.techs_energy_capacity_systemwide_constraint,
            rule=energy_capacity_systemwide_constraint_rule
        )
Пример #5
0
def load_constraints(backend_model):
    sets = backend_model.__calliope_model_data__['sets']

    backend_model.balance_conversion_constraint = po.Constraint(
        backend_model.loc_techs_balance_conversion_constraint,
        backend_model.timesteps,
        rule=balance_conversion_constraint_rule)

    if 'loc_techs_cost_var_conversion_constraint' in sets:
        backend_model.cost_var_conversion_constraint = po.Constraint(
            backend_model.costs,
            backend_model.loc_techs_cost_var_conversion_constraint,
            backend_model.timesteps,
            rule=cost_var_conversion_constraint_rule)
Пример #6
0
def ramping_rate(model):
    """
    Ramping rate constraints.

    Depends on: node_energy_balance, node_constraints_build

    """
    m = model.m
    time_res = model.data['_time_res'].to_series()

    # Constraint rules
    def _ramping_rule(m, loc_tech, t, direction):
        x, y = loc_tech.split(":", 1)
        # e_ramping: Ramping rate [fraction of installed capacity per hour]
        ramping_rate_value = model.get_option(y + '.constraints.e_ramping')
        if ramping_rate_value is False:
            # If the technology defines no `e_ramping`, we don't build a
            # ramping constraint for it!
            return po.Constraint.NoConstraint
        else:
            # No constraint for first timestep
            # NB: From Pyomo 3.5 to 3.6, order_dict became zero-indexed
            if m.t.order_dict[t] == 0:
                return po.Constraint.NoConstraint
            else:
                carrier = model.get_option(y + '.carrier', default=y + '.carrier_out')
                if isinstance(carrier, dict): # conversion_plus technology
                    carrier = model.get_carrier(y, 'out', primary=True)
                diff = ((m.c_prod[carrier, loc_tech, t]
                         + m.c_con[carrier, loc_tech, t]) / time_res.at[t]
                        - (m.c_prod[carrier, loc_tech, model.prev_t(t)]
                           + m.c_con[carrier, loc_tech, model.prev_t(t)])
                        / time_res.at[model.prev_t(t)])
                max_ramping_rate = ramping_rate_value * m.e_cap[loc_tech]
                if direction == 'up':
                    return diff <= max_ramping_rate
                else:
                    return -1 * max_ramping_rate <= diff

    def c_ramping_up_rule(m, loc_tech, t):
        return _ramping_rule(m, loc_tech, t, direction='up')

    def c_ramping_down_rule(m, loc_tech, t):
        return _ramping_rule(m, loc_tech, t, direction='down')

    # Constraints
    m.c_ramping_up = po.Constraint(m.loc_tech, m.t, rule=c_ramping_up_rule)
    m.c_ramping_down = po.Constraint(m.loc_tech, m.t, rule=c_ramping_down_rule)
Пример #7
0
def system_margin(model):
    """

    """
    m = model.m
    time_res = model.data['_time_res'].to_series()

    def carrier(y):
        return model.get_option(y + '.carrier')

    # Constraint rules
    def c_system_margin_rule(m, c):
        # If no margin defined for a carrier, use 0 (i.e. no margin)
        margin = model.config_model.system_margin.get_key(c, default=0)
        if margin:
            t = model.t_max_demand[c]
            return (sum(m.es_prod[c, y, x, t] for y in m.y
                        for x in m.x) * (1 + margin) <= time_res.at[t] * sum(
                            (m.e_cap[y, x] / model.get_eff_ref('e', y, x))
                            for y in m.y if carrier(y) == c for x in m.x))
        else:
            return po.Constraint.NoConstraint

    # Constraints
    m.c_system_margin = po.Constraint(m.c, rule=c_system_margin_rule)
Пример #8
0
def load_constraints(backend_model):
    sets = backend_model.__calliope_model_data__['sets']

    if 'loc_techs_symmetric_transmission_constraint' in sets and backend_model.mode != 'operate':
        backend_model.symmetric_transmission_constraint = po.Constraint(
            backend_model.loc_techs_symmetric_transmission_constraint,
            rule=symmetric_transmission_constraint_rule)
Пример #9
0
def system_margin(model):
    """

    """
    m = model.m
    time_res = model.data['_time_res'].to_series()

    def carrier(y, x):
        if y in m.y_conversion_plus:
            return model.get_cp_carriers(y, x)[0]
        else:
            return model.get_option(y + '.carrier', default=y + '.carrier_out')

    # Constraint rules
    def c_system_margin_rule(m, c):
        # If no margin defined for a carrier, use 0 (i.e. no margin)
        margin = model.config_model.system_margin.get_key(c, default=0)
        if margin:
            t = model.t_max_demand[c]
            return (sum(m.c_prod[c, y, x, t]
                        for y in m.y if y not in m.y_demand
                        for x in m.x) * (1 + margin) <= time_res.at[t] * sum(
                            (m.e_cap[y, x] /
                             base.get_constraint_param(m, 'e_eff', y, x, t))
                            for y in m.y if y not in m.y_demand
                            for x in m.x if carrier(y, x) == c))
        else:
            return po.Constraint.NoConstraint

    # Constraints
    m.c_system_margin = po.Constraint(m.c, rule=c_system_margin_rule)
Пример #10
0
def add_hacks(model, hacks):
    """ add hackish features to model object

    This function is reserved for corner cases/features that still lack a
    satisfyingly general solution that could become part of create_model.
    Use hack features sparingly and think about how to incorporate into main
    model function before adding here. Otherwise, these features might become
    a maintenance burden.

    """

    # Store hack data
    model.hacks = hacks

    # Global CO2 limit
    try:
        global_co2_limit = hacks.loc['Global CO2 limit', 'Value']
    except KeyError:
        global_co2_limit = float('inf')

    # only add constraint if limit is finite
    if not math.isinf(global_co2_limit):
        model.res_global_co2_limit = pyomo.Constraint(
            rule=res_global_co2_limit_rule,
            doc='total co2 commodity output <= hacks.Glocal CO2 limit')

    return model
Пример #11
0
def node_constraints_build_total(model):
    """

    """
    m = model.m

    # Constraint rules
    def c_e_cap_total_systemwide_rule(m, y):
        total_max = model.get_option(y + '.constraints.e_cap.total_max')
        total_equals = model.get_option(y + '.constraints.e_cap.total_equals')
        scale = model.get_option(y + '.constraints.e_cap_scale')

        if np.isinf(total_max) and not total_equals:
            return po.Constraint.NoConstraint

        sum_expr = sum(m.e_cap[y, x] for x in m.x)
        total_expr = total_equals * scale if total_equals else total_max * scale

        if total_equals:
            return sum_expr == total_expr
        else:
            return sum_expr <= total_expr

    # Constraints
    m.c_e_cap_total_systemwide = \
        po.Constraint(m.y, rule=c_e_cap_total_systemwide_rule)
Пример #12
0
def system_margin(model):
    """

    """
    m = model.m
    time_res = model.data['_time_res'].to_series()

    def carrier(loc_tech):
        x, y = get_y_x(loc_tech)
        return model.get_carrier(y, 'out', x=x, primary=True)

    def get_y_x(loc_tech):
        return loc_tech.split(":", 1)

    # Constraint rules
    def c_system_margin_rule(m, c):
        # If no margin defined for a carrier, use 0 (i.e. no margin)
        margin = model.config_model.system_margin.get_key(c, default=0)
        if margin:
            t = model.t_max_demand[c]
            return (sum(m.c_prod[c, loc_tech, t] for loc_tech in m.loc_tech
                        if loc_tech not in m.loc_tech_demand) *
                    (1 + margin) <= time_res.at[t] * sum(
                        (m.e_cap[loc_tech] /
                         base.get_constraint_param(model, 'e_eff',
                                                   get_y_x(loc_tech)[1],
                                                   get_y_x(loc_tech)[0], t))
                        for loc_tech in m.loc_tech if loc_tech not in
                        m.loc_tech_demand and carrier(loc_tech) == c))
        else:
            return po.Constraint.NoConstraint

    # Constraints
    m.c_system_margin = po.Constraint(m.c, rule=c_system_margin_rule)
Пример #13
0
def add_buy_sell_price(m):

    # Sets
    m.com_sell = pyomo.Set(within=m.com,
                           initialize=commodity_subset(m.com_tuples, 'Sell'),
                           doc='Commodities that can be sold')
    m.com_buy = pyomo.Set(within=m.com,
                          initialize=commodity_subset(m.com_tuples, 'Buy'),
                          doc='Commodities that can be purchased')

    # Variables
    m.e_co_sell = pyomo.Var(
        m.tm,
        m.com_tuples,
        within=pyomo.NonNegativeReals,
        doc='Use of sell commodity source (MW) per timestep')
    m.e_co_buy = pyomo.Var(m.tm,
                           m.com_tuples,
                           within=pyomo.NonNegativeReals,
                           doc='Use of buy commodity source (MW) per timestep')

    # Rules
    m.res_sell_step = pyomo.Constraint(
        m.tm,
        m.com_tuples,
        rule=res_sell_step_rule,
        doc='sell commodity output per step <= commodity.maxperstep')
    m.res_sell_total = pyomo.Constraint(
        m.com_tuples,
        rule=res_sell_total_rule,
        doc='total sell commodity output <= commodity.max')
    m.res_buy_step = pyomo.Constraint(
        m.tm,
        m.com_tuples,
        rule=res_buy_step_rule,
        doc='buy commodity output per step <= commodity.maxperstep')
    m.res_buy_total = pyomo.Constraint(
        m.com_tuples,
        rule=res_buy_total_rule,
        doc='total buy commodity output <= commodity.max')

    m.res_sell_buy_symmetry = pyomo.Constraint(
        m.pro_input_tuples,
        rule=res_sell_buy_symmetry_rule,
        doc='power connection capacity must be symmetric in both directions')

    return m
Пример #14
0
def load_constraints(backend_model):
    sets = backend_model.__calliope_model_data['sets']

    if 'loc_techs_balance_conversion_plus_primary_constraint' in sets:
        backend_model.balance_conversion_plus_primary_constraint = po.Constraint(
            backend_model.loc_techs_balance_conversion_plus_primary_constraint,
            backend_model.timesteps,
            rule=balance_conversion_plus_primary_constraint_rule)

    if 'loc_techs_carrier_production_max_conversion_plus_constraint' in sets:
        backend_model.carrier_production_max_conversion_plus_constraint = po.Constraint(
            backend_model.
            loc_techs_carrier_production_max_conversion_plus_constraint,
            backend_model.timesteps,
            rule=carrier_production_max_conversion_plus_constraint_rule)

    if 'loc_techs_carrier_production_min_conversion_plus_constraint' in sets:
        backend_model.carrier_production_min_conversion_plus_constraint = po.Constraint(
            backend_model.
            loc_techs_carrier_production_min_conversion_plus_constraint,
            backend_model.timesteps,
            rule=carrier_production_min_conversion_plus_constraint_rule)

    if 'loc_techs_cost_var_conversion_plus_constraint' in sets:
        backend_model.cost_var_conversion_plus_constraint = po.Constraint(
            backend_model.costs,
            backend_model.loc_techs_cost_var_conversion_plus_constraint,
            backend_model.timesteps,
            rule=cost_var_conversion_plus_constraint_rule)

    if 'loc_techs_balance_conversion_plus_in_2_constraint' in sets:
        backend_model.balance_conversion_plus_in_2_constraint = po.Constraint(
            ['in_2'],
            backend_model.loc_techs_balance_conversion_plus_in_2_constraint,
            backend_model.timesteps,
            rule=balance_conversion_plus_tiers_constraint_rule)

    if 'loc_techs_balance_conversion_plus_in_3_constraint' in sets:
        backend_model.balance_conversion_plus_in_3_constraint = po.Constraint(
            ['in_3'],
            backend_model.loc_techs_balance_conversion_plus_in_3_constraint,
            backend_model.timesteps,
            rule=balance_conversion_plus_tiers_constraint_rule)

    if 'loc_techs_balance_conversion_plus_out_2_constraint' in sets:
        backend_model.balance_conversion_plus_out_2_constraint = po.Constraint(
            ['out_2'],
            backend_model.loc_techs_balance_conversion_plus_out_2_constraint,
            backend_model.timesteps,
            rule=balance_conversion_plus_tiers_constraint_rule)

    if 'loc_techs_balance_conversion_plus_out_3_constraint' in sets:
        backend_model.balance_conversion_plus_out_3_constraint = po.Constraint(
            ['out_3'],
            backend_model.loc_techs_balance_conversion_plus_out_3_constraint,
            backend_model.timesteps,
            rule=balance_conversion_plus_tiers_constraint_rule)
Пример #15
0
def load_constraints(backend_model):
    sets = backend_model.__calliope_model_data['sets']
    run_config = backend_model.__calliope_run_config

    if 'loc_techs_symmetric_transmission_constraint' in sets and run_config['mode'] != 'operate':
        backend_model.symmetric_transmission_constraint = po.Constraint(
            backend_model.loc_techs_symmetric_transmission_constraint,
            rule=symmetric_transmission_constraint_rule
        )
Пример #16
0
def node_resource(model):
    m = model.m

    # TODO reformulate c_r_rule conditionally once Pyomo supports that.
    # had to remove the following formulation because it is not
    # re-evaluated on model re-construction -- we now check for
    # demand/supply tech instead, which means that `r` can only
    # be ALL negative or ALL positive for a given tech!
    # Ideally we have `elif po.value(m.r[y, x, t]) > 0:` instead of
    # `elif y in m.y_supply or y in m.y_unmet_demand:` and `elif y in m.y_demand:`

    def r_available_rule(m, y, x, t):
        r_scale = model.get_option(y + '.constraints.r_scale', x=x)
        force_r = get_constraint_param(model, 'force_r', y, x, t)

        if y in m.y_sd:
            e_eff = get_constraint_param(model, 'e_eff', y, x, t)
            if po.value(e_eff) == 0:
                c_prod = 0
            else:
                c_prod = sum(m.c_prod[c, y, x, t]
                             for c in m.c) / e_eff  # supply techs
            c_con = sum(m.c_con[c, y, x, t]
                        for c in m.c) * e_eff  # demand techs

            if y in m.y_sd_r_area:
                r_avail = (get_constraint_param(model, 'r', y, x, t) *
                           r_scale * m.r_area[y, x])
            else:
                r_avail = (get_constraint_param(model, 'r', y, x, t) * r_scale)

            if force_r:
                return c_prod + c_con == r_avail
            else:
                return c_prod - c_con <= r_avail

        elif y in m.y_supply_plus:
            r_eff = get_constraint_param(model, 'r_eff', y, x, t)

            if y in m.y_sp_r_area:
                r_avail = (get_constraint_param(model, 'r', y, x, t) *
                           r_scale * m.r_area[y, x] * r_eff)
            else:
                r_avail = get_constraint_param(model, 'r', y, x,
                                               t) * r_scale * r_eff

            if force_r:
                return m.r[y, x, t] == r_avail
            else:
                return m.r[y, x, t] <= r_avail

    # Constraints
    m.c_r_available = po.Constraint(m.y_finite_r,
                                    m.x_r,
                                    m.t,
                                    rule=r_available_rule)
Пример #17
0
def load_constraints(backend_model):
    sets = backend_model.__calliope_model_data["sets"]
    run_config = backend_model.__calliope_run_config

    if "loc_techs_cost_constraint" in sets:
        backend_model.cost_constraint = po.Constraint(
            backend_model.costs,
            backend_model.loc_techs_cost,
            rule=cost_constraint_rule)

    # FIXME: remove check for operate from constraint files, avoid investment costs more intelligently?
    if ("loc_techs_cost_investment_constraint" in sets
            and run_config["mode"] != "operate"):
        # Right-hand side expression can be later updated by MILP investment costs
        backend_model.cost_investment_rhs = po.Expression(
            backend_model.costs,
            backend_model.loc_techs_cost_investment_constraint,
            initialize=0.0,
        )

        backend_model.cost_investment_constraint = po.Constraint(
            backend_model.costs,
            backend_model.loc_techs_cost_investment_constraint,
            rule=cost_investment_constraint_rule,
        )

    if "loc_techs_om_cost" in sets:
        # Right-hand side expression can be later updated by export costs/revenue
        backend_model.cost_var_rhs = po.Expression(
            backend_model.costs,
            backend_model.loc_techs_om_cost,
            backend_model.timesteps,
            initialize=0.0,
        )
    if "loc_techs_cost_var_constraint" in sets:
        # Constraint is built over a different loc_techs set to expression, as
        # it is updated in conversion.py and conversion_plus.py constraints
        backend_model.cost_var_constraint = po.Constraint(
            backend_model.costs,
            backend_model.loc_techs_cost_var_constraint,
            backend_model.timesteps,
            rule=cost_var_constraint_rule,
        )
Пример #18
0
def load_constraints(backend_model):
    sets = backend_model.__calliope_model_data["sets"]
    run_config = backend_model.__calliope_run_config

    if ("loc_techs_symmetric_transmission_constraint" in sets
            and run_config["mode"] != "operate"):
        backend_model.symmetric_transmission_constraint = po.Constraint(
            backend_model.loc_techs_symmetric_transmission_constraint,
            rule=symmetric_transmission_constraint_rule,
        )
Пример #19
0
def load_constraints(backend_model):
    sets = backend_model.__calliope_model_data__['sets']

    if 'loc_carriers_system_balance_constraint' in sets:
        backend_model.system_balance = po.Expression(
            backend_model.loc_carriers_system_balance_constraint,
            backend_model.timesteps,
            initialize=0.0
        )

        backend_model.system_balance_constraint = po.Constraint(
            backend_model.loc_carriers_system_balance_constraint,
            backend_model.timesteps,
            rule=system_balance_constraint_rule
        )

    if 'loc_techs_balance_supply_constraint' in sets:
        backend_model.balance_supply_constraint = po.Constraint(
            backend_model.loc_techs_balance_supply_constraint,
            backend_model.timesteps,
            rule=balance_supply_constraint_rule
        )

    if 'loc_techs_balance_demand_constraint' in sets:
        backend_model.balance_demand_constraint = po.Constraint(
            backend_model.loc_techs_balance_demand_constraint,
            backend_model.timesteps,
            rule=balance_demand_constraint_rule
        )

    if 'loc_techs_balance_transmission_constraint' in sets:
        backend_model.balance_transmission_constraint = po.Constraint(
            backend_model.loc_techs_balance_transmission_constraint,
            backend_model.timesteps,
            rule=balance_transmission_constraint_rule
        )

    if 'loc_techs_resource_availability_supply_plus_constraint' in sets:
        backend_model.balance_supply_plus_constraint = po.Constraint(
            backend_model.loc_techs_resource_availability_supply_plus_constraint,
            backend_model.timesteps,
            rule=balance_supply_plus_constraint_rule
        )

    if 'loc_techs_balance_supply_plus_constraint' in sets:
        backend_model.resource_availability_supply_plus_constraint = po.Constraint(
            backend_model.loc_techs_balance_supply_plus_constraint,
            backend_model.timesteps,
            rule=resource_availability_supply_plus_constraint_rule
        )

    if 'loc_techs_balance_storage_constraint' in sets:
        backend_model.balance_storage_constraint = po.Constraint(
            backend_model.loc_techs_balance_storage_constraint,
            backend_model.timesteps,
            rule=balance_storage_constraint_rule
        )
Пример #20
0
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)
Пример #21
0
def load_constraints(backend_model):
    sets = backend_model.__calliope_model_data["sets"]
    run_config = backend_model.__calliope_run_config

    if "techlists_group_share_energy_cap_min_constraint" in sets:
        backend_model.group_share_energy_cap_min_constraint = po.Constraint(
            backend_model.techlists_group_share_energy_cap_min_constraint,
            ["min"],
            rule=group_share_energy_cap_constraint_rule,
        )

    if "techlists_group_share_energy_cap_max_constraint" in sets:
        backend_model.group_share_energy_cap_max_constraint = po.Constraint(
            backend_model.techlists_group_share_energy_cap_max_constraint,
            ["max"],
            rule=group_share_energy_cap_constraint_rule,
        )

    if "techlists_group_share_energy_cap_equals_constraint" in sets:
        backend_model.group_share_energy_cap_equals_constraint = po.Constraint(
            backend_model.techlists_group_share_energy_cap_equals_constraint,
            ["equals"],
            rule=group_share_energy_cap_constraint_rule,
        )

    if "techlists_carrier_group_share_carrier_prod_min_constraint" in sets:
        backend_model.group_share_carrier_prod_min_constraint = po.Constraint(
            backend_model.
            techlists_carrier_group_share_carrier_prod_min_constraint,
            ["min"],
            rule=group_share_carrier_prod_constraint_rule,
        )

    if "techlists_carrier_group_share_carrier_prod_max_constraint" in sets:
        backend_model.group_share_carrier_prod_max_constraint = po.Constraint(
            backend_model.
            techlists_carrier_group_share_carrier_prod_max_constraint,
            ["max"],
            rule=group_share_carrier_prod_constraint_rule,
        )

    if "techlists_carrier_group_share_carrier_prod_equals_constraint" in sets:
        backend_model.group_share_carrier_prod_equals_constraint = po.Constraint(
            backend_model.
            techlists_carrier_group_share_carrier_prod_equals_constraint,
            ["equals"],
            rule=group_share_carrier_prod_constraint_rule,
        )

    if "carriers_reserve_margin_constraint" in sets and run_config[
            "mode"] != "operate":
        backend_model.reserve_margin_constraint = po.Constraint(
            backend_model.carriers_reserve_margin_constraint,
            rule=reserve_margin_constraint_rule,
        )
Пример #22
0
def build_constraints(backend_model, model_data, constraint_definitions):
    for constraint_name, constraint_config in constraint_definitions.items():
        subset = create_valid_subset(model_data, constraint_name,
                                     constraint_config)
        if subset is None:
            continue
        setattr(
            backend_model,
            f"{constraint_name}_constraint",
            po.Constraint(
                subset,
                rule=_load_rule_function(f"{constraint_name}_constraint_rule"),
            ),
        )
Пример #23
0
def model_constraints(model):
    m = model.m

    @utils.memoize
    def get_parents(level):
        return list(model._locations[model._locations._level == level].index)

    @utils.memoize
    def get_children(parent, childless_only=True):
        """
        If childless_only is True, only children that have no children
        themselves are returned.

        """
        locations = model._locations
        children = list(locations[locations._within == parent].index)
        if childless_only:  # FIXME childless_only param needs tests
            children = [i for i in children if len(get_children(i)) == 0]
        return children

    # Constraint rules
    def c_system_balance_rule(m, c, x, t):
        # Balacing takes place at top-most (level 0) locations, as well
        # as within any lower-level locations that contain children
        if (model._locations.at[x, '_level'] == 0 or len(get_children(x)) > 0):
            family = get_children(x) + [x]  # list of children + parent
            balance = (sum(m.es_prod[c, y, xs, t] for xs in family
                           for y in m.y_np) + sum(m.ec_prod[c, y, xs, t]
                                                  for xs in family
                                                  for y in m.y_p) +
                       sum(m.es_con[c, y, xs, t] for xs in family
                           for y in m.y_np) + sum(m.ec_con[c, y, xs, t]
                                                  for xs in family
                                                  for y in m.y_p))
            if c == 'power':
                return balance == 0
            else:  # e.g. for heat
                return balance >= 0
        else:
            return po.Constraint.NoConstraint

    # Constraints
    m.c_system_balance = po.Constraint(m.c,
                                       m.x,
                                       m.t,
                                       rule=c_system_balance_rule)
Пример #24
0
def node_constraints_transmission(model):
    """
    Constrain e_cap symmetrically for transmission nodes. Transmission techs only.
    """
    m = model.m

    # Constraint rules
    def c_trans_rule(m, y, x):
        y_remote, x_remote = transmission.get_remotes(y, x)
        if y_remote in m.y_transmission:
            return m.e_cap[y, x] == m.e_cap[y_remote, x_remote]
        else:
            return po.Constraint.NoConstraint

    # Constraints
    m.c_transmission_capacity = po.Constraint(m.y_transmission,
                                              m.x_transmission,
                                              rule=c_trans_rule)
Пример #25
0
def load_constraints(backend_model):
    sets = backend_model.__calliope_model_data__['sets']

    if 'loc_tech_carriers_carrier_production_max_constraint' in sets:
        backend_model.carrier_production_max_constraint = po.Constraint(
            backend_model.loc_tech_carriers_carrier_production_max_constraint,
            backend_model.timesteps,
            rule=carrier_production_max_constraint_rule
        )
    if 'loc_tech_carriers_carrier_production_min_constraint' in sets:
        backend_model.carrier_production_min_constraint = po.Constraint(
            backend_model.loc_tech_carriers_carrier_production_min_constraint,
            backend_model.timesteps,
            rule=carrier_production_min_constraint_rule
        )
    if 'loc_tech_carriers_carrier_consumption_max_constraint' in sets:
        backend_model.carrier_consumption_max_constraint = po.Constraint(
            backend_model.loc_tech_carriers_carrier_consumption_max_constraint,
            backend_model.timesteps,
            rule=carrier_consumption_max_constraint_rule
        )

    if 'loc_techs_resource_max_constraint' in sets:
        backend_model.resource_max_constraint = po.Constraint(
            backend_model.loc_techs_resource_max_constraint,
            backend_model.timesteps,
            rule=resource_max_constraint_rule
        )

    if 'loc_techs_storage_max_constraint' in sets:
        backend_model.storage_max_constraint = po.Constraint(
            backend_model.loc_techs_storage_max_constraint,
            backend_model.timesteps,
            rule=storage_max_constraint_rule
        )

    if 'loc_tech_carriers_ramping_constraint' in sets:
        backend_model.ramping_up_constraint = po.Constraint(
            backend_model.loc_tech_carriers_ramping_constraint, backend_model.timesteps,
            rule=ramping_up_constraint_rule
        )

        backend_model.ramping_down_constraint = po.Constraint(
            backend_model.loc_tech_carriers_ramping_constraint, backend_model.timesteps,
            rule=ramping_down_constraint_rule
        )
Пример #26
0
def load_constraints(backend_model):
    sets = backend_model.__calliope_model_data['sets']
    run_config = backend_model.__calliope_run_config

    if 'techlists_group_share_energy_cap_min_constraint' in sets:
        backend_model.group_share_energy_cap_min_constraint = po.Constraint(
            backend_model.techlists_group_share_energy_cap_min_constraint,
            ['min'],
            rule=group_share_energy_cap_constraint_rule)

    if 'techlists_group_share_energy_cap_max_constraint' in sets:
        backend_model.group_share_energy_cap_max_constraint = po.Constraint(
            backend_model.techlists_group_share_energy_cap_max_constraint,
            ['max'],
            rule=group_share_energy_cap_constraint_rule)

    if 'techlists_group_share_energy_cap_equals_constraint' in sets:
        backend_model.group_share_energy_cap_equals_constraint = po.Constraint(
            backend_model.techlists_group_share_energy_cap_equals_constraint,
            ['equals'],
            rule=group_share_energy_cap_constraint_rule)

    if 'techlists_carrier_group_share_carrier_prod_min_constraint' in sets:
        backend_model.group_share_carrier_prod_min_constraint = po.Constraint(
            backend_model.
            techlists_carrier_group_share_carrier_prod_min_constraint, ['min'],
            rule=group_share_carrier_prod_constraint_rule)

    if 'techlists_carrier_group_share_carrier_prod_max_constraint' in sets:
        backend_model.group_share_carrier_prod_max_constraint = po.Constraint(
            backend_model.
            techlists_carrier_group_share_carrier_prod_max_constraint, ['max'],
            rule=group_share_carrier_prod_constraint_rule)

    if 'techlists_carrier_group_share_carrier_prod_equals_constraint' in sets:
        backend_model.group_share_carrier_prod_equals_constraint = po.Constraint(
            backend_model.
            techlists_carrier_group_share_carrier_prod_equals_constraint,
            ['equals'],
            rule=group_share_carrier_prod_constraint_rule)

    if 'carriers_reserve_margin_constraint' in sets and run_config[
            'mode'] != 'operate':
        backend_model.reserve_margin_constraint = po.Constraint(
            backend_model.carriers_reserve_margin_constraint,
            rule=reserve_margin_constraint_rule)
Пример #27
0
    def elec_export_rule(m, t):
        if m.flex_type[t] == 1:

            def solar_power_max(m, t):
                return m.solar_act[t] <= m.solar[t]

            m.solar_power_ma = pyen.Constraint(m.t, rule=solar_power_max)

            return m.elec_export[t] == m.grid_export[t] - m.flex_value[t]

        elif m.flex_type[t] == 2:
            return m.elec_export[t] == value(m.grid_export[t] +
                                             m.flex_value[t])

        # elif m.flex_type[t] == 3:
        #     if value(m.grid_export[t]) > 0 and value(m.grid_export[t]) >= value(m.flex_value[t]):
        #         return m.elec_export[t] == abs(value(m.grid_export[t] - m.flex_value[t]))
        #     else:
        #         return m.elec_export[t] == 0
        else:

            return m.elec_export[t] <= 50 * 5000
Пример #28
0
def node_resource(model):
    """
    Defines variables:

    * rs: resource <-> storage (+ production, - consumption)
    * r_area: resource collector area
    * rbs: secondary resource -> storage (+ production)

    """
    m = model.m

    # Variables
    m.rs = po.Var(m.y, m.x, m.t, within=po.Reals)
    m.r_area = po.Var(m.y_def_r, m.x, within=po.NonNegativeReals)
    m.rbs = po.Var(m.y_rb, m.x, m.t, within=po.NonNegativeReals)

    # Constraint rules
    def c_rs_rule(m, y, x, t):
        r_avail = (m.r[y, x, t] *
                   model.get_option(y + '.constraints.r_scale', x=x) *
                   m.r_area[y, x] *
                   model.get_option(y + '.constraints.r_eff', x=x))
        if model.get_option(y + '.constraints.force_r', x=x):
            return m.rs[y, x, t] == r_avail
        # TODO reformulate conditionally once Pyomo supports that:
        # had to remove the following formulation because it is not
        # re-evaluated on model re-construction -- we now check for
        # demand/supply tech instead, which means that `r` can only
        # be ALL negative or ALL positive for a given tech!
        # elif po.value(m.r[y, x, t]) > 0:
        elif (y in model.get_group_members('supply')
              or y in model.get_group_members('unmet_demand')):
            return m.rs[y, x, t] <= r_avail
        elif y in model.get_group_members('demand'):
            return m.rs[y, x, t] >= r_avail

    # Constraints
    m.c_rs = po.Constraint(m.y_def_r, m.x, m.t, rule=c_rs_rule)
Пример #29
0
def load_constraints(backend_model):
    model_data_dict = backend_model.__calliope_model_data["data"]

    for sense in ["min", "max", "equals"]:
        if "group_energy_cap_share_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_energy_cap_share_{}_constraint".format(sense),
                po.Constraint(
                    getattr(
                        backend_model, "group_names_energy_cap_share_{}".format(sense)
                    ),
                    [sense],
                    rule=energy_cap_share_constraint_rule,
                ),
            )

        if "group_energy_cap_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_energy_cap_{}_constraint".format(sense),
                po.Constraint(
                    getattr(backend_model, "group_names_energy_cap_{}".format(sense)),
                    [sense],
                    rule=energy_cap_constraint_rule,
                ),
            )

        if "group_storage_cap_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_storage_cap_{}_constraint".format(sense),
                po.Constraint(
                    getattr(backend_model, "group_names_storage_cap_{}".format(sense)),
                    [sense],
                    rule=storage_cap_constraint_rule,
                ),
            )              
            
        if "group_resource_area_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_resource_area_{}_constraint".format(sense),
                po.Constraint(
                    getattr(
                        backend_model, "group_names_resource_area_{}".format(sense)
                    ),
                    [sense],
                    rule=resource_area_constraint_rule,
                ),
            )

        if "group_carrier_prod_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_carrier_prod_{}_constraint".format(sense),
                po.Constraint(
                    getattr(backend_model, "group_names_carrier_prod_{}".format(sense)),
                    backend_model.carriers,
                    [sense],
                    rule=carrier_prod_constraint_rule,
                ),
            )

        if "group_demand_share_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_demand_share_{}_constraint".format(sense),
                po.Constraint(
                    getattr(backend_model, "group_names_demand_share_{}".format(sense)),
                    backend_model.carriers,
                    [sense],
                    rule=demand_share_constraint_rule,
                ),
            )

        if "group_demand_share_per_timestep_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_demand_share_per_timestep_{}_constraint".format(sense),
                po.Constraint(
                    getattr(
                        backend_model,
                        "group_names_demand_share_per_timestep_{}".format(sense),
                    ),
                    backend_model.carriers,
                    backend_model.timesteps,
                    [sense],
                    rule=demand_share_per_timestep_constraint_rule,
                ),
            )

        if "group_carrier_prod_share_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_carrier_prod_share_{}_constraint".format(sense),
                po.Constraint(
                    getattr(
                        backend_model, "group_names_carrier_prod_share_{}".format(sense)
                    ),
                    backend_model.carriers,
                    [sense],
                    rule=carrier_prod_share_constraint_rule,
                ),
            )

        if "group_carrier_prod_share_per_timestep_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_carrier_prod_share_per_timestep_{}_constraint".format(sense),
                po.Constraint(
                    getattr(
                        backend_model,
                        "group_names_carrier_prod_share_per_timestep_{}".format(sense),
                    ),
                    backend_model.carriers,
                    backend_model.timesteps,
                    [sense],
                    rule=carrier_prod_share_per_timestep_constraint_rule,
                ),
            )

        if "group_net_import_share_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_net_import_share_{}_constraint".format(sense),
                po.Constraint(
                    getattr(
                        backend_model, "group_names_net_import_share_{}".format(sense)
                    ),
                    backend_model.carriers,
                    [sense],
                    rule=net_import_share_constraint_rule,
                ),
            )

        if "group_cost_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_cost_{}_constraint".format(sense),
                po.Constraint(
                    getattr(backend_model, "group_names_cost_{}".format(sense)),
                    backend_model.costs,
                    [sense],
                    rule=cost_cap_constraint_rule,
                ),
            )
        if "group_cost_var_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_cost_var_{}_constraint".format(sense),
                po.Constraint(
                    getattr(backend_model, "group_names_cost_var_{}".format(sense)),
                    backend_model.costs,
                    [sense],
                    rule=cost_var_cap_constraint_rule,
                ),
            )
        if "group_cost_investment_{}".format(sense) in model_data_dict:
            setattr(
                backend_model,
                "group_cost_investment_{}_constraint".format(sense),
                po.Constraint(
                    getattr(
                        backend_model, "group_names_cost_investment_{}".format(sense)
                    ),
                    backend_model.costs,
                    [sense],
                    rule=cost_investment_cap_constraint_rule,
                ),
            )

    if "group_demand_share_per_timestep_decision" in model_data_dict:
        backend_model.group_demand_share_per_timestep_decision_main_constraint = po.Constraint(
            backend_model.group_names_demand_share_per_timestep_decision,
            backend_model.carriers,
            backend_model.techs,
            backend_model.timesteps,
            rule=demand_share_per_timestep_decision_main_constraint_rule,
        )
        backend_model.group_demand_share_per_timestep_decision_sum_constraint = po.Constraint(
            backend_model.group_names_demand_share_per_timestep_decision,
            backend_model.carriers,
            rule=demand_share_per_timestep_decision_sum_constraint_rule,
        )
Пример #30
0
def create_model(data, dt=1, timesteps=None, dual=False):
    """Create a pyomo ConcreteModel urbs object from given input data.

    Args:
        data: a dict of 6 DataFrames with the keys 'commodity', 'process',
            'transmission', 'storage', 'demand' and 'supim'.
        dt: timestep duration in hours (default: 1)
        timesteps: optional list of timesteps, default: demand timeseries
        dual: set True to add dual variables to model (slower); default: False

    Returns:
        a pyomo ConcreteModel object
    """

    # Optional
    if not timesteps:
        timesteps = data['demand'].index.tolist()
    m = pyomo_model_prep(data, timesteps)  # preparing pyomo model
    m.name = 'urbs'
    m.created = datetime.now().strftime('%Y%m%dT%H%M')
    m._data = data

    # Parameters

    # weight = length of year (hours) / length of simulation (hours)
    # weight scales costs and emissions from length of simulation to a full
    # year, making comparisons among cost types (invest is annualized, fixed
    # costs are annual by default, variable costs are scaled by weight) and
    # among different simulation durations meaningful.
    m.weight = pyomo.Param(
        initialize=float(8760) / (len(m.timesteps) * dt),
        doc='Pre-factor for variable costs and emissions for an annual result')

    # dt = spacing between timesteps. Required for storage equation that
    # converts between energy (storage content, e_sto_con) and power (all other
    # quantities that start with "e_")
    m.dt = pyomo.Param(initialize=dt,
                       doc='Time step duration (in hours), default: 1')

    # Sets
    # ====
    # Syntax: m.{name} = Set({domain}, initialize={values})
    # where name: set name
    #       domain: set domain for tuple sets, a cartesian set product
    #       values: set values, a list or array of element tuples

    # generate ordered time step sets
    m.t = pyomo.Set(initialize=m.timesteps,
                    ordered=True,
                    doc='Set of timesteps')

    # modelled (i.e. excluding init time step for storage) time steps
    m.tm = pyomo.Set(within=m.t,
                     initialize=m.timesteps[1:],
                     ordered=True,
                     doc='Set of modelled timesteps')

    # modelled Demand Side Management time steps (downshift):
    # downshift effective in tt to compensate for upshift in t
    m.tt = pyomo.Set(within=m.t,
                     initialize=m.timesteps[1:],
                     ordered=True,
                     doc='Set of additional DSM time steps')

    # site (e.g. north, middle, south...)
    m.sit = pyomo.Set(
        initialize=m.commodity.index.get_level_values('Site').unique(),
        doc='Set of sites')

    # commodity (e.g. solar, wind, coal...)
    m.com = pyomo.Set(
        initialize=m.commodity.index.get_level_values('Commodity').unique(),
        doc='Set of commodities')

    # commodity type (i.e. SupIm, Demand, Stock, Env)
    m.com_type = pyomo.Set(
        initialize=m.commodity.index.get_level_values('Type').unique(),
        doc='Set of commodity types')

    # process (e.g. Wind turbine, Gas plant, Photovoltaics...)
    m.pro = pyomo.Set(
        initialize=m.process.index.get_level_values('Process').unique(),
        doc='Set of conversion processes')

    # tranmission (e.g. hvac, hvdc, pipeline...)
    m.tra = pyomo.Set(initialize=m.transmission.index.get_level_values(
        'Transmission').unique(),
                      doc='Set of transmission technologies')

    # storage (e.g. hydrogen, pump storage)
    m.sto = pyomo.Set(
        initialize=m.storage.index.get_level_values('Storage').unique(),
        doc='Set of storage technologies')

    # cost_type
    m.cost_type = pyomo.Set(initialize=[
        'Invest', 'Fixed', 'Variable', 'Fuel', 'Revenue', 'Purchase',
        'Environmental'
    ],
                            doc='Set of cost types (hard-coded)')

    # tuple sets
    m.com_tuples = pyomo.Set(
        within=m.sit * m.com * m.com_type,
        initialize=m.commodity.index,
        doc='Combinations of defined commodities, e.g. (Mid,Elec,Demand)')
    m.pro_tuples = pyomo.Set(
        within=m.sit * m.pro,
        initialize=m.process.index,
        doc='Combinations of possible processes, e.g. (North,Coal plant)')
    m.tra_tuples = pyomo.Set(
        within=m.sit * m.sit * m.tra * m.com,
        initialize=m.transmission.index,
        doc='Combinations of possible transmissions, e.g. '
        '(South,Mid,hvac,Elec)')
    m.sto_tuples = pyomo.Set(
        within=m.sit * m.sto * m.com,
        initialize=m.storage.index,
        doc='Combinations of possible storage by site, e.g. (Mid,Bat,Elec)')
    m.dsm_site_tuples = pyomo.Set(
        within=m.sit * m.com,
        initialize=m.dsm.index,
        doc='Combinations of possible dsm by site, e.g. (Mid, Elec)')
    m.dsm_down_tuples = pyomo.Set(
        within=m.tm * m.tm * m.sit * m.com,
        initialize=[(t, tt, site, commodity)
                    for (t, tt, site, commodity) in dsm_down_time_tuples(
                        m.timesteps[1:], m.dsm_site_tuples, m)],
        doc='Combinations of possible dsm_down combinations, e.g. '
        '(5001,5003,Mid,Elec)')

    # commodity type subsets
    m.com_supim = pyomo.Set(
        within=m.com,
        initialize=commodity_subset(m.com_tuples, 'SupIm'),
        doc='Commodities that have intermittent (timeseries) input')
    m.com_stock = pyomo.Set(
        within=m.com,
        initialize=commodity_subset(m.com_tuples, 'Stock'),
        doc='Commodities that can be purchased at some site(s)')
    m.com_sell = pyomo.Set(within=m.com,
                           initialize=commodity_subset(m.com_tuples, 'Sell'),
                           doc='Commodities that can be sold')
    m.com_buy = pyomo.Set(within=m.com,
                          initialize=commodity_subset(m.com_tuples, 'Buy'),
                          doc='Commodities that can be purchased')
    m.com_demand = pyomo.Set(
        within=m.com,
        initialize=commodity_subset(m.com_tuples, 'Demand'),
        doc='Commodities that have a demand (implies timeseries)')
    m.com_env = pyomo.Set(
        within=m.com,
        initialize=commodity_subset(m.com_tuples, 'Env'),
        doc='Commodities that (might) have a maximum creation limit')

    # process tuples for area rule
    m.pro_area_tuples = pyomo.Set(
        within=m.sit * m.pro,
        initialize=m.proc_area.index,
        doc='Processes and Sites with area Restriction')

    # process input/output
    m.pro_input_tuples = pyomo.Set(
        within=m.sit * m.pro * m.com,
        initialize=[(site, process, commodity)
                    for (site, process) in m.pro_tuples
                    for (pro, commodity) in m.r_in.index if process == pro],
        doc='Commodities consumed by process by site, e.g. (Mid,PV,Solar)')
    m.pro_output_tuples = pyomo.Set(
        within=m.sit * m.pro * m.com,
        initialize=[(site, process, commodity)
                    for (site, process) in m.pro_tuples
                    for (pro, commodity) in m.r_out.index if process == pro],
        doc='Commodities produced by process by site, e.g. (Mid,PV,Elec)')

    # process tuples for maximum gradient feature
    m.pro_maxgrad_tuples = pyomo.Set(
        within=m.sit * m.pro,
        initialize=[(sit, pro) for (sit, pro) in m.pro_tuples
                    if m.process.loc[sit, pro]['max-grad'] < 1.0 / dt],
        doc='Processes with maximum gradient smaller than timestep length')

    # process tuples for partial feature
    m.pro_partial_tuples = pyomo.Set(within=m.sit * m.pro,
                                     initialize=[
                                         (site, process)
                                         for (site, process) in m.pro_tuples
                                         for (pro,
                                              _) in m.r_in_min_fraction.index
                                         if process == pro
                                     ],
                                     doc='Processes with partial input')

    m.pro_partial_input_tuples = pyomo.Set(
        within=m.sit * m.pro * m.com,
        initialize=[(site, process, commodity)
                    for (site, process) in m.pro_partial_tuples
                    for (pro, commodity) in m.r_in_min_fraction.index
                    if process == pro],
        doc='Commodities with partial input ratio, e.g. (Mid,Coal PP,Coal)')

    m.pro_partial_output_tuples = pyomo.Set(
        within=m.sit * m.pro * m.com,
        initialize=[(site, process, commodity)
                    for (site, process) in m.pro_partial_tuples
                    for (pro, commodity) in m.r_out_min_fraction.index
                    if process == pro],
        doc='Commodities with partial input ratio, e.g. (Mid,Coal PP,CO2)')

    # process tuples for time variable efficiency
    m.pro_timevar_output_tuples = pyomo.Set(
        within=m.sit * m.pro * m.com,
        initialize=[(site, process, commodity)
                    for (site, process) in m.eff_factor.columns.values
                    for (pro, commodity) in m.r_out.index if process == pro],
        doc='Outputs of processes with time dependent efficiency')

    # Variables

    # costs
    m.costs = pyomo.Var(m.cost_type,
                        within=pyomo.Reals,
                        doc='Costs by type (EUR/a)')

    # commodity
    m.e_co_stock = pyomo.Var(
        m.tm,
        m.com_tuples,
        within=pyomo.NonNegativeReals,
        doc='Use of stock commodity source (MW) per timestep')
    m.e_co_sell = pyomo.Var(
        m.tm,
        m.com_tuples,
        within=pyomo.NonNegativeReals,
        doc='Use of sell commodity source (MW) per timestep')
    m.e_co_buy = pyomo.Var(m.tm,
                           m.com_tuples,
                           within=pyomo.NonNegativeReals,
                           doc='Use of buy commodity source (MW) per timestep')

    # process
    m.cap_pro = pyomo.Var(m.pro_tuples,
                          within=pyomo.NonNegativeReals,
                          doc='Total process capacity (MW)')
    m.cap_pro_new = pyomo.Var(m.pro_tuples,
                              within=pyomo.NonNegativeReals,
                              doc='New process capacity (MW)')
    m.tau_pro = pyomo.Var(m.t,
                          m.pro_tuples,
                          within=pyomo.NonNegativeReals,
                          doc='Power flow (MW) through process')
    m.e_pro_in = pyomo.Var(
        m.tm,
        m.pro_tuples,
        m.com,
        within=pyomo.NonNegativeReals,
        doc='Power flow of commodity into process (MW) per timestep')
    m.e_pro_out = pyomo.Var(m.tm,
                            m.pro_tuples,
                            m.com,
                            within=pyomo.NonNegativeReals,
                            doc='Power flow out of process (MW) per timestep')

    # transmission
    m.cap_tra = pyomo.Var(m.tra_tuples,
                          within=pyomo.NonNegativeReals,
                          doc='Total transmission capacity (MW)')
    m.cap_tra_new = pyomo.Var(m.tra_tuples,
                              within=pyomo.NonNegativeReals,
                              doc='New transmission capacity (MW)')
    m.e_tra_in = pyomo.Var(
        m.tm,
        m.tra_tuples,
        within=pyomo.NonNegativeReals,
        doc='Power flow into transmission line (MW) per timestep')
    m.e_tra_out = pyomo.Var(
        m.tm,
        m.tra_tuples,
        within=pyomo.NonNegativeReals,
        doc='Power flow out of transmission line (MW) per timestep')

    # storage
    m.cap_sto_c = pyomo.Var(m.sto_tuples,
                            within=pyomo.NonNegativeReals,
                            doc='Total storage size (MWh)')
    m.cap_sto_c_new = pyomo.Var(m.sto_tuples,
                                within=pyomo.NonNegativeReals,
                                doc='New storage size (MWh)')
    m.cap_sto_p = pyomo.Var(m.sto_tuples,
                            within=pyomo.NonNegativeReals,
                            doc='Total storage power (MW)')
    m.cap_sto_p_new = pyomo.Var(m.sto_tuples,
                                within=pyomo.NonNegativeReals,
                                doc='New  storage power (MW)')
    m.e_sto_in = pyomo.Var(m.tm,
                           m.sto_tuples,
                           within=pyomo.NonNegativeReals,
                           doc='Power flow into storage (MW) per timestep')
    m.e_sto_out = pyomo.Var(m.tm,
                            m.sto_tuples,
                            within=pyomo.NonNegativeReals,
                            doc='Power flow out of storage (MW) per timestep')
    m.e_sto_con = pyomo.Var(m.t,
                            m.sto_tuples,
                            within=pyomo.NonNegativeReals,
                            doc='Energy content of storage (MWh) in timestep')

    # demand side management
    m.dsm_up = pyomo.Var(m.tm,
                         m.dsm_site_tuples,
                         within=pyomo.NonNegativeReals,
                         doc='DSM upshift')
    m.dsm_down = pyomo.Var(m.dsm_down_tuples,
                           within=pyomo.NonNegativeReals,
                           doc='DSM downshift')

    # Equation declarations
    # equation bodies are defined in separate functions, referred to here by
    # their name in the "rule" keyword.

    # commodity
    m.res_vertex = pyomo.Constraint(
        m.tm,
        m.com_tuples,
        rule=res_vertex_rule,
        doc='storage + transmission + process + source + buy - sell == demand')
    m.res_stock_step = pyomo.Constraint(
        m.tm,
        m.com_tuples,
        rule=res_stock_step_rule,
        doc='stock commodity input per step <= commodity.maxperstep')
    m.res_stock_total = pyomo.Constraint(
        m.com_tuples,
        rule=res_stock_total_rule,
        doc='total stock commodity input <= commodity.max')
    m.res_sell_step = pyomo.Constraint(
        m.tm,
        m.com_tuples,
        rule=res_sell_step_rule,
        doc='sell commodity output per step <= commodity.maxperstep')
    m.res_sell_total = pyomo.Constraint(
        m.com_tuples,
        rule=res_sell_total_rule,
        doc='total sell commodity output <= commodity.max')
    m.res_buy_step = pyomo.Constraint(
        m.tm,
        m.com_tuples,
        rule=res_buy_step_rule,
        doc='buy commodity output per step <= commodity.maxperstep')
    m.res_buy_total = pyomo.Constraint(
        m.com_tuples,
        rule=res_buy_total_rule,
        doc='total buy commodity output <= commodity.max')
    m.res_env_step = pyomo.Constraint(
        m.tm,
        m.com_tuples,
        rule=res_env_step_rule,
        doc='environmental output per step <= commodity.maxperstep')
    m.res_env_total = pyomo.Constraint(
        m.com_tuples,
        rule=res_env_total_rule,
        doc='total environmental commodity output <= commodity.max')

    # process
    m.def_process_capacity = pyomo.Constraint(
        m.pro_tuples,
        rule=def_process_capacity_rule,
        doc='total process capacity = inst-cap + new capacity')
    m.def_process_input = pyomo.Constraint(
        m.tm,
        m.pro_input_tuples - m.pro_partial_input_tuples,
        rule=def_process_input_rule,
        doc='process input = process throughput * input ratio')
    m.def_process_output = pyomo.Constraint(
        m.tm, (m.pro_output_tuples - m.pro_partial_output_tuples -
               m.pro_timevar_output_tuples),
        rule=def_process_output_rule,
        doc='process output = process throughput * output ratio')
    m.def_intermittent_supply = pyomo.Constraint(
        m.tm,
        m.pro_input_tuples,
        rule=def_intermittent_supply_rule,
        doc='process output = process capacity * supim timeseries')
    m.res_process_throughput_by_capacity = pyomo.Constraint(
        m.tm,
        m.pro_tuples,
        rule=res_process_throughput_by_capacity_rule,
        doc='process throughput <= total process capacity')
    m.res_process_maxgrad_lower = pyomo.Constraint(
        m.tm,
        m.pro_maxgrad_tuples,
        rule=res_process_maxgrad_lower_rule,
        doc='throughput may not decrease faster than maximal gradient')
    m.res_process_maxgrad_upper = pyomo.Constraint(
        m.tm,
        m.pro_maxgrad_tuples,
        rule=res_process_maxgrad_upper_rule,
        doc='throughput may not increase faster than maximal gradient')
    m.res_process_capacity = pyomo.Constraint(
        m.pro_tuples,
        rule=res_process_capacity_rule,
        doc='process.cap-lo <= total process capacity <= process.cap-up')

    m.res_area = pyomo.Constraint(
        m.sit,
        rule=res_area_rule,
        doc='used process area <= total process area')

    m.res_sell_buy_symmetry = pyomo.Constraint(
        m.pro_input_tuples,
        rule=res_sell_buy_symmetry_rule,
        doc='power connection capacity must be symmetric in both directions')

    m.res_throughput_by_capacity_min = pyomo.Constraint(
        m.tm,
        m.pro_partial_tuples,
        rule=res_throughput_by_capacity_min_rule,
        doc='cap_pro * min-fraction <= tau_pro')
    m.def_partial_process_input = pyomo.Constraint(
        m.tm,
        m.pro_partial_input_tuples,
        rule=def_partial_process_input_rule,
        doc='e_pro_in = '
        ' cap_pro * min_fraction * (r - R) / (1 - min_fraction)'
        ' + tau_pro * (R - min_fraction * r) / (1 - min_fraction)')
    m.def_partial_process_output = pyomo.Constraint(
        m.tm, (m.pro_partial_output_tuples -
               (m.pro_partial_output_tuples & m.pro_timevar_output_tuples)),
        rule=def_partial_process_output_rule,
        doc='e_pro_out = '
        ' cap_pro * min_fraction * (r - R) / (1 - min_fraction)'
        ' + tau_pro * (R - min_fraction * r) / (1 - min_fraction)')
    m.def_process_timevar_output = pyomo.Constraint(
        m.tm, (m.pro_timevar_output_tuples -
               (m.pro_partial_output_tuples & m.pro_timevar_output_tuples)),
        rule=def_pro_timevar_output_rule,
        doc='e_pro_out = tau_pro * r_out * eff_factor')
    m.def_process_partial_timevar_output = pyomo.Constraint(
        m.tm,
        m.pro_partial_output_tuples & m.pro_timevar_output_tuples,
        rule=def_pro_partial_timevar_output_rule,
        doc='e_pro_out = tau_pro * r_out * eff_factor')

    # transmission
    m.def_transmission_capacity = pyomo.Constraint(
        m.tra_tuples,
        rule=def_transmission_capacity_rule,
        doc='total transmission capacity = inst-cap + new capacity')
    m.def_transmission_output = pyomo.Constraint(
        m.tm,
        m.tra_tuples,
        rule=def_transmission_output_rule,
        doc='transmission output = transmission input * efficiency')
    m.res_transmission_input_by_capacity = pyomo.Constraint(
        m.tm,
        m.tra_tuples,
        rule=res_transmission_input_by_capacity_rule,
        doc='transmission input <= total transmission capacity')
    m.res_transmission_capacity = pyomo.Constraint(
        m.tra_tuples,
        rule=res_transmission_capacity_rule,
        doc='transmission.cap-lo <= total transmission capacity <= '
        'transmission.cap-up')
    m.res_transmission_symmetry = pyomo.Constraint(
        m.tra_tuples,
        rule=res_transmission_symmetry_rule,
        doc='total transmission capacity must be symmetric in both directions')

    # storage
    m.def_storage_state = pyomo.Constraint(
        m.tm,
        m.sto_tuples,
        rule=def_storage_state_rule,
        doc='storage[t] = (1 - sd) * storage[t-1] + in * eff_i - out / eff_o')
    m.def_storage_power = pyomo.Constraint(
        m.sto_tuples,
        rule=def_storage_power_rule,
        doc='storage power = inst-cap + new power')
    m.def_storage_capacity = pyomo.Constraint(
        m.sto_tuples,
        rule=def_storage_capacity_rule,
        doc='storage capacity = inst-cap + new capacity')
    m.res_storage_input_by_power = pyomo.Constraint(
        m.tm,
        m.sto_tuples,
        rule=res_storage_input_by_power_rule,
        doc='storage input <= storage power')
    m.res_storage_output_by_power = pyomo.Constraint(
        m.tm,
        m.sto_tuples,
        rule=res_storage_output_by_power_rule,
        doc='storage output <= storage power')
    m.res_storage_state_by_capacity = pyomo.Constraint(
        m.t,
        m.sto_tuples,
        rule=res_storage_state_by_capacity_rule,
        doc='storage content <= storage capacity')
    m.res_storage_power = pyomo.Constraint(
        m.sto_tuples,
        rule=res_storage_power_rule,
        doc='storage.cap-lo-p <= storage power <= storage.cap-up-p')
    m.res_storage_capacity = pyomo.Constraint(
        m.sto_tuples,
        rule=res_storage_capacity_rule,
        doc='storage.cap-lo-c <= storage capacity <= storage.cap-up-c')
    m.res_initial_and_final_storage_state = pyomo.Constraint(
        m.t,
        m.sto_tuples,
        rule=res_initial_and_final_storage_state_rule,
        doc='storage content initial == and final >= storage.init * capacity')

    # costs
    m.def_costs = pyomo.Constraint(m.cost_type,
                                   rule=def_costs_rule,
                                   doc='main cost function by cost type')
    m.obj = pyomo.Objective(rule=obj_rule,
                            sense=pyomo.minimize,
                            doc='minimize(cost = sum of all cost types)')

    # demand side management
    m.def_dsm_variables = pyomo.Constraint(
        m.tm,
        m.dsm_site_tuples,
        rule=def_dsm_variables_rule,
        doc='DSMup * efficiency factor n == DSMdo (summed)')

    m.res_dsm_upward = pyomo.Constraint(
        m.tm,
        m.dsm_site_tuples,
        rule=res_dsm_upward_rule,
        doc='DSMup <= Cup (threshold capacity of DSMup)')

    m.res_dsm_downward = pyomo.Constraint(
        m.tm,
        m.dsm_site_tuples,
        rule=res_dsm_downward_rule,
        doc='DSMdo (summed) <= Cdo (threshold capacity of DSMdo)')

    m.res_dsm_maximum = pyomo.Constraint(
        m.tm,
        m.dsm_site_tuples,
        rule=res_dsm_maximum_rule,
        doc='DSMup + DSMdo (summed) <= max(Cup,Cdo)')

    m.res_dsm_recovery = pyomo.Constraint(
        m.tm,
        m.dsm_site_tuples,
        rule=res_dsm_recovery_rule,
        doc='DSMup(t, t + recovery time R) <= Cup * delay time L')

    m.res_global_co2_limit = pyomo.Constraint(
        rule=res_global_co2_limit_rule,
        doc='total co2 commodity output <= Global CO2 limit')

    if dual:
        m.dual = pyomo.Suffix(direction=pyomo.Suffix.IMPORT)
    return m