Merge remote-tracking branch 'origin/master' into graphics-to-doc-img

This commit is contained in:
Fabian Neumann 2024-05-06 10:03:20 +02:00
commit 9318dd6b2b
11 changed files with 837 additions and 885 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 208 KiB

View File

@ -135,7 +135,7 @@ as part of the `Stromnetze Research Initiative
Workflow
========
.. image:: ../doc/img/workflow.png
.. image:: img/workflow.png
:class: full-width
:align: center

View File

@ -21,8 +21,7 @@ Having downloaded the necessary data,
With these and the externally extracted ENTSO-E online map topology
(``data/entsoegridkit``), it can build a base PyPSA network with the following rules:
- :mod:`base_network` builds and stores the base network with all buses, HVAC lines and HVDC links, while
- :mod:`build_bus_regions` determines `Voronoi cells <https://en.wikipedia.org/wiki/Voronoi_diagram>`__ for all substations.
- :mod:`base_network` builds and stores the base network with all buses, HVAC lines and HVDC links, and determines `Voronoi cells <https://en.wikipedia.org/wiki/Voronoi_diagram>`__ for all substations.
Then the process continues by calculating conventional power plant capacities, potentials, and per-unit availability time series for variable renewable energy carriers and hydro power plants with the following rules:
@ -35,13 +34,6 @@ Then the process continues by calculating conventional power plant capacities, p
The central rule :mod:`add_electricity` then ties all the different data inputs
together into a detailed PyPSA network stored in ``networks/elec.nc``.
.. _busregions:
Rule ``build_bus_regions``
=============================
.. automodule:: build_bus_regions
.. _cutout:
Rule ``build_cutout``

View File

@ -186,7 +186,7 @@ Upcoming Release
* Data on existing renewable capacities is now consistently taken from powerplantmatching (instead of being retrieved separately); the dataset has also been updated to include 2023 values.
* Added shapes to .nc file for different stages of the network object in `base_network`, `build_bus_regions`, and `cluster_network`.
* Added shapes to .nc file for different stages of the network object in `base_network`, `simplify_network`, and `cluster_network`; the `build_bus_regions` rule is now integrated into the `base_network` rule.
* Fix p_nom_min of renewables generators for myopic approach and add check of existing capacities in `add_land_use_constraint_m`.

View File

