Skip to content

Commit bcd10e3

Browse files
feat: add function to merge low voltage to market node (#790)
* feat: add function to merge low voltage to market node * fix: update docstring * feat: add check if buses from map are in network * fix: refer to write default in docstring Co-authored-by: Daniel Rüdt <117752024+daniel-rdt@users.noreply.github.com> * fix: remove static check Co-authored-by: Daniel Rüdt <117752024+daniel-rdt@users.noreply.github.com> * refactor: iterate over branch components + use n.components * docs: add release note * docs: update comment Co-authored-by: Daniel Rüdt <117752024+daniel-rdt@users.noreply.github.com> --------- Co-authored-by: Daniel Rüdt <117752024+daniel-rdt@users.noreply.github.com>
1 parent 32ff08a commit bcd10e3

2 files changed

Lines changed: 78 additions & 0 deletions

File tree

doc/release_notes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
**Features**
1111

12+
* Merge low voltage and market nodes in the CBA ([#722](https://github.com/open-energy-transition/open-tyndp/pull/722)).
13+
1214
* Add Snakemake rules to launch the `PyPSA-Explorer` with pre-solved SB networks from previous releases ([#724](https://github.com/open-energy-transition/open-tyndp/pull/724)).
1315

1416
**Changes**

scripts/cba/simplify_sb_network.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Fixed optimal capacities from scenario building
99
- Hurdle costs on DC links
1010
- Extended primary fuel source capacities
11+
- Merge low and high voltage buses
1112
1213
**Inputs**
1314
@@ -62,6 +63,78 @@ def extend_primary_fuel_sources(
6263
n.generators.loc[gen_i, "p_nom"] = inf
6364

6465

66+
def move_bus_carrier_and_cleanup(
67+
n: pypsa.Network,
68+
from_carrier: str = "low voltage",
69+
busmap: pd.Series | None = None,
70+
) -> None:
71+
"""
72+
Reassign all components attached to buses of `from_carrier` onto their
73+
corresponding buses (as given by `busmap`), remove the now-empty
74+
`from_carrier` buses, and drop any lines/links that end up with
75+
bus0 == bus1 as a result.
76+
77+
Also drops redundant load-shedding generators sitting on `from_carrier`
78+
buses, since after reassignment they would duplicate the load-shedding
79+
generator already present at the target bus.
80+
81+
Parameters
82+
----------
83+
n : pypsa.Network
84+
Network to modify in place.
85+
from_carrier : str, default "low voltage"
86+
Bus carrier to remove (e.g. "low voltage").
87+
busmap : pandas.Series, optional
88+
Mapping of `from_carrier` bus name -> target bus name onto which its
89+
components should be reassigned. Defaults to `None`, in which case
90+
each bus is mapped onto the bus matching its `location` field, i.e.
91+
`n.buses.loc[n.buses.carrier == from_carrier, "location"]` is used.
92+
93+
Returns
94+
-------
95+
None
96+
Network is modified in place.
97+
"""
98+
# create busmap
99+
if busmap is None:
100+
busmap = n.buses.loc[n.buses.carrier == from_carrier, "location"]
101+
# check if buses exist in network
102+
if not busmap.isin(n.buses.index).all():
103+
missing = busmap[~busmap.isin(n.buses.index)]
104+
raise ValueError(f"Locations not found in n.buses.index: {missing.tolist()}")
105+
106+
# remove load shedding generators on low voltage buses to avoid having two
107+
# load shedding generators at market node
108+
load_shedding_gens = n.generators.index[
109+
(n.generators.bus.map(n.buses.carrier) == from_carrier)
110+
& (n.generators.carrier == "load")
111+
]
112+
n.remove("Generator", load_shedding_gens)
113+
114+
# reassign every component attached to the from_carrier buses
115+
for c in n.components[sorted(n.branch_components | n.one_port_components)]:
116+
static = c.static
117+
if static.empty:
118+
continue
119+
for port in c.ports:
120+
col = f"bus{port}"
121+
mask = static[col].isin(busmap.index)
122+
if mask.any():
123+
static.loc[mask, col] = static.loc[mask, col].map(busmap)
124+
125+
# remove carrier buses
126+
n.remove("Bus", busmap.index)
127+
128+
# drop branch components (e.g. lines/links) where bus0 == bus1 after remapping
129+
for c in n.components[sorted(n.branch_components)]:
130+
static = c.static
131+
if static.empty:
132+
continue
133+
to_remove = static.index[static.bus0 == static.bus1]
134+
if len(to_remove):
135+
n.remove(c.name, to_remove)
136+
137+
65138
if __name__ == "__main__":
66139
if "snakemake" not in globals():
67140
from scripts._helpers import mock_snakemake
@@ -96,6 +169,9 @@ def extend_primary_fuel_sources(
96169
# TODO: for DE/GA add merging of the two H2 zones
97170
# TODO: for DE/GA add EV electricity consumption from SB as fixed demand
98171

172+
# merge low voltage to market node
173+
move_bus_carrier_and_cleanup(n, from_carrier="low voltage")
174+
99175
# Save base network
100176
n.export_to_netcdf(snakemake.output.network)
101177
logger.info(f"Saved CBA base network to {snakemake.output.network}")

0 commit comments

Comments
 (0)