"""The central module containing all code dealing with fixing ehv subnetworks"""
import geopandas as gpd
import numpy as np
import pandas as pd
from egon.data import config, db, logger
from egon.data.config import settings
from egon.data.datasets import Dataset, DatasetSources, DatasetTargets
from egon.data.datasets.etrago_setup import link_geom_from_buses
from egon.data.datasets.scenario_parameters import get_sector_parameters
[docs]
class FixEhvSubnetworks(Dataset):
"""
Manually fix grid topology in the extra high voltage grid to avoid subnetworks
This dataset includes fixes for the topology of the German extra high voltage grid.
The initial grid topology from openstreetmap resp. osmTGmod includes some issues, eg. because of
incomplete data. Thsi dataset does not fix all those issues, but deals only with subnetworks
in the extra high voltage grid that would result into problems in the grid optimisation.
*Dependencies*
* :py:class:`Osmtgmod <egon.data.datasets.osmtgmod.Osmtgmod>`
*Resulting tables*
* :py:class:`grid.egon_etrago_bus <egon.data.datasets.etrago_setup.EgonPfHvBus>` is updated
* :py:class:`grid.egon_etrago_line <egon.data.datasets.etrago_setup.EgonPfHvLine>` is updated
* :py:class:`grid.egon_etrago_transformer <egon.data.datasets.etrago_setup.EgonPfHvTransformer>` is updated
"""
#:
name: str = "FixEhvSubnetworks"
#:
version: str = "0.0.4"
sources = DatasetSources(
tables={
"buses": "grid.egon_etrago_bus",
"lines": "grid.egon_etrago_line",
"transformers": "grid.egon_etrago_transformer",
}
)
targets = DatasetTargets(
tables={
"buses": "grid.egon_etrago_bus",
"lines": "grid.egon_etrago_line",
"transformers": "grid.egon_etrago_transformer",
}
)
def __init__(self, dependencies):
super().__init__(
name=self.name,
version=self.version,
dependencies=dependencies,
tasks=(run,),
)
[docs]
def select_bus_id(x, y, v_nom, scn_name, carrier, find_closest=False):
bus_id = db.select_dataframe(f"""
SELECT bus_id
FROM {FixEhvSubnetworks.sources.tables['buses']}
WHERE x = {x}
AND y = {y}
AND v_nom = {v_nom}
AND scn_name = '{scn_name}'
AND carrier = '{carrier}'
""")
if bus_id.empty:
logger.info("No bus found")
if find_closest:
logger.info(f"Finding closest to x = {x}, y = {y}")
bus_id = db.select_dataframe(f"""
SELECT bus_id, st_distance(geom, 'SRID=4326;POINT({x} {y})'::geometry)
FROM {FixEhvSubnetworks.sources.tables['buses']}
WHERE v_nom = {v_nom}
AND scn_name = '{scn_name}'
AND carrier = '{carrier}'
ORDER BY st_distance
Limit 1
""")
logger.info(f"Bus ID = {bus_id.bus_id[0]} selected")
return bus_id.bus_id[0]
else:
logger.info("Find closest == False.")
return None
else:
logger.info(f"Exact match with bus ID = {bus_id.bus_id[0]} found.")
return bus_id.bus_id[0]
[docs]
def add_bus(x, y, v_nom, scn_name):
df = pd.DataFrame(
index=[db.next_etrago_id("bus")],
data={
"scn_name": scn_name,
"v_nom": v_nom,
"x": x,
"y": y,
"carrier": "AC",
},
)
gdf = gpd.GeoDataFrame(
df, geometry=gpd.points_from_xy(df.x, df.y, crs=4326)
).rename_geometry("geom")
gdf.index.name = "bus_id"
gdf.reset_index().to_postgis(
FixEhvSubnetworks.targets.get_table_name("buses"),
schema=FixEhvSubnetworks.targets.get_table_schema("buses"),
con=db.engine(),
if_exists="append",
)
[docs]
def drop_bus(x, y, v_nom, scn_name):
bus = select_bus_id(x, y, v_nom, scn_name, carrier="AC")
if bus is not None:
db.execute_sql(f"""
DELETE FROM {FixEhvSubnetworks.targets.tables['buses']}
WHERE
scn_name = '{scn_name}'
AND bus_id = {bus}
AND v_nom = {v_nom}
AND carrier = 'AC'
""")
[docs]
def add_line(x0, y0, x1, y1, v_nom, scn_name, cables):
parameters = get_sector_parameters("electricity", scenario=scn_name)
bus0 = select_bus_id(
x0, y0, v_nom, scn_name, carrier="AC", find_closest=True
)
bus1 = select_bus_id(
x1, y1, v_nom, scn_name, carrier="AC", find_closest=True
)
df = pd.DataFrame(
index=[db.next_etrago_id("line")],
data={
"bus0": bus0,
"bus1": bus1,
"scn_name": scn_name,
"v_nom": v_nom,
"cables": cables,
"carrier": "AC",
},
)
gdf = link_geom_from_buses(df, scn_name)
gdf["length"] = gdf.to_crs(3035).topo.length.mul(1e-3)
# all the values used for x, r and b are taken from the electrical values
# reference table from oemtgmod: github.com/wupperinst/osmTGmod
if v_nom == 220:
s_nom = 520
x_per_km = 0.001 * 2 * np.pi * 50
r_per_km = 0.05475
b_per_km = 11 * 2 * np.pi * 50 * 1e-9
cost_per_km = parameters["capital_cost"]["ac_ehv_overhead_line"]
elif v_nom == 380:
s_nom = 1790
x_per_km = 0.0008 * 2 * np.pi * 50
r_per_km = 0.027375
b_per_km = 14 * 2 * np.pi * 50 * 1e-9
cost_per_km = parameters["capital_cost"]["ac_ehv_overhead_line"]
gdf["s_nom"] = s_nom * gdf["cables"] / 3
gdf["s_nom_extendable"] = True
gdf["lifetime"] = parameters["lifetime"]["ac_ehv_overhead_line"]
gdf["s_nom_min"] = s_nom * gdf["cables"] / 3
gdf["x"] = (x_per_km * gdf["length"]) / (gdf["cables"] / 3)
gdf["r"] = (r_per_km * gdf["length"]) / (gdf["cables"] / 3)
gdf["b"] = (b_per_km * gdf["length"]) * (gdf["cables"] / 3)
gdf["capital_cost"] = (cost_per_km * gdf["length"]) * (gdf["cables"] / 3)
gdf.index.name = "line_id"
gdf.reset_index().to_postgis(
FixEhvSubnetworks.targets.get_table_name("lines"),
schema=FixEhvSubnetworks.targets.get_table_schema("lines"),
con=db.engine(),
if_exists="append",
)
[docs]
def drop_line(x0, y0, x1, y1, v_nom, scn_name):
bus0 = select_bus_id(x0, y0, v_nom, scn_name, carrier="AC")
bus1 = select_bus_id(x1, y1, v_nom, scn_name, carrier="AC")
if (bus0 is not None) and (bus1 is not None):
db.execute_sql(f"""
DELETE FROM {FixEhvSubnetworks.targets.tables['lines']}
WHERE
scn_name = '{scn_name}'
AND bus0 = {bus0}
AND bus1 = {bus1}
AND v_nom = {v_nom}
""")
[docs]
def add_trafo(x, y, v_nom0, v_nom1, scn_name, n=1):
bus0 = select_bus_id(
x, y, v_nom0, scn_name, carrier="AC", find_closest=True
)
bus1 = select_bus_id(
x, y, v_nom1, scn_name, carrier="AC", find_closest=True
)
df = pd.DataFrame(
index=[db.next_etrago_id("transformer")],
data={
"bus0": bus0,
"bus1": bus1,
"scn_name": scn_name,
},
)
gdf = link_geom_from_buses(df, scn_name)
if (v_nom0 == 220) & (v_nom1 == 380):
s_nom = 600
x = 0.0002
gdf["s_nom"] = s_nom * n
gdf["x"] = x / n
gdf.index.name = "trafo_id"
gdf.reset_index().to_postgis(
FixEhvSubnetworks.targets.get_table_name("transformers"),
schema=FixEhvSubnetworks.targets.get_table_schema("transformers"),
con=db.engine(),
if_exists="append",
)
[docs]
def drop_trafo(x, y, v_nom0, v_nom1, scn_name):
bus0 = select_bus_id(x, y, v_nom0, scn_name, carrier="AC")
bus1 = select_bus_id(x, y, v_nom1, scn_name, carrier="AC")
if (bus0 is not None) and (bus1 is not None):
db.execute_sql(f"""
DELETE FROM {FixEhvSubnetworks.targets.tables['transformers']}
WHERE
scn_name = '{scn_name}'
AND bus0 = {bus0}
AND bus1 = {bus1}
""")
[docs]
def fix_subnetworks(scn_name):
# Missing 220kV line to Lübeck Siems
# add 220kV bus at substation Lübeck Siems
add_bus(10.760835327266625, 53.90974536547805, 220, scn_name)
# add 220/380kV transformer at substation Lübeck Siems
add_trafo(10.760835327266625, 53.90974536547805, 220, 380, scn_name)
# add 220kV line from Umspannwerk Lübeck to Lübeck Siems
add_line(
10.760835327266625, # Lübeck Siems
53.90974536547805,
10.641467804496818, # Umspannwerk Lübeck
53.91916269128779,
220,
scn_name,
3,
)
if settings()["egon-data"]["--dataset-boundary"] == "Everything":
# Missing line from USW Uchtelfangen to 'Kraftwerk Weiher'
add_line(
7.032657738999395, # Kraftwerk Weiher
49.33473737285781,
6.996454674906, # Umspannwerk Uchtelfangen
49.3754149606116,
220,
scn_name,
6,
)
if (
select_bus_id(
12.85381530378627, 48.764209444817745, 380, scn_name, "AC"
)
!= None
):
# Missing line from Umspannwerk Plottling to Gänsdorf UW
add_line(
12.85381530378627, # Umspannwerk Plottling
48.764209444817745,
12.769768646403532, # Gänsdorf UW
48.80533685376445,
380,
scn_name,
3,
)
# Missing line inside Ottstedt
add_line(
11.4295305, # Ottstedt
50.9115176,
11.4299277, # Ottstedt
50.911449600000005,
380,
scn_name,
3,
)
[docs]
def run():
for scenario in config.settings()["egon-data"]["--scenarios"]:
fix_subnetworks(scenario)