From 6228a4672302e52fc9c96435e7109b3378b10295 Mon Sep 17 00:00:00 2001 From: daniel-rdt Date: Fri, 17 Jul 2026 13:07:10 +0200 Subject: [PATCH 1/4] fix: restore correct assigned countries for virtual tyndp nodes in base_network.py --- scripts/base_network.py | 22 ++++++++++++++++++++++ scripts/build_tyndp_network.py | 2 ++ 2 files changed, 24 insertions(+) diff --git a/scripts/base_network.py b/scripts/base_network.py index 5985d7b82d..a0f4ae9950 100644 --- a/scripts/base_network.py +++ b/scripts/base_network.py @@ -42,6 +42,7 @@ get_snapshots, set_scenario_config, ) +from scripts.build_tyndp_network import AC_VIRTUAL_NODES PD_GE_2_2 = parse(pd.__version__) >= Version("2.2") @@ -560,6 +561,25 @@ def prefer_voltage(x, which): return buses +def _restore_virtual_node_countries_tyndp( + n: pypsa.Network, buses: pd.DataFrame +) -> None: + """ + Restore the declared country of TYNDP virtual nodes instead of coordinate-based country assignment + from `_set_countries_and_substations`. + + Parameters + ---------- + n : pypsa.Network + Network whose `buses` country assignment is corrected in place. + buses : pd.DataFrame + Buses as loaded in `_load_buses`, holding the countries declared in + build_tyndp_network before the coordinate-based reassignment. + """ + nodes = n.buses.index.intersection(AC_VIRTUAL_NODES) + n.buses.loc[nodes, "country"] = buses.loc[nodes, "country"] + + def _replace_b2b_converter_at_country_border_by_link(n): # Affects only the B2B converter in Lithuania at the Polish border at the moment buscntry = n.buses.country @@ -752,6 +772,8 @@ def base_network( _set_countries_and_substations(n, config, country_shapes, offshore_shapes) + _restore_virtual_node_countries_tyndp(n, buses) + _set_links_underwater_fraction(n, offshore_shapes) _replace_b2b_converter_at_country_border_by_link(n) diff --git a/scripts/build_tyndp_network.py b/scripts/build_tyndp_network.py index 004877d955..489f2bb5bf 100644 --- a/scripts/build_tyndp_network.py +++ b/scripts/build_tyndp_network.py @@ -78,6 +78,8 @@ "UK": "GB", } +AC_VIRTUAL_NODES = ["ITCO", "ITVI"] + IBFI_COORD = (63.0, 25.0) From 64875c5971d3148e6b1f1de7672961225efc2524 Mon Sep 17 00:00:00 2001 From: daniel-rdt Date: Fri, 17 Jul 2026 13:18:20 +0200 Subject: [PATCH 2/4] doc: add release note --- doc/release_notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/release_notes.md b/doc/release_notes.md index 5cf6f67128..f7a66b294e 100644 --- a/doc/release_notes.md +++ b/doc/release_notes.md @@ -39,6 +39,8 @@ * Extend `tyndp-archive` support to integrate three new datasets (`desnz_electricity_consumption`, `jrc_energy_atlas`, `ons_lad`) and two new versions (`nitrogen_statistics`, `synthetic_electricity_demand`) ([#758](https://github.com/open-energy-transition/open-tyndp/pull/758)). +* Restore correct assigned countries for virtual TYNDP nodes ([#794](https://github.com/open-energy-transition/open-tyndp/pull/794)). + **Documentation** From fb69f8f912afe0e0c5730c9d911e684bd4eca7fe Mon Sep 17 00:00:00 2001 From: daniel-rdt Date: Mon, 20 Jul 2026 18:07:22 +0200 Subject: [PATCH 3/4] feat: generalise adding virtual nodes in build_tyndp_network.py --- scripts/base_network.py | 4 +- scripts/build_tyndp_network.py | 80 ++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/scripts/base_network.py b/scripts/base_network.py index a0f4ae9950..65eb6e4879 100644 --- a/scripts/base_network.py +++ b/scripts/base_network.py @@ -42,7 +42,7 @@ get_snapshots, set_scenario_config, ) -from scripts.build_tyndp_network import AC_VIRTUAL_NODES +from scripts.build_tyndp_network import AC_VIRTUAL_NODES_IT PD_GE_2_2 = parse(pd.__version__) >= Version("2.2") @@ -576,7 +576,7 @@ def _restore_virtual_node_countries_tyndp( Buses as loaded in `_load_buses`, holding the countries declared in build_tyndp_network before the coordinate-based reassignment. """ - nodes = n.buses.index.intersection(AC_VIRTUAL_NODES) + nodes = n.buses.index.intersection(AC_VIRTUAL_NODES_IT) n.buses.loc[nodes, "country"] = buses.loc[nodes, "country"] diff --git a/scripts/build_tyndp_network.py b/scripts/build_tyndp_network.py index 489f2bb5bf..9b35da2634 100644 --- a/scripts/build_tyndp_network.py +++ b/scripts/build_tyndp_network.py @@ -78,7 +78,10 @@ "UK": "GB", } -AC_VIRTUAL_NODES = ["ITCO", "ITVI"] +AC_VIRTUAL_NODES_IT = { + "ITCO": "FR15", + "ITVI": "ITSI", +} IBFI_COORD = (63.0, 25.0) @@ -225,6 +228,41 @@ def build_shapes( return bidding_shapes, country_shapes +def _add_virtual_node( + target_gdf: gpd.GeoDataFrame, + new_bus: str, + ref_bus: str, + source_gdf: gpd.GeoDataFrame | None = None, + **overrides, +) -> None: + """ + Add a virtual node to target Dataframe as a copy of an existing reference bus. + + The virtual node inherits every attribute of the reference bus and takes its + own name as ``station_id`` and ``tags``. Any remaining attribute is set + through ``overrides``. + + Parameters + ---------- + target_gdf : gpd.GeoDataFrame + Bus GeoDataFrame the virtual node is appended to, modified in place. + new_bus : str + Name of the virtual node. + ref_bus : str + Name of the reference bus whose attributes are copied. + source_gdf : gpd.GeoDataFrame, optional + Bus GeoDataFrame holding the reference bus. Defaults to ``target``. + **overrides, optional + Attribute values overriding those inherited from the reference bus. + """ + source_gdf = target_gdf if source_gdf is None else source_gdf + target_gdf.loc[new_bus] = ( + source_gdf.loc[[ref_bus]] + .assign(station_id=new_bus, tags=new_bus, **overrides) + .loc[ref_bus] + ) + + def build_buses( buses_fn: str, countries: list[str], @@ -283,14 +321,10 @@ def build_buses( # Manually add Italian virtual nodes # TODO Refine assumptions if "IT" in countries: - buses.loc["ITCO"] = ( - buses.loc[["FR15"]] - .assign(station_id="ITCO", country="IT", tags="ITCO") - .loc["FR15"] - ) - buses.loc["ITVI"] = ( - buses.loc[["ITSI"]].assign(station_id="ITVI", tags="ITVI").loc["ITSI"] - ) + for node, location in AC_VIRTUAL_NODES_IT.items(): + _add_virtual_node( + target_gdf=buses, new_bus=node, ref_bus=location, country="IT" + ) buses_h2 = ( country_shapes[["node", "x", "y"]] @@ -311,23 +345,23 @@ def build_buses( # Manually add IBIT and IBFI nodes # TODO Refine assumptions if "IT" in countries: - buses_h2.loc["IBIT H2"] = ( - buses.loc[["ITN1"]] - .assign(station_id="IBIT H2", voltage=None, dc="f", tags="IBIT H2") - .loc["ITN1"] + _add_virtual_node( + target_gdf=buses_h2, + new_bus="IBIT H2", + ref_bus="ITN1", + source_gdf=buses, + voltage=None, + dc="f", ) if "FI" in countries: ibfi_lat, ibfi_long = IBFI_COORD - buses_h2.loc["IBFI H2"] = ( - buses_h2.loc[["FI H2"]] - .assign( - station_id="IBFI H2", - tags="IBFI H2", - x=ibfi_long, - y=ibfi_lat, - geometry=Point(ibfi_long, ibfi_lat), - ) - .loc["FI H2"] + _add_virtual_node( + target_gdf=buses_h2, + new_bus="IBFI H2", + ref_bus="FI H2", + x=ibfi_long, + y=ibfi_lat, + geometry=Point(ibfi_long, ibfi_lat), ) return buses, buses_h2 From d942073032283f388c70c58bc12c7ef25d453daf Mon Sep 17 00:00:00 2001 From: daniel-rdt Date: Wed, 22 Jul 2026 11:56:35 +0200 Subject: [PATCH 4/4] doc: clarify docstring --- scripts/build_tyndp_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_tyndp_network.py b/scripts/build_tyndp_network.py index 9b35da2634..83fde838b6 100644 --- a/scripts/build_tyndp_network.py +++ b/scripts/build_tyndp_network.py @@ -251,7 +251,7 @@ def _add_virtual_node( ref_bus : str Name of the reference bus whose attributes are copied. source_gdf : gpd.GeoDataFrame, optional - Bus GeoDataFrame holding the reference bus. Defaults to ``target``. + Bus GeoDataFrame holding the reference bus. Defaults to None in which case ``target_gdf`` is used. **overrides, optional Attribute values overriding those inherited from the reference bus. """