Esempio n. 1
0
def reserve_margin_constraint_rule(backend_model, carrier):
    """
    Enforces a system reserve margin per carrier.

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc\\_tech\\_carriers\\_supply\\_all} energy_{cap}(loc::tech::carrier, timestep_{max\\_demand})
            \\geq
            \\sum_{loc::tech::carrier \\in loc\\_tech\\_carriers\\_demand} carrier_{con}(loc::tech::carrier, timestep_{max\\_demand})
            \\times -1 \\times \\frac{1}{time\\_resolution_{max\\_demand}}
            \\times (1 + reserve\\_margin)

    """

    reserve_margin = get_param(backend_model, "reserve_margin", carrier)
    max_demand_timestep = backend_model.max_demand_timesteps[carrier]
    max_demand_time_res = backend_model.timestep_resolution[
        max_demand_timestep]
    techs = [
        idx[-1] for idx in backend_model.carrier["out", carrier, :].index()
    ]
    return po.quicksum(  # Sum all supply capacity for this carrier
        backend_model.energy_cap[node, tech]
        for node, tech in backend_model.energy_cap._index if tech in techs
    ) >= po.quicksum(  # Sum all demand for this carrier and timestep
        backend_model.carrier_con[carrier, node, tech, max_demand_timestep]
        for tech in techs for node in backend_model.nodes if
        [carrier, node, tech, max_demand_timestep] in backend_model.carrier_con
        ._index) * -1 * (1 / max_demand_time_res) * (1 + reserve_margin)
Esempio n. 2
0
def create_linear_dual_from_matrix_repn(c, b, P, d):
    blk = Block()
    n, m = P.shape
    # Add dual variables
    blk.var = Var(range(n), within=NonNegativeReals)
    # Dual objective
    blk.obj = Constraint(expr=quicksum(d[j] * b.var[j] for j in range(n)) <= b)
    # Dual constraints
    blk.cons = ConstraintList()
    for i in range(m):
        blk.cons.add(quicksum(P[i, j] * b.var[j] for j in range(n)) == c[i])

    return blk
Esempio n. 3
0
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
Esempio n. 4
0
    def _apply_to(self, model):
        """Apply the transformation."""
        m = model

        for constr in m.component_data_objects(
                ctype=Constraint, active=True, descend_into=True):
            if not constr.body.polynomial_degree() == 1:
                continue  # we currently only process linear constraints
            repn = generate_standard_repn(constr.body)

            # get the index of all nonzero coefficient variables
            nonzero_vars_indx = [
                i for i, _ in enumerate(repn.linear_vars)
                if not repn.linear_coefs[i] == 0
            ]
            const = repn.constant

            # reconstitute the constraint, including only variable terms with
            # nonzero coefficients
            constr_body = quicksum(repn.linear_coefs[i] * repn.linear_vars[i]
                                   for i in nonzero_vars_indx) + const
            if constr.equality:
                constr.set_value(constr_body == constr.upper)
            elif constr.has_lb() and not constr.has_ub():
                constr.set_value(constr_body >= constr.lower)
            elif constr.has_ub() and not constr.has_lb():
                constr.set_value(constr_body <= constr.upper)
            else:
                # constraint is a bounded inequality of form a <= x <= b.
                constr.set_value(EXPR.inequality(
                    constr.lower, constr_body, constr.upper))
Esempio n. 5
0
 def test_linear_expression(self):
     m = ConcreteModel()
     m.x = Var(bounds=(1, 2), initialize=1)
     with self.assertRaises(NotImplementedError):
         mc_expr = mc(quicksum([m.x, m.x], linear=True))
         self.assertEqual(mc_expr.lower(), 2)
         self.assertEqual(mc_expr.upper(), 4)
Esempio n. 6
0
 def test_linear_expression(self):
     m = ConcreteModel()
     m.x = Var(bounds=(1, 2), initialize=1)
     with self.assertRaises(NotImplementedError):
         mc_expr = mc(quicksum([m.x, m.x], linear=True))
         self.assertEqual(mc_expr.lower(), 2)
         self.assertEqual(mc_expr.upper(), 4)
