Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b9d7eaa
Adjusts args and its calls with regard to the settings for spatial cl…
KathiEsterl Jul 10, 2025
e2caa5f
Apply correct black version
KathiEsterl Jul 10, 2025
c7eca8e
Fix errors from flake8 and isort checks
KathiEsterl Jul 10, 2025
3543aa9
Apply black again after manual changes to fulfill requirements for is…
KathiEsterl Jul 11, 2025
31b1aa2
Implement clustering with focus region as Zooming-Approach including …
KathiEsterl Aug 26, 2025
08d4c46
Apply isort and black
KathiEsterl Aug 26, 2025
aac1b10
Merge branch 'dev' into features/#800-clustering-with-focus
KathiEsterl Aug 26, 2025
71d3dd9
Minor changes to default settings
KathiEsterl Aug 26, 2025
2236389
Merge dev
KathiEsterl Oct 6, 2025
430aab2
Merge dev
KathiEsterl Oct 29, 2025
078c07d
Merge branch 'dev' into features/#800-clustering-with-focus
KathiEsterl Dec 2, 2025
e0e1fce
Merge dev
KathiEsterl Feb 22, 2026
f062193
Apply black
KathiEsterl Feb 22, 2026
6392643
Fix aggr strategy
KathiEsterl Feb 22, 2026
f423803
Set up spatial zooming clustering in first verson to merge: adapt cal…
KathiEsterl Feb 24, 2026
583a13e
Apply black
KathiEsterl Feb 24, 2026
b59a598
Remove option to load weighting from clustering as it is redundant wi…
KathiEsterl Feb 25, 2026
a81ae43
Merge dev
KathiEsterl Feb 25, 2026
fac97ad
Apply black
KathiEsterl Feb 25, 2026
30bb3a1
Fix flake8 issue
KathiEsterl Feb 25, 2026
a431db1
Add docstring to focus_weighting function
KathiEsterl Feb 25, 2026
5e62d25
Fix flake8 issues
KathiEsterl Feb 25, 2026
cb51356
Explicitly set datatype to avoid problems in pandas<2.1
ClaraBuettner Feb 26, 2026
0c5bd04
Add information on needed format for focus region, remove scn_decommi…
KathiEsterl Feb 26, 2026
0a70255
Adapt json-file to new args
KathiEsterl Feb 26, 2026
762fcee
Add short desription of clustering with focus to documentation
KathiEsterl Mar 10, 2026
c36f4b1
Adapt args within spatial clustering for better overview and understa…
KathiEsterl Mar 10, 2026
1a106e4
Fix error with regions close to borders and add warning when calling …
KathiEsterl Mar 10, 2026
ce1f8c6
Apply black
KathiEsterl Mar 10, 2026
9b055c1
Merge branch 'dev' into features/#800-clustering-with-focus
KathiEsterl Mar 10, 2026
22b2adb
Fix problem with focus region, kmeans and foreign DC links
KathiEsterl Mar 25, 2026
7168776
Set back to default settings
KathiEsterl Mar 25, 2026
1796944
Merge dev
KathiEsterl Mar 25, 2026
b11903d
Add Li-ion as default for negative load shedding within load shedding…
KathiEsterl Mar 30, 2026
33a73db
Adapt description for per_country-parameter which is named diffently …
KathiEsterl Mar 31, 2026
876a361
Adapt args for minimal example
KathiEsterl Apr 7, 2026
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/theoretical_background.rst
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ The procedures of the two methods are depicted in the following figure [Esterl20

|

Additionally, there is the option to perform a **clustering with a focus on a defined region**, in which the spatial distribution of clustered buses is intentionally weighted toward the corresponding region. This allows a higher spatial resolution within and around the region of interest, while areas farther away are represented with fewer buses.

In general, the clustering of the **sector-coupled system** is divided into two steps:
First, the electrical and gas grid are clustered independently using one of the methods described above. Afterwards, nodes of the other sectors (hydrogen, heat, e-mobility and DSM nodes) are mapped according to their connection to electricity or gas buses and aggregated to one node per carrier.

Expand Down
298 changes: 147 additions & 151 deletions etrago/appl.py

Large diffs are not rendered by default.

55 changes: 30 additions & 25 deletions etrago/args.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"q_allocation": "p_nom"
},
"start_snapshot": 1,
"end_snapshot": 2,
"end_snapshot": 168,
"solver": "gurobi",
"solver_options": {},
"model_formulation": "kirchhoff",
Expand Down Expand Up @@ -63,31 +63,36 @@
"extra_functionality": {},
"network_clustering_ehv": {
"active": false,
"busmap": false
"busmap": false,
"cpu_cores": 4
},
"network_clustering": {
"active": true,
"method": "kmedoids-dijkstra",
"n_clusters_AC": 30,
"cluster_foreign_AC": false,
"method_gas": "kmedoids-dijkstra",
"n_clusters_gas": 15,
"n_clusters_h2": 15,
"cluster_foreign_gas": false,
"k_elec_busmap": false,
"k_gas_busmap": false,
"bus_weight_tocsv": null,
"bus_weight_fromcsv": null,
"gas_weight_tocsv": null,
"gas_weight_fromcsv": null,
"line_length_factor": 1,
"remove_stubs": false,
"use_reduced_coordinates": false,
"random_state": 42,
"n_init": 10,
"max_iter": 100,
"tol": 1e-6,
"CPU_cores": 4
"network_clustering": {
"method": {
"focus_region": null,
"per_country": true,
"algorithm": "kmedoids-dijkstra",
"remove_stubs": false,
"use_reduced_coordinates": false,
"line_length_factor": 1,
"random_state": 42,
"n_init": 10,
"max_iter": 100,
"tol": 1e-6,
"cpu_cores": 4
},
"electricity_grid": {
"active": true,
"cluster_within_focus": false,
"n_clusters": 30,
"k_elec_busmap": false
},
"gas_grids": {
"active": true,
"cluster_within_focus": false,
"n_clusters_ch4": 15,
"n_clusters_h2": 15,
"k_ch4_busmap": false,
}
},
"spatial_disaggregation": null,
"snapshot_clustering": {
Expand Down
102 changes: 65 additions & 37 deletions etrago/cluster/electrical.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from etrago.cluster.spatial import (
busmap_ehv_clustering,
drop_nan_values,
focus_weighting,
group_links,
kmean_clustering,
kmedoids_dijkstra_clustering,
Expand Down Expand Up @@ -195,7 +196,7 @@ def find_de_closest(network, bus_ne):
# Do not apply this part if the function is used for creating the market
# model. It adds one bus per country, which is not useful in this case.
if apply_on != "market_model":
if (not etrago.args["network_clustering"]["cluster_foreign_AC"]) & (
if (etrago.args["network_clustering"]["method"]["per_country"]) & (
cluster_met in ["kmeans", "kmedoids-dijkstra"]
):
buses_orig = network.buses.copy()
Expand Down Expand Up @@ -515,9 +516,9 @@ def select_elec_network(etrago, apply_on="grid_model"):
apply_on: str
gives information about the objective of the output network. If
"grid_model" is provided, the value assigned in the args for
["network_clustering"]["cluster_foreign_AC""] will define if the
foreign buses will be included in the network. if "market_model" is
provided, foreign buses will be always included.
["network_clustering"]["method"]["per_country""] will
define if the foreign buses will be included in the network.
If "market_model" is provided, foreign buses will be always included.

Returns
-------
Expand All @@ -536,10 +537,12 @@ def select_elec_network(etrago, apply_on="grid_model"):
"""Parameter apply_on must be either 'grid_model' or 'market_model'
"""
)
settings = etrago.args["network_clustering"]
settings = etrago.args["network_clustering"]["electricity_grid"]

if apply_on == "grid_model":
include_foreign = settings["cluster_foreign_AC"]
include_foreign = not etrago.args["network_clustering"]["method"][
"per_country"
]
elif apply_on == "market_model":
include_foreign = True
else:
Expand All @@ -556,7 +559,7 @@ def select_elec_network(etrago, apply_on="grid_model"):
(elec_network.links.carrier == "AC")
| (elec_network.links.carrier == "DC")
]
n_clusters = settings["n_clusters_AC"]
n_clusters = settings["n_clusters"]
else:
AC_filter = elec_network.buses.carrier.values == "AC"

Expand All @@ -572,7 +575,7 @@ def select_elec_network(etrago, apply_on="grid_model"):
elec_network.buses = elec_network.buses[
AC_filter & (elec_network.buses.country.values == "DE")
]
n_clusters = settings["n_clusters_AC"] - num_neighboring_country
n_clusters = settings["n_clusters"] - num_neighboring_country

# Dealing with generators
elec_network.generators = elec_network.generators[
Expand Down Expand Up @@ -813,14 +816,17 @@ def preprocessing(etrago, apply_on="grid_model"):
""")
network.buses.country.loc[network.buses.country.isna()] = "DE"

if settings["k_elec_busmap"] is False:
if settings["electricity_grid"]["k_elec_busmap"] is False:
busmap_foreign = unify_foreign_buses(etrago)
else:
busmap_foreign = pd.Series(name="foreign", dtype=str)

network_elec, n_clusters = select_elec_network(etrago, apply_on=apply_on)

if settings["method"] == "kmedoids-dijkstra":
if (
settings["method"]["algorithm"] == "kmedoids-dijkstra"
or settings["method"]["focus_region"] is not None
):
lines_col = network_elec.lines.columns

# The Dijkstra clustering works using the shortest electrical path
Expand All @@ -835,19 +841,8 @@ def preprocessing(etrago, apply_on="grid_model"):
network_elec.lines = lines_plus_dc.copy()
network_elec.lines["carrier"] = "AC"

# State whether to create a bus weighting and save it, create or not save
# it, or use a bus weighting from a csv file
if settings["bus_weight_tocsv"] is not None:
weight = weighting_for_scenario(
network=network, save=settings["bus_weight_tocsv"]
)
elif settings["bus_weight_fromcsv"] is not None:
weight = pd.read_csv(
settings["bus_weight_fromcsv"], index_col="Bus", squeeze=True
)
weight.index = weight.index.astype(str)
else:
weight = weighting_for_scenario(network=network, save=False)
# weight buses for clustering
weight = weighting_for_scenario(network=network, save=False)

return network_elec, weight, n_clusters, busmap_foreign

Expand Down Expand Up @@ -883,9 +878,9 @@ def postprocessing(
busmap : pandas.Series
Updated mapping between buses and clusters
"""
settings = etrago.args["network_clustering"]
method = settings["method"]
num_clusters = settings["n_clusters_AC"]
settings = etrago.args["network_clustering"]["electricity_grid"]
method = etrago.args["network_clustering"]["method"]["algorithm"]
num_clusters = settings["n_clusters"]

if not settings["k_elec_busmap"]:
busmap.name = "cluster"
Expand Down Expand Up @@ -933,7 +928,9 @@ def postprocessing(
)

# merge busmap for foreign buses with the German buses
if not settings["cluster_foreign_AC"] and (apply_on == "grid_model"):
if etrago.args["network_clustering"]["method"]["per_country"] and (
apply_on == "grid_model"
):
for bus in busmap_foreign.index:
busmap[bus] = busmap_foreign[bus]
if bus == busmap_foreign[bus]:
Expand All @@ -952,7 +949,9 @@ def postprocessing(
one_port_strategies=strategies_one_ports(),
generator_strategies=strategies_generators(),
aggregate_one_ports=aggregate_one_ports,
line_length_factor=settings["line_length_factor"],
line_length_factor=etrago.args["network_clustering"]["method"][
"line_length_factor"
],
bus_strategies=strategies_buses(),
line_strategies=strategies_lines(),
)
Expand Down Expand Up @@ -1139,17 +1138,39 @@ def run_spatial_clustering(self):
-------
None
"""
if self.args["network_clustering"]["active"]:
if self.args["network_clustering"]["electricity_grid"]["active"]:
if self.args["spatial_disaggregation"] is not None:
self.disaggregated_network = self.network.copy()
else:
self.disaggregated_network = self.network.copy(with_time=False)

elec_network, weight, n_clusters, busmap_foreign = preprocessing(self)

if self.args["network_clustering"]["method"] == "kmeans":
if not self.args["network_clustering"]["k_elec_busmap"]:
logger.info("Start k-means Clustering")
focus_region = self.args["network_clustering"]["method"][
"focus_region"
]
if focus_region:
weight = focus_weighting(
self,
elec_network,
weight,
focus_region=focus_region,
cluster_within=self.args["network_clustering"][
"electricity_grid"
]["cluster_within_focus"],
per_country=self.args["network_clustering"]["method"][
"per_country"
],
cpu_cores=self.args["network_clustering"]["method"][
"cpu_cores"
],
)

if self.args["network_clustering"]["method"]["algorithm"] == "kmeans":
if not self.args["network_clustering"]["electricity_grid"][
"k_elec_busmap"
]:
logger.info("Start k-means Clustering AC")

busmap = kmean_clustering(
self, elec_network, weight, n_clusters
Expand All @@ -1159,9 +1180,14 @@ def run_spatial_clustering(self):
busmap = pd.Series(dtype=str)
medoid_idx = pd.Series(dtype=str)

elif self.args["network_clustering"]["method"] == "kmedoids-dijkstra":
if not self.args["network_clustering"]["k_elec_busmap"]:
logger.info("Start k-medoids Dijkstra Clustering")
elif (
self.args["network_clustering"]["method"]["algorithm"]
== "kmedoids-dijkstra"
):
if not self.args["network_clustering"]["electricity_grid"][
"k_elec_busmap"
]:
logger.info("Start k-medoids Dijkstra Clustering AC")

busmap, medoid_idx = kmedoids_dijkstra_clustering(
self,
Expand Down Expand Up @@ -1193,7 +1219,9 @@ def run_spatial_clustering(self):

logger.info(
"Network clustered to {} buses with ".format(
self.args["network_clustering"]["n_clusters_AC"]
self.args["network_clustering"]["electricity_grid"][
"n_clusters"
]
)
+ self.args["network_clustering"]["method"]
+ self.args["network_clustering"]["method"]["algorithm"]
)
Loading