-
Notifications
You must be signed in to change notification settings - Fork 266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce PTES and TES constraints #1546
base: master
Are you sure you want to change the base?
Changes from 15 commits
55f4d0e
6f684d3
2f67385
8dedc43
2a44a18
ca2d24c
3576274
e40fcf6
a16db94
b46ec98
ee3b5f9
84ebe30
df3fc81
9662bf2
599867d
69e0809
68740c6
cf9c4e4
b538ee7
ef05a0e
ec0f962
7933ec2
8ab2e84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ | |
import sys | ||
from typing import Any | ||
|
||
import linopy | ||
import numpy as np | ||
import pandas as pd | ||
import pypsa | ||
|
@@ -794,6 +795,74 @@ def add_operational_reserve_margin(n, sns, config): | |
n.model.add_constraints(lhs <= rhs, name="Generator-p-reserve-upper") | ||
|
||
|
||
def add_TES_etpr_constraints(n): | ||
""" | ||
Add a constraint for each TES storage unit enforcing: | ||
Store-e_nom - etpr * Link-p_nom == 0 | ||
|
||
This constraint is only applied if both: | ||
- n.config["sector"]["heating"] is True | ||
- n.config["sector"]["tes"] is True | ||
""" | ||
sector_config = n.config.get("sector", {}) | ||
if not (sector_config.get("heating", False) and sector_config.get("tes", False)): | ||
return | ||
|
||
tes_charger_bool = n.links.index.str.contains( | ||
"water tanks charger|water pits charger" | ||
) | ||
tes_store_bool = n.stores.index.str.contains("water tanks|water pits") | ||
|
||
chargers_ext = n.links[tes_charger_bool].query("p_nom_extendable").index | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we use more descriptive variable names? (It's okay if they get long) |
||
tes_ext = n.stores[tes_store_bool].query("e_nom_extendable").index | ||
etpr_values = n.links.loc[chargers_ext, "etpr"].values | ||
|
||
linear_expr_list = [] | ||
for discharger, tes, etpr_value in zip(chargers_ext, tes_ext, etpr_values): | ||
char_var = n.model["Link-p_nom"].loc[discharger] | ||
store_var = n.model["Store-e_nom"].loc[tes] | ||
linear_expr = store_var - etpr_value * char_var | ||
linear_expr_list.append(linear_expr) | ||
|
||
# Merge the individual expressions | ||
merged_expr = linopy.expressions.merge( | ||
linear_expr_list, dim="Store-ext, Link-ext", cls=type(linear_expr_list[0]) | ||
) | ||
|
||
n.model.add_constraints(merged_expr == 0, name="TES_etpr") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't we simplify the two functions to something like this:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The challenge is that vectorizing these operations leads to unintended broadcasting. Since we have two different variables, p_nom and e_nom, vectorized multiplication would result in a 24x24 matrix if there are 24 chargers and 24 stores. This mismatch means the constraint wouldn’t correctly associate each TES store with its corresponding charger. |
||
|
||
|
||
def add_TES_charger_ratio_constraints(n): | ||
""" | ||
Add constraints ensuring that for each TES unit, the charger and discharger are sized the same. | ||
|
||
Link-p_nom(charger) - efficiency * Link-p_nom(discharger) == 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add a more extensive docstring and type hinting for the input here. |
||
|
||
This constraint is only applied if both: | ||
- n.config["sector"]["heating"] is True | ||
- n.config["sector"]["tes"] is True | ||
""" | ||
sector_config = n.config.get("sector", {}) | ||
if not (sector_config.get("heating", False) and sector_config.get("tes", False)): | ||
return | ||
|
||
discharger_bool = n.links.index.str.contains( | ||
"water tanks discharger|water pits discharger" | ||
) | ||
charger_bool = n.links.index.str.contains("water tanks charger|water pits charger") | ||
|
||
dischargers_ext = n.links[discharger_bool].query("p_nom_extendable").index | ||
chargers_ext = n.links[charger_bool].query("p_nom_extendable").index | ||
|
||
eff = n.links.efficiency[dischargers_ext].values | ||
lhs = ( | ||
n.model["Link-p_nom"].loc[chargers_ext] | ||
- n.model["Link-p_nom"].loc[dischargers_ext] * eff | ||
) | ||
|
||
n.model.add_constraints(lhs == 0, name="TES_charger_ratio") | ||
|
||
|
||
def add_battery_constraints(n): | ||
""" | ||
Add constraint ensuring that charger = discharger, i.e. | ||
|
@@ -997,6 +1066,8 @@ def extra_functionality( | |
): | ||
add_solar_potential_constraints(n, config) | ||
|
||
add_TES_charger_ratio_constraints(n) | ||
add_TES_etpr_constraints(n) | ||
add_battery_constraints(n) | ||
add_lossy_bidirectional_link_constraints(n) | ||
add_pipe_retrofit_constraint(n) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a more extensive docstring and type hinting for the input here.