Esempio n. 7
0
def carrier_production_max_conversion_plus_milp_constraint_rule(
        backend_model, node, tech, timestep):
    """
    Set maximum carrier production of conversion_plus MILP techs

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}}
            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\leq energy_{cap, per unit}(loc::tech) \\times timestep\\_resolution(timestep)
            \\times \\boldsymbol{operating\\_units}(loc::tech, timestep)
            \\times \\eta_{parasitic}(loc::tech, timestep)
            \\quad \\forall loc::tech \\in loc::techs_{milp, conversion^{+}}, \\forall timestep \\in timesteps

    """
    timestep_resolution = backend_model.timestep_resolution[timestep]
    energy_cap = get_param(backend_model, "energy_cap_per_unit", (node, tech))
    carriers_out = backend_model.carrier["out", :, tech].index()

    carrier_prod = po.quicksum(backend_model.carrier_prod[idx[1], node, tech,
                                                          timestep]
                               for idx in carriers_out)

    return carrier_prod <= (backend_model.operating_units[node, tech, timestep]
                            * timestep_resolution * energy_cap)
Esempio n. 8
0
def carrier_production_min_conversion_plus_constraint_rule(
        backend_model, node, tech, timestep):
    """
    Set minimum conversion_plus carrier production.

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}}
            \\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)
            \\leq \\boldsymbol{energy_{cap}}(loc::tech) \\times timestep\\_resolution(timestep)
            \\times energy_{cap, min use}(loc::tech)
            \\quad \\forall loc::tech \\in loc::techs_{conversion^{+}},
            \\forall timestep \\in timesteps
    """

    timestep_resolution = backend_model.timestep_resolution[timestep]
    min_use = get_param(backend_model, "energy_cap_min_use",
                        (node, tech, timestep))

    carriers_out = backend_model.carrier["out", :, tech].index()

    carrier_prod = po.quicksum(backend_model.carrier_prod[idx[1], node, tech,
                                                          timestep]
                               for idx in carriers_out)

    return carrier_prod >= (timestep_resolution *
                            backend_model.energy_cap[node, tech] * min_use)
Esempio n. 9
0
def energy_capacity_systemwide_constraint_rule(backend_model, tech):
    """
    Set constraints to limit the capacity of a single technology type across all locations in the model.

    The first valid case is applied:

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc}\\boldsymbol{energy_{cap}}(loc::tech)
            \\begin{cases}
                = energy_{cap, equals, systemwide}(loc::tech),&
                    \\text{if } energy_{cap, equals, systemwide}(loc::tech)\\\\
                \\leq energy_{cap, max, systemwide}(loc::tech),&
                    \\text{if } energy_{cap, max, systemwide}(loc::tech)\\\\
                \\text{unconstrained},& \\text{otherwise}
            \\end{cases}
            \\forall tech \\in techs

    """

    max_systemwide = get_param(backend_model, "energy_cap_max_systemwide",
                               tech)
    equals_systemwide = get_param(backend_model,
                                  "energy_cap_equals_systemwide", tech)
    energy_cap = po.quicksum(
        backend_model.energy_cap[node, tech] for node in backend_model.nodes
        if [node, tech] in backend_model.energy_cap._index)
    if not invalid(equals_systemwide):
        return energy_cap == equals_systemwide
    else:
        return energy_cap <= max_systemwide
Esempio n. 10
0
    def _apply_to(self, model):
        """Apply the transformation."""
        m = model

        for constr in m.component_data_objects(
                ctype=Constraint, active=True, descend_into=True):
            if not constr.body.polynomial_degree() == 1:
                continue  # we currently only process linear constraints
            repn = generate_standard_repn(constr.body)

            # get the index of all nonzero coefficient variables
            nonzero_vars_indx = [
                i for i, _ in enumerate(repn.linear_vars)
                if not repn.linear_coefs[i] == 0
            ]
            const = repn.constant

            # reconstitute the constraint, including only variable terms with
            # nonzero coefficients
            constr_body = quicksum(repn.linear_coefs[i] * repn.linear_vars[i]
                                   for i in nonzero_vars_indx) + const
            if constr.equality:
                constr.set_value(constr_body == constr.upper)
            elif constr.has_lb() and not constr.has_ub():
                constr.set_value(constr_body >= constr.lower)
            elif constr.has_ub() and not constr.has_lb():
                constr.set_value(constr_body <= constr.upper)
            else:
                # constraint is a bounded inequality of form a <= x <= b.
                constr.set_value(EXPR.inequality(
                    constr.lower, constr_body, constr.upper))