@ -133,32 +133,31 @@ This triggers a workflow of multiple preceding jobs that depend on each rule's i
graph[bgcolor=white, margin=0];
node[shape=box, style=rounded, fontname=sans, fontsize=10, penwidth=2];
edge[penwidth=2, color=grey];
0[label = "solve_network", color = "0.39 0.6 0.85", style="rounded"];
1[label = "prepare_network\nll: copt\nopts: Co2L-24H", color = "0.29 0.6 0.85", style="rounded"];
2[label = "add_extra_components", color = "0.28 0.6 0.85", style="rounded"];
3[label = "cluster_network\nclusters: 6", color = "0.19 0.6 0.85", style="rounded"];
4[label = "simplify_network\nsimpl: ", color = "0.01 0.6 0.85", style="rounded"];
5[label = "add_electricity", color = "0.49 0.6 0.85", style="rounded"];
6[label = "build_renewable_profiles\ntechnology: solar", color = "0.21 0.6 0.85", style="rounded"];
7[label = "base_network", color = "0.27 0.6 0.85", style="rounded"];
8[label = "build_shapes", color = "0.26 0.6 0.85", style="rounded"];
9[label = "retrieve_databundle", color = "0.59 0.6 0.85", style="rounded"];
10[label = "retrieve_natura_raster", color = "0.47 0.6 0.85", style="rounded"];
11[label = "build_bus_regions", color = "0.13 0.6 0.85", style="rounded"];
12[label = "retrieve_cutout\ncutout: be-03-2013-era5", color = "0.36 0.6 0.85", style="rounded,dashed"];
13[label = "build_renewable_profiles\ntechnology: onwind", color = "0.21 0.6 0.85", style="rounded"];
14[label = "build_renewable_profiles\ntechnology: offwind-ac", color = "0.21 0.6 0.85", style="rounded"];
15[label = "build_ship_raster", color = "0.00 0.6 0.85", style="rounded"];
16[label = "retrieve_ship_raster", color = "0.51 0.6 0.85", style="rounded,dashed"];
17[label = "build_renewable_profiles\ntechnology: offwind-dc", color = "0.21 0.6 0.85", style="rounded"];
18[label = "build_line_rating", color = "0.05 0.6 0.85", style="rounded"];
19[label = "retrieve_cost_data\nyear: 2030", color = "0.15 0.6 0.85", style="rounded"];
20[label = "build_powerplants", color = "0.54 0.6 0.85", style="rounded"];
21[label = "build_electricity_demand", color = "0.52 0.6 0.85", style="rounded"];
22[label = "retrieve_electricity_demand", color = "0.22 0.6 0.85", style="rounded"];
23[label = "copy_config", color = "0.44 0.6 0.85", style="rounded"];
0[label = "solve_network", color = "0.24 0.6 0.85", style="rounded"];
1[label = "prepare_network\nll: vopt\nopts: Co2L-3H", color = "0.10 0.6 0.85", style="rounded"];
2[label = "add_extra_components", color = "0.33 0.6 0.85", style="rounded"];
3[label = "cluster_network\nclusters: 128", color = "0.59 0.6 0.85", style="rounded"];
4[label = "simplify_network\nsimpl: ", color = "0.18 0.6 0.85", style="rounded"];
5[label = "add_electricity", color = "0.48 0.6 0.85", style="rounded"];
6[label = "build_renewable_profiles\ntechnology: solar", color = "0.29 0.6 0.85", style="rounded"];
7[label = "base_network", color = "0.30 0.6 0.85", style="rounded"];
8[label = "build_shapes", color = "0.61 0.6 0.85", style="rounded"];
9[label = "retrieve_databundle", color = "0.06 0.6 0.85", style="rounded"];
10[label = "retrieve_natura_raster", color = "0.03 0.6 0.85", style="rounded"];
11[label = "retrieve_cutout\ncutout: europe-2013-sarah", color = "0.50 0.6 0.85", style="rounded"];
12[label = "build_renewable_profiles\ntechnology: onwind", color = "0.29 0.6 0.85", style="rounded"];
13[label = "retrieve_cutout\ncutout: europe-2013-era5", color = "0.50 0.6 0.85", style="rounded"];
14[label = "build_renewable_profiles\ntechnology: offwind-ac", color = "0.29 0.6 0.85", style="rounded"];
15[label = "build_ship_raster", color = "0.16 0.6 0.85", style="rounded"];
16[label = "retrieve_ship_raster", color = "0.53 0.6 0.85", style="rounded"];
17[label = "build_renewable_profiles\ntechnology: offwind-dc", color = "0.29 0.6 0.85", style="rounded"];
18[label = "build_hydro_profile", color = "0.47 0.6 0.85", style="rounded"];
19[label = "retrieve_cost_data\nyear: 2030", color = "0.21 0.6 0.85", style="rounded"];
20[label = "build_powerplants", color = "0.56 0.6 0.85", style="rounded"];
21[label = "build_electricity_demand", color = "0.54 0.6 0.85", style="rounded"];
22[label = "retrieve_electricity_demand", color = "0.34 0.6 0.85", style="rounded"];
23[label = "retrieve_synthetic_electricity_demand", color = "0.65 0.6 0.85", style="rounded"];
1 -> 0
23 -> 0
2 -> 1
19 -> 1
3 -> 2
@ -167,15 +166,14 @@ This triggers a workflow of multiple preceding jobs that depend on each rule's i
19 -> 3
5 -> 4
19 -> 4
11 -> 4
7 -> 4
6 -> 5
13 -> 5
12 -> 5
14 -> 5
17 -> 5
7 -> 5
18 -> 5
7 -> 5
19 -> 5
11 -> 5
20 -> 5
9 -> 5
21 -> 5
@ -185,37 +183,32 @@ This triggers a workflow of multiple preceding jobs that depend on each rule's i
10 -> 6
8 -> 6
11 -> 6
12 -> 6
8 -> 7
9 -> 8
8 -> 11
7 -> 11
7 -> 13
9 -> 13
10 -> 13
8 -> 13
11 -> 13
12 -> 13
7 -> 12
9 -> 12
10 -> 12
8 -> 12
13 -> 12
7 -> 14
9 -> 14
10 -> 14
15 -> 14
8 -> 14
11 -> 14
12 -> 14
13 -> 14
16 -> 15
12 -> 15
13 -> 15
7 -> 17
9 -> 17
10 -> 17
15 -> 17
8 -> 17
11 -> 17
12 -> 17
7 -> 18
12 -> 18
13 -> 17
8 -> 18
13 -> 18
7 -> 20
22 -> 21
23 -> 21
}
|
@ -226,28 +219,25 @@ In the terminal, this will show up as a list of jobs to be run:
Building DAG of jobs...
Job stats:
job count
--------------------------- -------
add_electricity 1
add_extra_components 1
base_network 1
build_bus_regions 1
build_electricity_demand 1
build_line_rating 1
build_powerplants 1
build_renewable_profiles 4
build_shapes 1
build_ship_raster 1
cluster_network 1
copy_config 1
prepare_network 1
retrieve_cost_data 1
retrieve_databundle 1
retrieve_electricity_demand 1
retrieve_natura_raster 1
simplify_network 1
solve_network 1
total 22
job count
------------------------------------- -------
add_electricity 1
add_extra_components 1
build_line_rating 1
build_renewable_profiles 4
build_ship_raster 1
cluster_network 1
prepare_network 1
retrieve_cost_data 1
retrieve_cutout 1
retrieve_databundle 1
retrieve_electricity_demand 1
retrieve_natura_raster 1
retrieve_ship_raster 1
retrieve_synthetic_electricity_demand 1
simplify_network 1
solve_network 1
total 19
``snakemake`` then runs these jobs in the correct order.

