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)
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
def cost_expression_rule(backend_model, cost, node, tech): """ Combine investment and time varying costs into one cost per technology. .. container:: scrolling-wrapper .. math:: \\boldsymbol{cost}(cost, loc::tech) = \\boldsymbol{cost_{investment}}(cost, loc::tech) + \\sum_{timestep \\in timesteps} \\boldsymbol{cost_{var}}(cost, loc::tech, timestep) """ if loc_tech_is_in(backend_model, (cost, node, tech), "cost_investment_index"): cost_investment = backend_model.cost_investment[cost, node, tech] else: cost_investment = 0 if hasattr(backend_model, "cost_var"): cost_var = po.quicksum( backend_model.cost_var[cost, node, tech, timestep] for timestep in backend_model.timesteps if [cost, node, tech, timestep] in backend_model.cost_var._index) else: cost_var = 0 return cost_investment + cost_var
def _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))
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)
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)
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)
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
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
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
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)
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)
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
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
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])
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)
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"))
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)
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 )
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