Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

**Features**

* Merge low voltage and market nodes in the CBA ([#722](https://github.com/open-energy-transition/open-tyndp/pull/722)).

* 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)).

**Changes**
Expand Down
76 changes: 76 additions & 0 deletions scripts/cba/simplify_sb_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Fixed optimal capacities from scenario building
- Hurdle costs on DC links
- Extended primary fuel source capacities
- Merge low and high voltage buses

**Inputs**

Expand Down Expand Up @@ -62,6 +63,78 @@ def extend_primary_fuel_sources(
n.generators.loc[gen_i, "p_nom"] = inf


def move_bus_carrier_and_cleanup(
n: pypsa.Network,
from_carrier: str = "low voltage",
busmap: pd.Series | None = None,
) -> None:
"""
Reassign all components attached to buses of `from_carrier` onto their
corresponding buses (as given by `busmap`), remove the now-empty
`from_carrier` buses, and drop any lines/links that end up with
bus0 == bus1 as a result.

Also drops redundant load-shedding generators sitting on `from_carrier`
buses, since after reassignment they would duplicate the load-shedding
generator already present at the target bus.

Parameters
----------
n : pypsa.Network
Network to modify in place.
from_carrier : str, default "low voltage"
Bus carrier to remove (e.g. "low voltage").
busmap : pandas.Series, optional
Mapping of `from_carrier` bus name -> target bus name onto which its
components should be reassigned. Defaults to `None`, in which case
each bus is mapped onto the bus matching its `location` field, i.e.
`n.buses.loc[n.buses.carrier == from_carrier, "location"]` is used.

Returns
-------
None
Network is modified in place.
"""
# create busmap
if busmap is None:
busmap = n.buses.loc[n.buses.carrier == from_carrier, "location"]
# check if buses exist in network
if not busmap.isin(n.buses.index).all():
missing = busmap[~busmap.isin(n.buses.index)]
raise ValueError(f"Locations not found in n.buses.index: {missing.tolist()}")

# remove load shedding generators on low voltage buses to avoid having two
# load shedding generators at market node
load_shedding_gens = n.generators.index[
(n.generators.bus.map(n.buses.carrier) == from_carrier)
& (n.generators.carrier == "load")
]
n.remove("Generator", load_shedding_gens)

# reassign every component attached to the from_carrier buses
for c in n.components[sorted(n.branch_components | n.one_port_components)]:
static = c.static
if static.empty:
continue
for port in c.ports:
col = f"bus{port}"
mask = static[col].isin(busmap.index)
if mask.any():
static.loc[mask, col] = static.loc[mask, col].map(busmap)

# remove carrier buses
n.remove("Bus", busmap.index)

# drop branch components (e.g. lines/links) where bus0 == bus1 after remapping
for c in n.components[sorted(n.branch_components)]:
static = c.static
if static.empty:
continue
to_remove = static.index[static.bus0 == static.bus1]
if len(to_remove):
n.remove(c.name, to_remove)


if __name__ == "__main__":
if "snakemake" not in globals():
from scripts._helpers import mock_snakemake
Expand Down Expand Up @@ -96,6 +169,9 @@ def extend_primary_fuel_sources(
# TODO: for DE/GA add merging of the two H2 zones
# TODO: for DE/GA add EV electricity consumption from SB as fixed demand

# merge low voltage to market node
move_bus_carrier_and_cleanup(n, from_carrier="low voltage")

# Save base network
n.export_to_netcdf(snakemake.output.network)
logger.info(f"Saved CBA base network to {snakemake.output.network}")