The extra funcionalities for the native pypsa optimization code are mostly using the function of the pypsa.linopt
model. Here we show how you can recycle large parts of your code by using the compatibility functions from the pypsa.optimization.compat
module.
These are
define_variables
define_constraints
get_var
linexpr
You might want to use them if you have extra_functionalities
written for the native optimization code. However, expect some hick-ups, as some operations might behave differently.
Let's import pypsa and the compat functions:
import pypsa
from pypsa.optimization.compat import get_var, define_constraints, linexpr
We load the same network from the previous section into memory:
n = pypsa.examples.ac_dc_meshed(from_master=True)
n.generators.loc[n.generators.carrier == "gas", "p_nom_extendable"] = False
n.generators.loc[n.generators.carrier == "gas", "ramp_limit_down"] = 0.2
n.generators.loc[n.generators.carrier == "gas", "ramp_limit_up"] = 0.2
n.add(
"StorageUnit",
"su",
bus="Manchester",
marginal_cost=10,
inflow=50,
p_nom_extendable=True,
capital_cost=10,
p_nom=2000,
efficiency_dispatch=0.5,
cyclic_state_of_charge=True,
state_of_charge_initial=1000,
)
n.add(
"StorageUnit",
"su2",
bus="Manchester",
marginal_cost=10,
p_nom_extendable=True,
capital_cost=50,
p_nom=2000,
efficiency_dispatch=0.5,
carrier="gas",
cyclic_state_of_charge=False,
state_of_charge_initial=1000,
)
n.storage_units_t.state_of_charge_set.loc[n.snapshots[7], "su"] = 100
n.add("Bus", "storebus", carrier="hydro", x=-5, y=55)
n.madd(
"Link",
["battery_power", "battery_discharge"],
"",
bus0=["Manchester", "storebus"],
bus1=["storebus", "Manchester"],
p_nom=100,
efficiency=0.9,
p_nom_extendable=True,
p_nom_max=1000,
)
n.madd(
"Store",
["store"],
bus="storebus",
e_nom=2000,
e_nom_extendable=True,
marginal_cost=10,
capital_cost=10,
e_nom_max=5000,
e_initial=100,
e_cyclic=True,
);
And define the extra functionalities as we defined them for the native code in here
def minimal_state_of_charge(n, snapshots):
vars_soc = get_var(n, "StorageUnit", "state_of_charge")
lhs = linexpr((1, vars_soc))
define_constraints(n, lhs, ">", 50, "StorageUnit", "soc_lower_bound")
With the compat functions, this will work as expected. Let's go on to the next one.
def fix_link_cap_ratio(n, snapshots):
vars_link = get_var(n, "Link", "p_nom")
eff = n.links.at["battery_power", "efficiency"]
lhs = linexpr(
(1, vars_link.loc["battery_power"]), (-eff, vars_link.loc["battery_discharge"])
)
define_constraints(n, lhs, "=", 0, "battery_discharge", attr="fixratio")
This function as well should not make any problems. Let's go on.
def fix_bus_production(n, snapshots):
total_demand = n.loads_t.p_set.sum().sum()
prod_per_bus = (
linexpr((1, get_var(n, "Generator", "p")))
.groupby(n.generators.bus, axis=1)
.apply(join_exprs)
)
define_constraints(
n, prod_per_bus, ">=", total_demand / 5, "Bus", "production_share"
)
Here, we come into difficult terrain. The groupby function won't work since the linexpr
functions returns some sort of a xarray object (a LinearExpression
object, derived from xarray.Dataset
).
Instead, we have to rewrite parts:
axis
argumentdef fix_bus_production(n, snapshots):
total_demand = n.loads_t.p_set.sum().sum()
prod_per_bus = (
linexpr((1, get_var(n, "Generator", "p")))
.groupby(n.generators.bus.to_xarray())
.sum()
.sum()
)
define_constraints(
n, prod_per_bus, ">=", total_demand / 5, "Bus", "production_share"
)
def extra_functionalities(n, snapshots):
minimal_state_of_charge(n, snapshots)
fix_link_cap_ratio(n, snapshots)
fix_bus_production(n, snapshots)
n.optimize(
extra_functionality=extra_functionalities,
)