Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,7 @@ clustering:
remove_stubs_across_borders: false
cluster_network:
algorithm: kmeans
allow_ac_dc_mix: true
hac_features:
- wnd100m
- influx_direct
Expand Down
15 changes: 15 additions & 0 deletions config/schema.default.json
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,11 @@
],
"type": "string"
},
"allow_ac_dc_mix": {
"default": true,
"description": "Controls whether clustering is allowed to mix AC and DC buses within a cluster. If true, mixed clusters are coerced to AC before aggregation. If false, mixed clusters are kept separate.",
"type": "boolean"
},
"hac_features": {
"description": "List of meteorological variables contained in the weather data cutout that should be considered for hierarchical clustering.",
"items": {
Expand Down Expand Up @@ -5949,6 +5954,11 @@
],
"type": "string"
},
"allow_ac_dc_mix": {
"default": true,
"description": "Controls whether clustering is allowed to mix AC and DC buses within a cluster. If true, mixed clusters are coerced to AC before aggregation. If false, mixed clusters are kept separate.",
"type": "boolean"
},
"hac_features": {
"description": "List of meteorological variables contained in the weather data cutout that should be considered for hierarchical clustering.",
"items": {
Expand Down Expand Up @@ -12002,6 +12012,11 @@
],
"type": "string"
},
"allow_ac_dc_mix": {
"default": true,
"description": "Controls whether clustering is allowed to mix AC and DC buses within a cluster. If true, mixed clusters are coerced to AC before aggregation. If false, mixed clusters are kept separate.",
"type": "boolean"
},
"hac_features": {
"description": "List of meteorological variables contained in the weather data cutout that should be considered for hierarchical clustering.",
"items": {
Expand Down
2 changes: 2 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Release Notes

.. Upcoming Release
.. =================
* Fix: Prevent over-aggressive HVDC simplification in simplify_network for branched/multi-terminal DC topologies (e.g. UK/Shetland edge cases). Supernode detection now only collapses true chain nodes (degree 2) and preserves DC junctions (degree 3+) so branches are not dropped(https://github.com/PyPSA/pypsa-eur/pull/2147).

* The lockfile update workflow now excludes packages published within the last 7 days to reduce the risk of pulling in broken or yanked releases (https://github.com/PyPSA/pypsa-eur/pull/2130).

* The industry reference year and the ammonia production data have been updated to 2023 (https://github.com/PyPSA/pypsa-eur/pull/2103)
Expand Down
37 changes: 34 additions & 3 deletions scripts/cluster_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,33 @@ def clustering_for_n_clusters(
return clustering


def apply_carrier_mixing_policy(
n: pypsa.Network, busmap: pd.Series, allow_ac_dc_mix: bool
) -> pd.Series:
"""Apply carrier mixing policy to a busmap before clustering."""
Comment thread
bobbyxng marked this conversation as resolved.
Outdated
busmap = busmap.astype(str)
carrier_by_bus = n.buses.carrier.reindex(busmap.index).astype(str)

mixed_clusters = carrier_by_bus.groupby(busmap).nunique().loc[lambda s: s > 1].index

if allow_ac_dc_mix:
if len(mixed_clusters):
logger.warning(
"`allow_ac_dc_mix` is enabled. Coercing bus carrier to AC in %s mixed clusters.",
len(mixed_clusters),
)
mixed_bus_i = busmap.index[busmap.isin(mixed_clusters)]
n.buses.loc[mixed_bus_i, "carrier"] = "AC"
return busmap

if len(mixed_clusters):
logger.info(
"Splitting %s mixed AC/DC clusters by carrier before aggregation.",
len(mixed_clusters),
)
return busmap.str.cat(carrier_by_bus, sep="::")


def cluster_regions(
busmaps: tuple | list, regions: gpd.GeoDataFrame, with_country: bool = False
) -> gpd.GeoDataFrame:
Expand Down Expand Up @@ -516,7 +543,7 @@ def busmap_for_admin_regions(
buses_subset.to_crs(epsg=3857),
admin_regions.loc[admin_regions["country"] == country].to_crs(epsg=3857),
how="left",
)["admin"]
)["admin"].astype(str)

return buses["busmap"]

Expand Down Expand Up @@ -586,10 +613,11 @@ def update_bus_coordinates(
admin_regions["y"] = admin_regions["poi"].y

busmap_df = pd.DataFrame(busmap)
busmap_df["admin"] = busmap_df["busmap"].astype(str).str.split("::", n=1).str[0]
Comment thread
bobbyxng marked this conversation as resolved.
Outdated
busmap_df = pd.merge(
busmap_df,
admin_regions[["x", "y"]],
left_on="busmap",
left_on="admin",
right_index=True,
how="left",
)
Expand All @@ -603,7 +631,7 @@ def update_bus_coordinates(
if "snakemake" not in globals():
from scripts._helpers import mock_snakemake

snakemake = mock_snakemake("cluster_network", clusters=60)
snakemake = mock_snakemake("cluster_network", clusters=50)
configure_logging(snakemake)
set_scenario_config(snakemake)

Expand Down Expand Up @@ -686,6 +714,9 @@ def update_bus_coordinates(
features=features,
)

allow_ac_dc_mix = params.cluster_network.get("allow_ac_dc_mix", False)
Comment thread
bobbyxng marked this conversation as resolved.
Outdated
busmap = apply_carrier_mixing_policy(n, busmap, allow_ac_dc_mix)

clustering = clustering_for_n_clusters(
n,
busmap,
Expand Down
4 changes: 4 additions & 0 deletions scripts/lib/validation/config/clustering.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ class _ClusterNetworkConfig(BaseModel):
"kmeans",
description="Clustering algorithm to use.",
)
allow_ac_dc_mix: bool = Field(
True,
description="Controls whether clustering is allowed to mix AC and DC buses within a cluster. If true, mixed clusters are coerced to AC before aggregation. If false, mixed clusters are kept separate.",
)
hac_features: list[str] = Field(
default_factory=lambda: ["wnd100m", "influx_direct"],
description="List of meteorological variables contained in the weather data cutout that should be considered for hierarchical clustering.",
Expand Down
7 changes: 4 additions & 3 deletions scripts/simplify_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,14 @@ def split_links(nodes, added_supernodes):

seen = set()

# Supernodes are endpoints of links, identified by having lass then two neighbours or being an AC Bus
# An example for the latter is if two different links are connected to the same AC bus.
# Supernodes are buses that are not simple chain nodes within the component.
# A chain node has degree 2 inside the component; endpoints (degree 1),
# junctions (degree >=3), and AC buses are kept as supernodes.
supernodes = {
m
for m in nodes
if (
(len(G.adj[m]) < 2 or (set(G.adj[m]) - nodes))
(len(set(G.adj[m]) & nodes) != 2)
or (n.buses.loc[m, "carrier"] == "AC")
or (m in added_supernodes)
)
Expand Down
Loading