Esempio n. 11
0
 def _sum(var_name):
     if hasattr(backend_model, var_name):
         return po.quicksum(
             getattr(backend_model, var_name)[node, tech]
             for node in backend_model.nodes
             if [node, tech] in getattr(backend_model, var_name)._index)
     else:
         return 0
Esempio n. 12
0
def balance_conversion_plus_primary_constraint_rule(backend_model, node, tech,
                                                    timestep):
    """
    Balance energy carrier consumption and production for carrier_in and carrier_out

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}}
            \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, `out')} =
            -1 * \\sum_{loc::tech::carrier \\in loc::tech::carriers_{in}} (
            \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)
            * carrier\\_ratio(loc::tech::carrier, `in') * \\eta_{energy}(loc::tech, timestep))
            \\quad \\forall loc::tech \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps
    """

    carriers_out = backend_model.carrier["out", :, tech].index()
    carriers_in = backend_model.carrier["in", :, tech].index()

    energy_eff = get_param(backend_model, "energy_eff", (node, tech, timestep))

    carrier_prod = []
    for idx in carriers_out:
        carrier = idx[1]
        carrier_ratio = get_param(backend_model, "carrier_ratios",
                                  ("out", carrier, node, tech, timestep))
        if po.value(carrier_ratio) != 0:
            carrier_prod.append(
                backend_model.carrier_prod[carrier, node, tech, timestep] /
                carrier_ratio)

    carrier_con = po.quicksum(
        backend_model.carrier_con[idx[1], node, tech, timestep] *
        get_param(backend_model, "carrier_ratios", ("in", idx[1], node, tech,
                                                    timestep))
        for idx in carriers_in)

    return po.quicksum(carrier_prod) == -1 * carrier_con * energy_eff
Esempio n. 13
0
    def obj_rule(backend_model):
        if backend_model.__calliope_run_config.get("ensure_feasibility",
                                                   False):
            unmet_demand = (po.quicksum(
                (backend_model.unmet_demand[carrier, node, timestep] -
                 backend_model.unused_supply[carrier, node, timestep]) *
                backend_model.timestep_weights[timestep]
                for [carrier, node, timestep] in backend_model.carriers *
                backend_model.nodes * backend_model.timesteps
                if [carrier, node, timestep] in
                backend_model.unmet_demand._index) * backend_model.bigM)
            if backend_model.objective_sense == "maximize":
                unmet_demand *= -1
        else:
            unmet_demand = 0

        return (po.quicksum(
            po.quicksum(
                backend_model.cost[class_name, node, tech]
                for [node, tech] in backend_model.nodes * backend_model.techs
                if [class_name, node, tech] in backend_model.cost._index) *
            weight for class_name, weight in
            backend_model.objective_cost_class.items()) + unmet_demand)
Esempio n. 14
0
def resource_area_capacity_per_loc_constraint_rule(backend_model, node):
    """
    Set upper bound on use of area for all locations which have `available_area`
    constraint set. Does not consider resource_area applied to demand technologies

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{tech} \\boldsymbol{resource_{area}}(loc::tech) \\leq available\\_area
            \\quad \\forall loc \\in locs \\text{ if } available\\_area(loc)
    """
    available_area = backend_model.available_area[node]

    return (po.quicksum(backend_model.resource_area[node, tech]
                        for tech in backend_model.techs
                        if [node, tech] in backend_model.resource_area._index)
            <= available_area)
Esempio n. 15
0
    def get_mock_model_with_priorities(self):
        m = pmo.block()
        m.x = pmo.variable(domain=Integers)
        m.s = range(10)

        m.y = pmo.variable_list(pmo.variable(domain=Integers) for _ in m.s)

        m.o = pmo.objective(expr=m.x + sum(m.y), sense=minimize)
        m.c = pmo.constraint(expr=m.x >= 1)
        m.c2 = pmo.constraint(expr=quicksum(m.y[i] for i in m.s) >= 10)

        m.priority = pmo.suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)
        m.direction = pmo.suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)

        m.priority[m.x] = 1
        m.priority[m.y] = 2
        m.direction[m.y] = BranchDirection.down
        m.direction[m.y[-1]] = BranchDirection.up
        return m
