From 2bcee87c18df703cc7ce9f216ba6ed5bbf535e2e Mon Sep 17 00:00:00 2001 From: bobbyxng Date: Wed, 14 Aug 2024 09:56:58 +0200 Subject: [PATCH 1/9] Fixed simplify_network.py to handle more complex topologies, i.e. if two links are connected to nearby AC buses separated by an AC line. --- scripts/simplify_network.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/simplify_network.py b/scripts/simplify_network.py index 558e4cf28..f255ed7bf 100644 --- a/scripts/simplify_network.py +++ b/scripts/simplify_network.py @@ -298,13 +298,24 @@ def simplify_links( _, labels = connected_components(adjacency_matrix, directed=False) labels = pd.Series(labels, n.buses.index) - G = n.graph() + # Only span graph over the DC link components + G = n.graph(branch_components=["Link"]) def split_links(nodes): nodes = frozenset(nodes) seen = set() - supernodes = {m for m in nodes if len(G.adj[m]) > 2 or (set(G.adj[m]) - nodes)} + + # 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 = { + m + for m in nodes + if ( + (len(G.adj[m]) < 2 or (set(G.adj[m]) - nodes)) + or (n.buses.loc[m, "carrier"] == "AC") + ) + } for u in supernodes: for m, ls in G.adj[u].items(): From 953e1286d42433f47b4566e0f167b5decd65b37b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 08:10:21 +0000 Subject: [PATCH 2/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/simplify_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/simplify_network.py b/scripts/simplify_network.py index f255ed7bf..2b759a732 100644 --- a/scripts/simplify_network.py +++ b/scripts/simplify_network.py @@ -305,7 +305,7 @@ def split_links(nodes): nodes = frozenset(nodes) 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 = { From 6cdc0c4148d7b8f29f673fbb8dd76ecc3eb30414 Mon Sep 17 00:00:00 2001 From: bobbyxng Date: Wed, 14 Aug 2024 13:40:14 +0200 Subject: [PATCH 3/9] Added fix for Corsica node: If region containing Corsica is modelled, include substation on Corsica as supernode (end point). --- scripts/simplify_network.py | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/scripts/simplify_network.py b/scripts/simplify_network.py index 2b759a732..2da97dcea 100644 --- a/scripts/simplify_network.py +++ b/scripts/simplify_network.py @@ -306,6 +306,14 @@ def split_links(nodes): seen = set() + # Corsica substation + node_corsica = find_closest_bus( + n, + x=9.44802, + y=42.52842, + tol=2000 # Tolerance needed to only return the bus if the region is actually modelled + ) + # 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 = { @@ -314,6 +322,7 @@ def split_links(nodes): if ( (len(G.adj[m]) < 2 or (set(G.adj[m]) - nodes)) or (n.buses.loc[m, "carrier"] == "AC") + or (m == node_corsica) ) } @@ -530,6 +539,37 @@ def cluster( return clustering.network, clustering.busmap +def find_closest_bus(n, x, y, tol=2000): + """ + Find the index of the closest bus to the given coordinates within a specified tolerance. + Parameters: + n (pypsa.Network): The network object. + x (float): The x-coordinate (longitude) of the target location. + y (float): The y-coordinate (latitude) of the target location. + tol (float): The distance tolerance in meters. Default is 2000 meters. + + Returns: + int: The index of the closest bus to the target location within the tolerance. + Returns None if no bus is within the tolerance. + """ + # Conversion factors + meters_per_degree_lat = 111139 # Meters per degree of latitude + meters_per_degree_lon = 111139 * np.cos(np.radians(y)) # Meters per degree of longitude at the given latitude + + x0 = np.array(n.buses.x) + y0 = np.array(n.buses.y) + + # Calculate distances in meters + dist = np.sqrt(((x - x0)*meters_per_degree_lon) ** 2 + ((y - y0)*meters_per_degree_lat) ** 2) + + # Find the closest bus within the tolerance + min_dist = dist.min() + if min_dist <= tol: + return n.buses.index[dist.argmin()] + else: + return None + + if __name__ == "__main__": if "snakemake" not in globals(): from _helpers import mock_snakemake From 9fcdc892a80641f369bc39c5c5ceaf750729f51e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:50:38 +0000 Subject: [PATCH 4/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/simplify_network.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/simplify_network.py b/scripts/simplify_network.py index 2da97dcea..c54e3418b 100644 --- a/scripts/simplify_network.py +++ b/scripts/simplify_network.py @@ -306,12 +306,12 @@ def split_links(nodes): seen = set() - # Corsica substation + # Corsica substation node_corsica = find_closest_bus( n, x=9.44802, y=42.52842, - tol=2000 # Tolerance needed to only return the bus if the region is actually modelled + tol=2000, # Tolerance needed to only return the bus if the region is actually modelled ) # Supernodes are endpoints of links, identified by having lass then two neighbours or being an AC Bus @@ -554,13 +554,18 @@ def find_closest_bus(n, x, y, tol=2000): """ # Conversion factors meters_per_degree_lat = 111139 # Meters per degree of latitude - meters_per_degree_lon = 111139 * np.cos(np.radians(y)) # Meters per degree of longitude at the given latitude + meters_per_degree_lon = 111139 * np.cos( + np.radians(y) + ) # Meters per degree of longitude at the given latitude x0 = np.array(n.buses.x) y0 = np.array(n.buses.y) # Calculate distances in meters - dist = np.sqrt(((x - x0)*meters_per_degree_lon) ** 2 + ((y - y0)*meters_per_degree_lat) ** 2) + dist = np.sqrt( + ((x - x0) * meters_per_degree_lon) ** 2 + + ((y - y0) * meters_per_degree_lat) ** 2 + ) # Find the closest bus within the tolerance min_dist = dist.min() From 167d84ad901adf555a67c38364f046975d99f2ad Mon Sep 17 00:00:00 2001 From: bobbyxng Date: Thu, 15 Aug 2024 17:41:14 +0200 Subject: [PATCH 5/9] Bug fix: Carrier type of all supernodes corrected to 'AC' --- scripts/simplify_network.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/scripts/simplify_network.py b/scripts/simplify_network.py index 9ab0cb233..e8633e9e3 100644 --- a/scripts/simplify_network.py +++ b/scripts/simplify_network.py @@ -108,7 +108,7 @@ logger = logging.getLogger(__name__) -def simplify_network_to_380(n): +def simplify_network_to_380(n, linetype_380): """ Fix all lines to a voltage level of 380 kV and remove all transformers. @@ -124,7 +124,7 @@ def simplify_network_to_380(n): n.buses["v_nom"] = 380.0 - linetype_380 = n.lines["type"].mode()[0] + # TODO pypsa-eur: In the future, make this even more generic (voltage level) n.lines["type"] = linetype_380 n.lines["v_nom"] = 380 n.lines["i_nom"] = n.line_types.i_nom[linetype_380] @@ -301,19 +301,11 @@ def simplify_links( # Only span graph over the DC link components G = n.graph(branch_components=["Link"]) - def split_links(nodes): + def split_links(nodes, added_supernodes=None): nodes = frozenset(nodes) seen = set() - # Corsica substation - node_corsica = find_closest_bus( - n, - x=9.44802, - y=42.52842, - tol=2000, # Tolerance needed to only return the bus if the region is actually modelled - ) - # 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 = { @@ -322,7 +314,7 @@ def split_links(nodes): if ( (len(G.adj[m]) < 2 or (set(G.adj[m]) - nodes)) or (n.buses.loc[m, "carrier"] == "AC") - or (m == node_corsica) + or (m in added_supernodes) ) } @@ -359,8 +351,18 @@ def split_links(nodes): 0.0, index=n.buses.index, columns=list(connection_costs_per_link) ) + node_corsica = find_closest_bus( + n, + x=9.44802, + y=42.52842, + tol=2000, # Tolerance needed to only return the bus if the region is actually modelled + ) + + added_supernodes = [] + added_supernodes.append(node_corsica) + for lbl in labels.value_counts().loc[lambda s: s > 2].index: - for b, buses, links in split_links(labels.index[labels == lbl]): + for b, buses, links in split_links(labels.index[labels == lbl], added_supernodes): if len(buses) <= 2: continue @@ -421,6 +423,9 @@ def split_links(nodes): logger.debug("Collecting all components using the busmap") + # Change carrier type of all added super_nodes to "AC" + n.buses.loc[added_supernodes, "carrier"] = "AC" + _aggregate_and_move_components( n, busmap, @@ -579,7 +584,7 @@ def find_closest_bus(n, x, y, tol=2000): if "snakemake" not in globals(): from _helpers import mock_snakemake - snakemake = mock_snakemake("simplify_network", simpl="", run="all") + snakemake = mock_snakemake("simplify_network", simpl="") configure_logging(snakemake) set_scenario_config(snakemake) @@ -592,7 +597,8 @@ def find_closest_bus(n, x, y, tol=2000): # remove integer outputs for compatibility with PyPSA v0.26.0 n.generators.drop("n_mod", axis=1, inplace=True, errors="ignore") - n, trafo_map = simplify_network_to_380(n) + linetype_380 = snakemake.config["lines"]["types"][380] + n, trafo_map = simplify_network_to_380(n, linetype_380) technology_costs = load_costs( snakemake.input.tech_costs, @@ -655,7 +661,6 @@ def find_closest_bus(n, x, y, tol=2000): "substation_off", "geometry", "underground", - "project_status", ] n.buses.drop(remove, axis=1, inplace=True, errors="ignore") n.lines.drop(remove, axis=1, errors="ignore", inplace=True) From a930df33b3597366683a7969ce86180851cb99ac Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:47:15 +0000 Subject: [PATCH 6/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/simplify_network.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/simplify_network.py b/scripts/simplify_network.py index e8633e9e3..a2c32b61d 100644 --- a/scripts/simplify_network.py +++ b/scripts/simplify_network.py @@ -362,7 +362,9 @@ def split_links(nodes, added_supernodes=None): added_supernodes.append(node_corsica) for lbl in labels.value_counts().loc[lambda s: s > 2].index: - for b, buses, links in split_links(labels.index[labels == lbl], added_supernodes): + for b, buses, links in split_links( + labels.index[labels == lbl], added_supernodes + ): if len(buses) <= 2: continue From 18cbd81b58154aa2e6ccc18e16fb14a33e48d1d0 Mon Sep 17 00:00:00 2001 From: bobbyxng Date: Thu, 15 Aug 2024 17:57:35 +0200 Subject: [PATCH 7/9] Bug fix: Carrier type of all supernodes corrected to 'AC' --- scripts/simplify_network.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/scripts/simplify_network.py b/scripts/simplify_network.py index a2c32b61d..751e363f7 100644 --- a/scripts/simplify_network.py +++ b/scripts/simplify_network.py @@ -108,7 +108,7 @@ logger = logging.getLogger(__name__) -def simplify_network_to_380(n, linetype_380): +def simplify_network_to_380(n): """ Fix all lines to a voltage level of 380 kV and remove all transformers. @@ -124,7 +124,7 @@ def simplify_network_to_380(n, linetype_380): n.buses["v_nom"] = 380.0 - # TODO pypsa-eur: In the future, make this even more generic (voltage level) + linetype_380 = n.lines["type"].mode()[0] n.lines["type"] = linetype_380 n.lines["v_nom"] = 380 n.lines["i_nom"] = n.line_types.i_nom[linetype_380] @@ -362,9 +362,7 @@ def split_links(nodes, added_supernodes=None): added_supernodes.append(node_corsica) for lbl in labels.value_counts().loc[lambda s: s > 2].index: - for b, buses, links in split_links( - labels.index[labels == lbl], added_supernodes - ): + for b, buses, links in split_links(labels.index[labels == lbl]): if len(buses) <= 2: continue @@ -586,7 +584,7 @@ def find_closest_bus(n, x, y, tol=2000): if "snakemake" not in globals(): from _helpers import mock_snakemake - snakemake = mock_snakemake("simplify_network", simpl="") + snakemake = mock_snakemake("simplify_network", simpl="", run="all") configure_logging(snakemake) set_scenario_config(snakemake) @@ -599,8 +597,7 @@ def find_closest_bus(n, x, y, tol=2000): # remove integer outputs for compatibility with PyPSA v0.26.0 n.generators.drop("n_mod", axis=1, inplace=True, errors="ignore") - linetype_380 = snakemake.config["lines"]["types"][380] - n, trafo_map = simplify_network_to_380(n, linetype_380) + n, trafo_map = simplify_network_to_380(n) technology_costs = load_costs( snakemake.input.tech_costs, @@ -663,6 +660,7 @@ def find_closest_bus(n, x, y, tol=2000): "substation_off", "geometry", "underground", + "project_status", ] n.buses.drop(remove, axis=1, inplace=True, errors="ignore") n.lines.drop(remove, axis=1, errors="ignore", inplace=True) @@ -693,4 +691,4 @@ def find_closest_bus(n, x, y, tol=2000): append_bus_shapes(n, clustered_regions, type=which.split("_")[1]) n.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards))) - n.export_to_netcdf(snakemake.output.network) + n.export_to_netcdf(snakemake.output.network) \ No newline at end of file From 8dddd44a481ce31fc443316c251fc857329f20a2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:58:04 +0000 Subject: [PATCH 8/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- scripts/simplify_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/simplify_network.py b/scripts/simplify_network.py index 751e363f7..fa2855bb4 100644 --- a/scripts/simplify_network.py +++ b/scripts/simplify_network.py @@ -691,4 +691,4 @@ def find_closest_bus(n, x, y, tol=2000): append_bus_shapes(n, clustered_regions, type=which.split("_")[1]) n.meta = dict(snakemake.config, **dict(wildcards=dict(snakemake.wildcards))) - n.export_to_netcdf(snakemake.output.network) \ No newline at end of file + n.export_to_netcdf(snakemake.output.network) From c9ad74230eb94e5b4698458e954c71062bfcf26f Mon Sep 17 00:00:00 2001 From: bobbyxng Date: Thu, 15 Aug 2024 18:07:01 +0200 Subject: [PATCH 9/9] Bug fix: Carrier type of all supernodes corrected to 'AC' --- scripts/simplify_network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/simplify_network.py b/scripts/simplify_network.py index fa2855bb4..66a5d1190 100644 --- a/scripts/simplify_network.py +++ b/scripts/simplify_network.py @@ -359,7 +359,8 @@ def split_links(nodes, added_supernodes=None): ) added_supernodes = [] - added_supernodes.append(node_corsica) + if node_corsica is not None: + added_supernodes.append(node_corsica) for lbl in labels.value_counts().loc[lambda s: s > 2].index: for b, buses, links in split_links(labels.index[labels == lbl]):