File diff suppressed because it is too large Load Diff

View File

@ -86,7 +86,9 @@ rule base_network:
offshore_shapes=resources("offshore_shapes.geojson"),
europe_shape=resources("europe_shape.geojson"),
output:
resources("networks/base.nc"),
base_network=resources("networks/base.nc"),
regions_onshore=resources("regions_onshore.geojson"),
regions_offshore=resources("regions_offshore.geojson"),
log:
logs("base_network.log"),
benchmark:
@ -127,27 +129,6 @@ rule build_shapes:
"../scripts/build_shapes.py"
rule build_bus_regions:
params:
countries=config_provider("countries"),
input:
country_shapes=resources("country_shapes.geojson"),
offshore_shapes=resources("offshore_shapes.geojson"),
base_network=resources("networks/base.nc"),
output:
regions_onshore=resources("regions_onshore.geojson"),
regions_offshore=resources("regions_offshore.geojson"),
log:
logs("build_bus_regions.log"),
threads: 1
resources:
mem_mb=1000,
conda:
"../envs/environment.yaml"
script:
"../scripts/build_bus_regions.py"
if config["enable"].get("build_cutout", False):
rule build_cutout:

View File

@ -5,10 +5,7 @@
# coding: utf-8
"""
Creates the network topology from a `ENTSO-E map extract.
<https://github.com/PyPSA/GridKit/tree/master/entsoe>`_ (March 2022) as a PyPSA
network.
Creates the network topology from an `ENTSO-E map extract <https://github.com/PyPSA/GridKit/tree/master/entsoe>`_ (March 2022) as a PyPSA network.
Relevant Settings
-----------------
@ -59,8 +56,19 @@ Outputs
.. image:: img/base.png
:scale: 33 %
- ``resources/regions_onshore.geojson``:
.. image:: img/regions_onshore.png
:scale: 33 %
- ``resources/regions_offshore.geojson``:
.. image:: img/regions_offshore.png
:scale: 33 %
Description
-----------
Creates the network topology from an ENTSO-E map extract, and create Voronoi shapes for each bus representing both onshore and offshore regions.
"""
import logging
@ -75,11 +83,11 @@ import shapely
import shapely.prepared
import shapely.wkt
import yaml
from _helpers import configure_logging, get_snapshots, set_scenario_config
from _helpers import REGION_COLS, configure_logging, get_snapshots, set_scenario_config
from packaging.version import Version, parse
from scipy import spatial
from scipy.sparse import csgraph
from shapely.geometry import LineString, Point
from shapely.geometry import LineString, Point, Polygon
PD_GE_2_2 = parse(pd.__version__) >= Version("2.2")
@ -779,9 +787,147 @@ def base_network(
return n
def voronoi_partition_pts(points, outline):
"""
Compute the polygons of a voronoi partition of `points` within the polygon
`outline`. Taken from
https://github.com/FRESNA/vresutils/blob/master/vresutils/graph.py.
Attributes
----------
points : Nx2 - ndarray[dtype=float]
outline : Polygon
Returns
-------
polygons : N - ndarray[dtype=Polygon|MultiPolygon]
"""
points = np.asarray(points)
if len(points) == 1:
polygons = [outline]
else:
xmin, ymin = np.amin(points, axis=0)
xmax, ymax = np.amax(points, axis=0)
xspan = xmax - xmin
yspan = ymax - ymin
# to avoid any network positions outside all Voronoi cells, append
# the corners of a rectangle framing these points
vor = spatial.Voronoi(
np.vstack(
(
points,
[
[xmin - 3.0 * xspan, ymin - 3.0 * yspan],
[xmin - 3.0 * xspan, ymax + 3.0 * yspan],
[xmax + 3.0 * xspan, ymin - 3.0 * yspan],
[xmax + 3.0 * xspan, ymax + 3.0 * yspan],
],
)
)
)
polygons = []
for i in range(len(points)):
poly = Polygon(vor.vertices[vor.regions[vor.point_region[i]]])
if not poly.is_valid:
poly = poly.buffer(0)
with np.errstate(invalid="ignore"):
poly = poly.intersection(outline)
polygons.append(poly)
return polygons
def build_bus_shapes(n, country_shapes, offshore_shapes, countries):
country_shapes = gpd.read_file(country_shapes).set_index("name")["geometry"]
offshore_shapes = gpd.read_file(offshore_shapes)
offshore_shapes = offshore_shapes.reindex(columns=REGION_COLS).set_index("name")[
"geometry"
]
onshore_regions = []
offshore_regions = []
for country in countries:
c_b = n.buses.country == country
onshore_shape = country_shapes[country]
onshore_locs = (
n.buses.loc[c_b & n.buses.onshore_bus]
.sort_values(
by="substation_lv", ascending=False
) # preference for substations
.drop_duplicates(subset=["x", "y"], keep="first")[["x", "y"]]
)
onshore_regions.append(
gpd.GeoDataFrame(
{
"name": onshore_locs.index,
"x": onshore_locs["x"],
"y": onshore_locs["y"],
"geometry": voronoi_partition_pts(
onshore_locs.values, onshore_shape
),
"country": country,
}
)
)
if country not in offshore_shapes.index:
continue
offshore_shape = offshore_shapes[country]
offshore_locs = n.buses.loc[c_b & n.buses.substation_off, ["x", "y"]]
offshore_regions_c = gpd.GeoDataFrame(
{
"name": offshore_locs.index,
"x": offshore_locs["x"],
"y": offshore_locs["y"],
"geometry": voronoi_partition_pts(offshore_locs.values, offshore_shape),
"country": country,
}
)
offshore_regions_c = offshore_regions_c.loc[offshore_regions_c.area > 1e-2]
offshore_regions.append(offshore_regions_c)
shapes = pd.concat(onshore_regions, ignore_index=True)
return onshore_regions, offshore_regions, shapes
def append_bus_shapes(n, shapes, type):
"""
Append shapes to the network. If shapes with the same component and type
already exist, they will be removed.
Parameters:
n (pypsa.Network): The network to which the shapes will be appended.
shapes (geopandas.GeoDataFrame): The shapes to be appended.
**kwargs: Additional keyword arguments used in `n.madd`.
Returns:
None
"""
remove = n.shapes.query("component == 'Bus' and type == @type").index
n.mremove("Shape", remove)
offset = n.shapes.index.astype(int).max() + 1 if not n.shapes.empty else 0
shapes = shapes.rename(lambda x: int(x) + offset)
n.madd(
"Shape",
shapes.index,
geometry=shapes.geometry,
idx=shapes.name,
component="Bus",
type=type,
)
if __name__ == "__main__":
if "snakemake" not in globals():
from _helpers import mock_snakemake
snakemake = mock_snakemake("base_network")
@ -803,5 +949,22 @@ if __name__ == "__main__":
snakemake.config,
)
onshore_regions, offshore_regions, shapes = build_bus_shapes(
n,
snakemake.input.country_shapes,
snakemake.input.offshore_shapes,
snakemake.params.countries,
)
shapes.to_file(snakemake.output.regions_onshore)
append_bus_shapes(n, shapes, "onshore")
if offshore_regions:
shapes = pd.concat(offshore_regions, ignore_index=True)
shapes.to_file(snakemake.output.regions_offshore)
append_bus_shapes(n, shapes, "offshore")
else:
offshore_shapes.to_frame().to_file(snakemake.output.regions_offshore)
n.meta = snakemake.config
n.export_to_netcdf(snakemake.output[0])
n.export_to_netcdf(snakemake.output.base_network)

