1
|
|
|
# -*- coding: utf-8 -*-
|
2
|
|
|
"""
|
3
|
|
|
The central module containing all code dealing with importing gas stores
|
4
|
|
|
|
5
|
|
|
This module contains the functions to import the existing methane stores
|
6
|
|
|
in Germany and to insert them into the database. They are modelled as
|
7
|
|
|
PyPSA stores and are not extendable.
|
8
|
|
|
|
9
|
|
|
"""
|
10
|
|
|
from pathlib import Path
|
11
|
|
|
from telnetlib import GA
|
12
|
|
|
import ast
|
13
|
|
|
|
14
|
|
|
import geopandas
|
15
|
|
|
import numpy as np
|
16
|
|
|
import pandas as pd
|
17
|
|
|
|
18
|
|
|
from egon.data import config, db
|
19
|
|
|
from egon.data.config import settings
|
20
|
|
|
from egon.data.datasets import Dataset
|
21
|
|
|
from egon.data.datasets.gas_grid import (
|
22
|
|
|
ch4_nodes_number_G,
|
23
|
|
|
define_gas_nodes_list,
|
24
|
|
|
)
|
25
|
|
|
from egon.data.datasets.scenario_parameters import get_sector_parameters
|
26
|
|
|
|
27
|
|
|
|
28
|
|
|
class GasStores(Dataset):
|
29
|
|
|
"""
|
30
|
|
|
Insert the non extendable gas stores in Germany into the database
|
31
|
|
|
|
32
|
|
|
Insert the non extendable gas stores into the database in Germany
|
33
|
|
|
for the scnenarios eGon2035 and eGon100RE using the function
|
34
|
|
|
:py:func:`insert_ch4_storages`.
|
35
|
|
|
|
36
|
|
|
*Dependencies*
|
37
|
|
|
* :py:class:`GasAreaseGon2035 <egon.data.datasets.gas_areas.GasAreaseGon2035>`
|
38
|
|
|
* :py:class:`GasAreaseGon2035 <egon.data.datasets.gas_areas.GasAreaseGon100RE>`
|
39
|
|
|
* :py:class:`GasNodesAndPipes <egon.data.datasets.gas_grid.GasNodesAndPipes>`
|
40
|
|
|
|
41
|
|
|
*Resulting tables*
|
42
|
|
|
* :py:class:`grid.egon_etrago_store <egon.data.datasets.etrago_setup.EgonPfHvStore>` is extended
|
43
|
|
|
|
44
|
|
|
|
45
|
|
|
"""
|
46
|
|
|
|
47
|
|
|
#:
|
48
|
|
|
name: str = "GasStores"
|
49
|
|
|
#:
|
50
|
|
|
version: str = "0.0.3"
|
51
|
|
|
|
52
|
|
|
def __init__(self, dependencies):
|
53
|
|
|
super().__init__(
|
54
|
|
|
name=self.name,
|
55
|
|
|
version=self.version,
|
56
|
|
|
dependencies=dependencies,
|
57
|
|
|
tasks=(insert_gas_stores_DE),
|
58
|
|
|
)
|
59
|
|
|
|
60
|
|
|
|
61
|
|
|
def import_installed_ch4_storages(scn_name):
|
62
|
|
|
"""
|
63
|
|
|
Define list of CH4 stores from the SciGRID_gas data
|
64
|
|
|
|
65
|
|
|
This function reads from the SciGRID_gas dataset the existing CH4
|
66
|
|
|
cavern stores in Germany, adjusts and returns them.
|
67
|
|
|
Caverns reference: SciGRID_gas dataset (datasets/gas_data/data/IGGIELGN_Storages.csv
|
68
|
|
|
downloaded in :func:`download_SciGRID_gas_data <egon.data.datasets.gas_grid.download_SciGRID_gas_data>`).
|
69
|
|
|
For more information on these data, refer to the
|
70
|
|
|
`SciGRID_gas IGGIELGN documentation <https://zenodo.org/record/4767098>`_.
|
71
|
|
|
|
72
|
|
|
Parameters
|
73
|
|
|
----------
|
74
|
|
|
scn_name : str
|
75
|
|
|
Name of the scenario
|
76
|
|
|
|
77
|
|
|
Returns
|
78
|
|
|
-------
|
79
|
|
|
Gas_storages_list :
|
80
|
|
|
Dataframe containing the CH4 cavern stores units in Germany
|
81
|
|
|
|
82
|
|
|
"""
|
83
|
|
|
target_file = (
|
84
|
|
|
Path(".") / "datasets" / "gas_data" / "data" / "IGGIELGN_Storages.csv"
|
85
|
|
|
)
|
86
|
|
|
|
87
|
|
|
Gas_storages_list = pd.read_csv(
|
88
|
|
|
target_file,
|
89
|
|
|
delimiter=";",
|
90
|
|
|
decimal=".",
|
91
|
|
|
usecols=["lat", "long", "country_code", "param"],
|
92
|
|
|
)
|
93
|
|
|
|
94
|
|
|
Gas_storages_list = Gas_storages_list[
|
95
|
|
|
Gas_storages_list["country_code"].str.match("DE")
|
96
|
|
|
]
|
97
|
|
|
|
98
|
|
|
# Define new columns
|
99
|
|
|
max_workingGas_M_m3 = []
|
100
|
|
|
NUTS1 = []
|
101
|
|
|
end_year = []
|
102
|
|
|
for index, row in Gas_storages_list.iterrows():
|
103
|
|
|
param = ast.literal_eval(row["param"])
|
104
|
|
|
NUTS1.append(param["nuts_id_1"])
|
105
|
|
|
end_year.append(param["end_year"])
|
106
|
|
|
max_workingGas_M_m3.append(param["max_workingGas_M_m3"])
|
107
|
|
|
|
108
|
|
|
Gas_storages_list = Gas_storages_list.assign(NUTS1=NUTS1)
|
109
|
|
|
|
110
|
|
|
# Calculate e_nom
|
111
|
|
|
conv_factor = 10830 # gross calorific value = 39 MJ/m3 (eurogas.org)
|
112
|
|
|
Gas_storages_list["e_nom"] = [conv_factor * i for i in max_workingGas_M_m3]
|
113
|
|
|
|
114
|
|
|
end_year = [float("inf") if x == None else x for x in end_year]
|
115
|
|
|
Gas_storages_list = Gas_storages_list.assign(end_year=end_year)
|
116
|
|
|
|
117
|
|
|
# Cut data to federal state if in testmode
|
118
|
|
|
boundary = settings()["egon-data"]["--dataset-boundary"]
|
119
|
|
|
if boundary != "Everything":
|
120
|
|
|
map_states = {
|
121
|
|
|
"Baden-Württemberg": "DE1",
|
122
|
|
|
"Nordrhein-Westfalen": "DEA",
|
123
|
|
|
"Hessen": "DE7",
|
124
|
|
|
"Brandenburg": "DE4",
|
125
|
|
|
"Bremen": "DE5",
|
126
|
|
|
"Rheinland-Pfalz": "DEB",
|
127
|
|
|
"Sachsen-Anhalt": "DEE",
|
128
|
|
|
"Schleswig-Holstein": "DEF",
|
129
|
|
|
"Mecklenburg-Vorpommern": "DE8",
|
130
|
|
|
"Thüringen": "DEG",
|
131
|
|
|
"Niedersachsen": "DE9",
|
132
|
|
|
"Sachsen": "DED",
|
133
|
|
|
"Hamburg": "DE6",
|
134
|
|
|
"Saarland": "DEC",
|
135
|
|
|
"Berlin": "DE3",
|
136
|
|
|
"Bayern": "DE2",
|
137
|
|
|
}
|
138
|
|
|
|
139
|
|
|
Gas_storages_list = Gas_storages_list[
|
140
|
|
|
Gas_storages_list["NUTS1"].isin([map_states[boundary], np.nan])
|
141
|
|
|
]
|
142
|
|
|
|
143
|
|
|
# Remove unused storage units
|
144
|
|
|
Gas_storages_list = Gas_storages_list[
|
145
|
|
|
Gas_storages_list["end_year"] >= 2035
|
146
|
|
|
]
|
147
|
|
|
|
148
|
|
|
Gas_storages_list = Gas_storages_list.rename(
|
149
|
|
|
columns={"lat": "y", "long": "x"}
|
150
|
|
|
)
|
151
|
|
|
Gas_storages_list = geopandas.GeoDataFrame(
|
152
|
|
|
Gas_storages_list,
|
153
|
|
|
geometry=geopandas.points_from_xy(
|
154
|
|
|
Gas_storages_list["x"], Gas_storages_list["y"]
|
155
|
|
|
),
|
156
|
|
|
)
|
157
|
|
|
Gas_storages_list = Gas_storages_list.rename(
|
158
|
|
|
columns={"geometry": "geom"}
|
159
|
|
|
).set_geometry("geom", crs=4326)
|
160
|
|
|
|
161
|
|
|
# Match to associated gas bus
|
162
|
|
|
Gas_storages_list = Gas_storages_list.reset_index(drop=True)
|
163
|
|
|
Gas_storages_list = db.assign_gas_bus_id(
|
164
|
|
|
Gas_storages_list, scn_name, "CH4"
|
165
|
|
|
)
|
166
|
|
|
|
167
|
|
|
# Remove useless columns
|
168
|
|
|
Gas_storages_list = Gas_storages_list.drop(
|
169
|
|
|
columns=[
|
170
|
|
|
"x",
|
171
|
|
|
"y",
|
172
|
|
|
"param",
|
173
|
|
|
"country_code",
|
174
|
|
|
"NUTS1",
|
175
|
|
|
"end_year",
|
176
|
|
|
"geom",
|
177
|
|
|
"bus_id",
|
178
|
|
|
]
|
179
|
|
|
)
|
180
|
|
|
|
181
|
|
|
return Gas_storages_list
|
182
|
|
|
|
183
|
|
|
|
184
|
|
|
def import_gas_grid_capacity(scn_name, carrier):
|
185
|
|
|
"""
|
186
|
|
|
Define the gas stores modelling the store capacity of the grid
|
187
|
|
|
|
188
|
|
|
Define dataframe containing the modelling of the grid storage
|
189
|
|
|
capacity. The whole storage capacity of the grid (130000 MWh,
|
190
|
|
|
estimation of the Bundesnetzagentur) is split uniformly between
|
191
|
|
|
all the German gas nodes of the grid (without consideration of the
|
192
|
|
|
capacities of the pipes).
|
193
|
|
|
In eGon100RE, the storage capacity of the grid is split between H2
|
194
|
|
|
and CH4 stores, with the same share than the pipes capacity (value
|
195
|
|
|
calculated in the p-e-s run).
|
196
|
|
|
|
197
|
|
|
Parameters
|
198
|
|
|
----------
|
199
|
|
|
scn_name : str
|
200
|
|
|
Name of the scenario
|
201
|
|
|
carrier : str
|
202
|
|
|
Name of the carrier
|
203
|
|
|
|
204
|
|
|
Returns
|
205
|
|
|
-------
|
206
|
|
|
Gas_storages_list :
|
207
|
|
|
List of gas stores in Germany modelling the gas grid storage capacity
|
208
|
|
|
|
209
|
|
|
"""
|
210
|
|
|
scn_params = get_sector_parameters("gas", scn_name)
|
211
|
|
|
# Select source from dataset configuration
|
212
|
|
|
source = config.datasets()["gas_stores"]["source"]
|
213
|
|
|
|
214
|
|
|
Gas_grid_capacity = 130000 # Storage capacity of the CH4 grid - G.Volk "Die Herauforderung an die Bundesnetzagentur die Energiewende zu meistern" Berlin, Dec 2012
|
215
|
|
|
N_ch4_nodes_G = ch4_nodes_number_G(
|
216
|
|
|
define_gas_nodes_list()
|
217
|
|
|
) # Number of nodes in Germany
|
218
|
|
|
Store_capacity = (
|
219
|
|
|
Gas_grid_capacity / N_ch4_nodes_G
|
220
|
|
|
) # Storage capacity associated to each CH4 node of the German grid
|
221
|
|
|
|
222
|
|
|
sql_gas = f"""SELECT bus_id, geom
|
223
|
|
|
FROM {source['buses']['schema']}.{source['buses']['table']}
|
224
|
|
|
WHERE carrier = '{carrier}' AND scn_name = '{scn_name}'
|
225
|
|
|
AND country = 'DE';"""
|
226
|
|
|
Gas_storages_list = db.select_geodataframe(sql_gas, epsg=4326)
|
227
|
|
|
|
228
|
|
|
# Add missing column
|
229
|
|
|
Gas_storages_list["bus"] = Gas_storages_list["bus_id"]
|
230
|
|
|
|
231
|
|
|
if scn_name == "eGon100RE" and carrier == "CH4":
|
232
|
|
|
Gas_storages_list["e_nom"] = Store_capacity * (
|
233
|
|
|
1
|
234
|
|
|
- get_sector_parameters("gas", scn_name)[
|
235
|
|
|
"retrofitted_CH4pipeline-to-H2pipeline_share"
|
236
|
|
|
]
|
237
|
|
|
)
|
238
|
|
|
elif scn_name == "eGon2035" and carrier == "CH4":
|
239
|
|
|
Gas_storages_list["e_nom"] = Store_capacity
|
240
|
|
|
elif scn_name == "eGon100RE" and carrier == "H2_grid":
|
241
|
|
|
Gas_storages_list["e_nom"] = Store_capacity * (
|
242
|
|
|
scn_params["retrofitted_CH4pipeline-to-H2pipeline_share"]
|
243
|
|
|
)
|
244
|
|
|
|
245
|
|
|
# Remove useless columns
|
246
|
|
|
Gas_storages_list = Gas_storages_list.drop(columns=["bus_id", "geom"])
|
247
|
|
|
|
248
|
|
|
return Gas_storages_list
|
249
|
|
|
|
250
|
|
|
|
251
|
|
|
def insert_gas_stores_germany(scn_name, carrier):
|
252
|
|
|
"""
|
253
|
|
|
Insert gas stores for specific scenario
|
254
|
|
|
|
255
|
|
|
Insert non extendable gas stores for specific scenario in Germany
|
256
|
|
|
by executing the following steps:
|
257
|
|
|
* Clean the database
|
258
|
|
|
* For CH4 stores, call the functions
|
259
|
|
|
:py:func:`import_installed_ch4_storages` to receive the CH4
|
260
|
|
|
cavern stores and :py:func:`import_gas_grid_capacity` to
|
261
|
|
|
receive the CH4 stores modelling the storage capacity of the
|
262
|
|
|
grid.
|
263
|
|
|
* For H2 stores, call only the function
|
264
|
|
|
:py:func:`import_gas_grid_capacity` to receive the H2 stores
|
265
|
|
|
modelling the storage capacity of the grid.
|
266
|
|
|
* Aggregate of the stores attached to the same bus
|
267
|
|
|
* Add the missing columns: store_id, scn_name, carrier, e_cyclic
|
268
|
|
|
* Insert the stores in the database
|
269
|
|
|
|
270
|
|
|
Parameters
|
271
|
|
|
----------
|
272
|
|
|
scn_name : str
|
273
|
|
|
Name of the scenario
|
274
|
|
|
carrier: str
|
275
|
|
|
Name of the carrier
|
276
|
|
|
|
277
|
|
|
Returns
|
278
|
|
|
-------
|
279
|
|
|
None
|
280
|
|
|
|
281
|
|
|
"""
|
282
|
|
|
# Connect to local database
|
283
|
|
|
engine = db.engine()
|
284
|
|
|
|
285
|
|
|
# Select target from dataset configuration
|
286
|
|
|
source = config.datasets()["gas_stores"]["source"]
|
287
|
|
|
target = config.datasets()["gas_stores"]["target"]
|
288
|
|
|
|
289
|
|
|
# Clean table
|
290
|
|
|
db.execute_sql(
|
291
|
|
|
f"""
|
292
|
|
|
DELETE FROM {target['stores']['schema']}.{target['stores']['table']}
|
293
|
|
|
WHERE "carrier" = '{carrier}'
|
294
|
|
|
AND scn_name = '{scn_name}'
|
295
|
|
|
AND bus IN (
|
296
|
|
|
SELECT bus_id FROM {source['buses']['schema']}.{source['buses']['table']}
|
297
|
|
|
WHERE scn_name = '{scn_name}'
|
298
|
|
|
AND country = 'DE'
|
299
|
|
|
);
|
300
|
|
|
"""
|
301
|
|
|
)
|
302
|
|
|
|
303
|
|
|
if carrier == "CH4":
|
304
|
|
|
gas_storages_list = pd.concat(
|
305
|
|
|
[
|
306
|
|
|
import_installed_ch4_storages(scn_name),
|
307
|
|
|
import_gas_grid_capacity(scn_name, carrier),
|
308
|
|
|
]
|
309
|
|
|
)
|
310
|
|
|
elif carrier == "H2":
|
311
|
|
|
gas_storages_list = import_gas_grid_capacity(scn_name, "H2_grid")
|
312
|
|
|
|
313
|
|
|
# Aggregate ch4 stores with same properties at the same bus
|
314
|
|
|
gas_storages_list = (
|
315
|
|
|
gas_storages_list.groupby(["bus"])
|
|
|
|
|
316
|
|
|
.agg({"e_nom": "sum"})
|
317
|
|
|
.reset_index(drop=False)
|
318
|
|
|
)
|
319
|
|
|
|
320
|
|
|
# Add missing columns
|
321
|
|
|
c = {"scn_name": scn_name, "carrier": carrier, "e_cyclic": True}
|
322
|
|
|
gas_storages_list = gas_storages_list.assign(**c)
|
323
|
|
|
|
324
|
|
|
new_id = db.next_etrago_id("store")
|
325
|
|
|
gas_storages_list["store_id"] = range(
|
326
|
|
|
new_id, new_id + len(gas_storages_list)
|
327
|
|
|
)
|
328
|
|
|
|
329
|
|
|
# Insert data to db
|
330
|
|
|
gas_storages_list.to_sql(
|
331
|
|
|
target["stores"]["table"],
|
332
|
|
|
engine,
|
333
|
|
|
schema=target["stores"]["schema"],
|
334
|
|
|
index=False,
|
335
|
|
|
if_exists="append",
|
336
|
|
|
)
|
337
|
|
|
|
338
|
|
|
|
339
|
|
|
def insert_gas_stores_DE():
|
340
|
|
|
"""
|
341
|
|
|
Overall function to import non extendable gas stores in Germany
|
342
|
|
|
|
343
|
|
|
This function calls :py:func:`insert_gas_stores_germany` three
|
344
|
|
|
times to insert the corresponding non extendable gas stores in the
|
345
|
|
|
database:
|
346
|
|
|
* The CH4 stores for eGon2035: caverns from SciGRID_gas data and
|
347
|
|
|
modelling of the storage grid capacity
|
348
|
|
|
* The CH4 stores for eGon100RE: caverns from SciGRID_gas data and
|
349
|
|
|
modelling of the storage grid capacity (split with H2)
|
350
|
|
|
* The H2 stores for eGon100RE: caverns from SciGRID_gas data and
|
351
|
|
|
modelling of the storage grid capacity (split with CH4)
|
352
|
|
|
This function has no return.
|
353
|
|
|
|
354
|
|
|
"""
|
355
|
|
|
insert_gas_stores_germany("eGon2035", "CH4")
|
356
|
|
|
insert_gas_stores_germany("eGon100RE", "CH4")
|
357
|
|
|
insert_gas_stores_germany("eGon100RE", "H2")
|
358
|
|
|
|