Esempio n. 16
0
    def get_mock_model_with_priorities(self):
        m = ConcreteModel()
        m.x = Var(domain=Integers)
        m.s = RangeSet(10)
        m.y = Var(m.s, domain=Integers)
        m.o = Objective(expr=m.x + sum(m.y), sense=minimize)
        m.c = Constraint(expr=m.x >= 1)
        m.c2 = Constraint(expr=quicksum(m.y[i] for i in m.s) >= 10)

        m.priority = Suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)
        m.direction = Suffix(direction=Suffix.EXPORT, datatype=Suffix.INT)

        m.priority.set_value(m.x, 1)

        # Ensure tests work for both options of `expand`
        m.priority.set_value(m.y, 2, expand=False)
        m.direction.set_value(m.y, BranchDirection.down, expand=True)

        m.direction.set_value(m.y[10], BranchDirection.up)
        return m
Esempio n. 17
0
 def generate_cons_from_lib(self, param):
     for i, row in enumerate(self.mat):
         yield (None,
                quicksum(row[j]*param[ind]
                         for j, ind in enumerate(param)),
                self.rhs[i])
Esempio n. 18
0
 def _sum(var_name):
     return po.quicksum(
         getattr(backend_model, var_name)[carrier, node, tech, timestep]
         for carrier in backend_model.carriers
         if [carrier, node, tech, timestep] in getattr(
             backend_model, var_name)._index)
Esempio n. 19
0
 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"))
Esempio n. 20
0
def balance_conversion_plus_non_primary_constraint_rule(
        backend_model, tier, node, tech, timestep):
    """
    Force all carrier_in_2/carrier_in_3 and carrier_out_2/carrier_out_3 to follow
    carrier_in and carrier_out (respectively).

    If `tier` in ['out_2', 'out_3']:

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{out}} (
            \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, `out')} =
            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{tier}} (
            \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, tier)}
            \\quad \\forall \\text { tier } \\in [`out_2', `out_3'], \\forall loc::tech
                \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps

    If `tier` in ['in_2', 'in_3']:

    .. container:: scrolling-wrapper

        .. math::

            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{in}}
            \\frac{\\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, `in')} =
            \\sum_{loc::tech::carrier \\in loc::tech::carriers_{tier}}
            \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{
                carrier\\_ratio(loc::tech::carrier, tier)}
            \\quad \\forall \\text{ tier } \\in [`in_2', `in_3'], \\forall loc::tech
                \\in loc::techs_{conversion^{+}}, \\forall timestep \\in timesteps
    """
    primary_tier, decision_variable = get_conversion_plus_io(
        backend_model, tier)

    carriers_1 = backend_model.carrier[primary_tier, :, tech].index()
    carriers_2 = backend_model.carrier[tier, :, tech].index()

    c_1 = []
    c_2 = []
    for idx in carriers_1:
        carrier_ratio_1 = get_param(
            backend_model,
            "carrier_ratios",
            (primary_tier, idx[1], node, tech, timestep),
        )
        if po.value(carrier_ratio_1) != 0:
            c_1.append(decision_variable[idx[1], node, tech, timestep] /
                       carrier_ratio_1)
    for idx in carriers_2:
        carrier_ratio_2 = get_param(backend_model, "carrier_ratios",
                                    (tier, idx[1], node, tech, timestep))
        if po.value(carrier_ratio_2) != 0:
            c_2.append(decision_variable[idx[1], node, tech, timestep] /
                       carrier_ratio_2)
    if len(c_2) == 0:
        return po.Constraint.Skip
    else:
        return po.quicksum(c_1) == po.quicksum(c_2)
Esempio n. 21
0
 def new_constraint_rule(backend_model, node, tech, timestep):
     carrier_con = backend_model.carrier_con[:, node, tech, timestep]
     timestep_resolution = backend_model.timestep_resolution[timestep]
     return po.quicksum(carrier_con) * 2 >= (
         -1 * backend_model.energy_cap[node, tech] * timestep_resolution
     )
Esempio n. 22
0
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