View File

@ -1,218 +0,0 @@
# -*- coding: utf-8 -*-
# SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors
#
# SPDX-License-Identifier: MIT
"""
Creates Voronoi shapes for each bus representing both onshore and offshore
regions.
Relevant Settings
-----------------
.. code:: yaml
countries:
.. seealso::
Documentation of the configuration file ``config/config.yaml`` at
:ref:`toplevel_cf`
Inputs
------
- ``resources/country_shapes.geojson``: confer :ref:`shapes`
- ``resources/offshore_shapes.geojson``: confer :ref:`shapes`
- ``networks/base.nc``: confer :ref:`base`
Outputs
-------
- ``resources/regions_onshore.geojson``:
.. image:: img/regions_onshore.png
:scale: 33 %
- ``resources/regions_offshore.geojson``:
.. image:: img/regions_offshore.png
:scale: 33 %
Description
-----------
"""
import logging
import geopandas as gpd
import numpy as np
import pandas as pd
import pypsa
from _helpers import REGION_COLS, configure_logging, set_scenario_config
from scipy.spatial import Voronoi
from shapely.geometry import Polygon
logger = logging.getLogger(__name__)
def voronoi_partition_pts(points, outline):
"""
Compute the polygons of a voronoi partition of `points` within the polygon
`outline`. Taken from
https://github.com/FRESNA/vresutils/blob/master/vresutils/graph.py.
Attributes
----------
points : Nx2 - ndarray[dtype=float]
outline : Polygon
Returns
-------
polygons : N - ndarray[dtype=Polygon|MultiPolygon]
"""
points = np.asarray(points)
if len(points) == 1:
polygons = [outline]
else:
xmin, ymin = np.amin(points, axis=0)
xmax, ymax = np.amax(points, axis=0)
xspan = xmax - xmin
yspan = ymax - ymin
# to avoid any network positions outside all Voronoi cells, append
# the corners of a rectangle framing these points
vor = Voronoi(
np.vstack(
(
points,
[
[xmin - 3.0 * xspan, ymin - 3.0 * yspan],
[xmin - 3.0 * xspan, ymax + 3.0 * yspan],
[xmax + 3.0 * xspan, ymin - 3.0 * yspan],
[xmax + 3.0 * xspan, ymax + 3.0 * yspan],
],
)
)
)
polygons = []
for i in range(len(points)):
poly = Polygon(vor.vertices[vor.regions[vor.point_region[i]]])
if not poly.is_valid:
poly = poly.buffer(0)
with np.errstate(invalid="ignore"):
poly = poly.intersection(outline)
polygons.append(poly)
return polygons
def append_bus_shapes(n, shapes, type):
"""
Append shapes to the network. If shapes with the same component and type
already exist, they will be removed.
Parameters:
n (pypsa.Network): The network to which the shapes will be appended.
shapes (geopandas.GeoDataFrame): The shapes to be appended.
**kwargs: Additional keyword arguments used in `n.madd`.
Returns:
None
"""
remove = n.shapes.query("component == 'Bus' and type == @type").index
n.mremove("Shape", remove)
offset = n.shapes.index.astype(int).max() + 1 if not n.shapes.empty else 0
shapes = shapes.rename(lambda x: int(x) + offset)
n.madd(
"Shape",
shapes.index,
geometry=shapes.geometry,
idx=shapes.name,
component="Bus",
type=type,
)
if __name__ == "__main__":
if "snakemake" not in globals():
from _helpers import mock_snakemake
snakemake = mock_snakemake("build_bus_regions")
configure_logging(snakemake)
set_scenario_config(snakemake)
countries = snakemake.params.countries
base_network = snakemake.input.base_network
n = pypsa.Network(base_network)
country_shapes = gpd.read_file(snakemake.input.country_shapes).set_index("name")[
"geometry"
]
offshore_shapes = gpd.read_file(snakemake.input.offshore_shapes)
offshore_shapes = offshore_shapes.reindex(columns=REGION_COLS).set_index("name")[
"geometry"
]
onshore_regions = []
offshore_regions = []
for country in countries:
c_b = n.buses.country == country
onshore_shape = country_shapes[country]
onshore_locs = (
n.buses.loc[c_b & n.buses.onshore_bus]
.sort_values(
by="substation_lv", ascending=False
) # preference for substations
.drop_duplicates(subset=["x", "y"], keep="first")[["x", "y"]]
)
onshore_regions.append(
gpd.GeoDataFrame(
{
"name": onshore_locs.index,
"x": onshore_locs["x"],
"y": onshore_locs["y"],
"geometry": voronoi_partition_pts(
onshore_locs.values, onshore_shape
),
"country": country,
}
)
)
if country not in offshore_shapes.index:
continue
offshore_shape = offshore_shapes[country]
offshore_locs = n.buses.loc[c_b & n.buses.substation_off, ["x", "y"]]
offshore_regions_c = gpd.GeoDataFrame(
{
"name": offshore_locs.index,
"x": offshore_locs["x"],
"y": offshore_locs["y"],
"geometry": voronoi_partition_pts(offshore_locs.values, offshore_shape),
"country": country,
}
)
offshore_regions_c = offshore_regions_c.loc[offshore_regions_c.area > 1e-2]
offshore_regions.append(offshore_regions_c)
shapes = pd.concat(onshore_regions, ignore_index=True)
shapes.to_file(snakemake.output.regions_onshore)
append_bus_shapes(n, shapes, "onshore")
if offshore_regions:
shapes = pd.concat(offshore_regions, ignore_index=True)
shapes.to_file(snakemake.output.regions_offshore)
append_bus_shapes(n, shapes, "offshore")
else:
offshore_shapes.to_frame().to_file(snakemake.output.regions_offshore)
# save network with shapes
n.export_to_netcdf(base_network)

View File

@ -135,7 +135,7 @@ import pypsa
import seaborn as sns
from _helpers import configure_logging, set_scenario_config, update_p_nom_max
from add_electricity import load_costs
from build_bus_regions import append_bus_shapes
from base_network import append_bus_shapes
from packaging.version import Version, parse
from pypsa.clustering.spatial import (
busmap_by_greedy_modularity,

View File

@ -95,7 +95,7 @@ import pypsa
import scipy as sp
from _helpers import configure_logging, set_scenario_config, update_p_nom_max
from add_electricity import load_costs
from build_bus_regions import append_bus_shapes
from base_network import append_bus_shapes
from cluster_network import cluster_regions, clustering_for_n_clusters
from pypsa.clustering.spatial import (
aggregateoneport,