Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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 lines/links where bus0 == bus1 after remapping
Comment thread
lisazeyen marked this conversation as resolved.
Outdated
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}")