Completed
Push — dev ( 8582b4...82307e )
by
unknown
30s queued 19s
created

data.datasets.hydrogen_etrago.power_to_h2   A

Complexity

Total Complexity 8

Size/Duplication

Total Lines 242
Duplicated Lines 19.42 %

Importance

Changes 0
Metric Value
wmc 8
eloc 100
dl 47
loc 242
rs 10
c 0
b 0
f 0

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
# -*- coding: utf-8 -*-
2
"""
3
Module containing the definition of the AC grid to H2 links
4
5
In this module the functions used to define and insert into the database
6
the links between H2 and AC buses are to be found.
7
These links are modelling:
8
  * Electrolysis (carrier name: 'power_to_H2'): technology to produce H2
9
    from AC
10
  * Fuel cells (carrier name: 'H2_to_power'): techonology to produce
11
    power from H2
12
  * Waste_heat usage (carrier name: 'PtH2_waste_heat'): Components to use 
13
    waste heat as by-product from electrolysis
14
  * Oxygen usage (carrier name: 'PtH2_O2'): Components to use 
15
    oxygen as by-product from elctrolysis
16
    
17
 
18
"""
19
import pandas as pd
20
import math
21
import geopandas as gpd
22
from itertools import count
23
from sqlalchemy import text
24
from shapely.geometry import MultiLineString, LineString, Point
25
from shapely.wkb import dumps
26
from egon.data import db, config
27
from egon.data.datasets.scenario_parameters import get_sector_parameters
28
from pathlib import Path
29
import numpy as np
30
from shapely.strtree import STRtree
31
32
33
34
def insert_power_to_h2_to_power():
35
    """
36
    Insert electrolysis and fuel cells capacities into the database.
37
    For electrolysis potential waste_heat- and oxygen-utilisation is 
38
    implemented if district_heating-/oxygen-demand is nearby electrolysis
39
    location
40
41
    The potentials for power-to-H2 in electrolysis and H2-to-power in
42
    fuel cells are created between each HVMV Substaion (or each AC_BUS related 
43
    to setting SUBSTATION) and closest H2-Bus (H2 and H2_saltcaverns) inside 
44
    buffer-range of 30km. 
45
    For oxygen-usage all WWTP within MV-district and buffer-range of 10km 
46
    is connected to relevant HVMV Substation
47
    For heat-usage closest central-heat-bus inner an dynamic buffer is connected 
48
    to relevant HVMV-Substation.
49
    
50
    All links are extendable. 
51
52
    This function inserts data into the database and has no return.
53
54
    Parameters
55
    ----------
56
    scn_name : str
57
        Name of the scenario
58
59
    Returns
60
    -------
61
    None
62
63
    """
64
    scenarios = config.settings()["egon-data"]["--scenarios"]  
65
    
66
    # General Constant Parameters  
67
    DATA_CRS = 4326  # default CRS
68
    METRIC_CRS = 32632  # demanded CRS
69
 
70
    ELEC_COST = 60  # [EUR/MWh]
71
    O2_PRESSURE_ELZ = 13  # [bar]
72
    FACTOR_AERATION_EC = 0.6  # [%] aeration EC from total capacity of WWTP (PE)
73
    FACTOR_O2_EC = 0.8  # [%] Oxygen EC from total aeration EC
74
    O2_PRESSURE_MIN = 2  # [bar]
75
    MOLAR_MASS_O2 = 0.0319988  # [kg/mol]
76
    PIPELINE_DIAMETER_RANGE = [0.10, 0.15, 0.20, 0.25, 0.30, 0.40, 0.50]  # [m]
77
    TEMPERATURE = 15 + 273.15  # [Kelvin] degree + 273.15
78
    UNIVERSAL_GAS_CONSTANT = 8.3145  # [J/(mol·K)]
79
  
80
    # Power to O2 (Wastewater Treatment Plants)
81
    WWTP_SEC = {
82
        "c5": 29.6,
83
        "c4": 31.3,
84
        "c3": 39.8,
85
        "c2": 42.1,
86
    }  # [kWh/year] Specific Energy Consumption
87
    
88
    H2 = "h2"
89
    WWTP = "wwtp"
90
    AC = "ac"
91
    H2GRID = "h2_grid"
92
    ACZONE_HVMV = "ac_zone_hvmv"
93
    ACZONE_EHV = "ac_zone_ehv"
94
    ACSUB_HVMV = "ac_sub_hvmv"
95
    ACSUB_EHV = "ac_sub_ehv"
96
    HEAT_BUS = "heat_point"
97
    HEAT_LOAD = "heat_load"
98
    HEAT_TIMESERIES = "heat_timeseries" 
99
    H2_BUSES_CH4 = 'h2_buses_ch4' 
100
    AC_LOAD = 'ac_load'
101
    HEAT_AREA = 'heat_area'
102
    
103
    #buffer_range
104
    buffer_heat_factor= 625  # [m/MW_th] 625/3125 for worstcase/bestcase-Szeanrio
105
    max_buffer_heat= 5000 #[m] 5000/30000 for worstcase/bestcase-Szenario 
106
    Buffer = {
107
        "O2": 5000,  # [m] 
108
        "H2_HVMV": 5000, 
109
        "H2_EHV": 20000,
110
        "HVMV": 10000,
111
        "EHV": 20000,
112
        "HEAT": 5000,
113
    }  
114
    
115
    # connet to PostgreSQL database (to localhost)
116
    engine = db.engine()
117
    
118
    data_config = config.datasets()
119
    sources = data_config["PtH2_waste_heat_O2"]["sources"]
120
    targets = data_config["PtH2_waste_heat_O2"]["targets"]
121
    
122
    for SCENARIO_NAME in scenarios:
123
        
124
        if SCENARIO_NAME not in ["eGon100RE", "eGon2035"]:
125
            continue
126
        
127
        scn_params_gas = get_sector_parameters("gas", SCENARIO_NAME)
128
        scn_params_elec = get_sector_parameters("electricity", SCENARIO_NAME)
129
        
130
        AC_TRANS = scn_params_elec["capital_cost"]["transformer_220_110"]  # [EUR/MW/YEAR]
131
        AC_COST_CABLE = scn_params_elec["capital_cost"]["ac_hv_cable"]   #[EUR/MW/km/YEAR]
132
        ELZ_CAPEX_SYSTEM = scn_params_gas["capital_cost"]["power_to_H2_system"]   # [EUR/MW/YEAR]
133
        ELZ_CAPEX_STACK = scn_params_gas["capital_cost"]["power_to_H2_stack"]  # [EUR/MW/YEAR]
134
        ELZ_LIFETIME_Y = scn_params_gas["lifetime"]["power_to_H2_system"]  # [Year] 
135
        if SCENARIO_NAME == 'eGon2035':
136
            ELZ_OPEX = scn_params_gas["capital_cost"]["power_to_H2_OPEX"]# [EUR/MW/YEAR]
137
        else:
138
            ELZ_OPEX = 0  # [EUR/MW/YEAR] , for eGon100RE OPEX are already included in SYSTEM and STACK costs
139
        H2_COST_PIPELINE = scn_params_gas["capital_cost"]["H2_pipeline"]  #[EUR/MW/km/YEAR] 
140
        ELZ_EFF = scn_params_gas["efficiency"]["power_to_H2"] 
141
        
142
        HEAT_COST_EXCHANGER = scn_params_gas["capital_cost"]["Heat_exchanger"]  # [EUR/MW/YEAR]
143
        HEAT_COST_PIPELINE = scn_params_gas["capital_cost"]["Heat_pipeline"] # [EUR/MW/YEAR]  
144
      
145
        O2_PIPELINE_COSTS = scn_params_gas["O2_capital_cost"]   #[EUR/km/YEAR]
146
        O2_COST_EQUIPMENT = scn_params_gas["capital_cost"]["O2_components"]  #[EUR/MW/YEAR]
147
         
148
        FUEL_CELL_COST = scn_params_gas["capital_cost"]["H2_to_power"]   #[EUR/MW/YEAR]
149
        FUEL_CELL_EFF = scn_params_gas["efficiency"]["H2_to_power"] 
150
        FUEL_CELL_LIFETIME = scn_params_gas["lifetime"]["H2_to_power"]
151
                               
152
        
153
        
154
        def export_o2_buses_to_db(df):
155
            max_bus_id = db.next_etrago_id("bus")
156
            next_bus_id = count(start=max_bus_id, step=1)
157
            schema = targets['buses']['schema']
158
            table_name = targets['buses']['table']
159
160
            db.execute_sql(
161
                        f"DELETE FROM {schema}.{table_name} WHERE carrier = 'O2' AND scn_name='{SCENARIO_NAME}'"
162
                )
163
            df = df.copy(deep=True)
164
            result = []
165
            for _, row in df.iterrows():
166
                bus_id = next(next_bus_id)
167
                result.append(
168
                    {
169
                        "scn_name": SCENARIO_NAME,
170
                        "bus_id": bus_id,
171
                        "v_nom": "110",
172
                        "type": row["KA_ID"],
173
                        "carrier": "O2",
174
                        "x": row["Koord_Kläranlage_rw"],
175
                        "y": row["Koord_Kläranlage_hw"],
176
                        "geom": dumps(
177
                            Point(
178
                                row["Koord_Kläranlage_rw"], row["Koord_Kläranlage_hw"]
179
                            ),
180
                            srid=DATA_CRS,
181
                        ),
182
                        "country": "DE",
183
                    }
184
                )
185
            result_df = pd.DataFrame(result)
186
            result_df.to_sql(table_name, engine, schema=schema, if_exists="append", index=False)
187
        
188
        
189
        wwtp_spec = pd.read_csv(Path(".")/"WWTP_spec.csv")
190
        export_o2_buses_to_db(wwtp_spec)  # Call the function with the dataframe
191
        
192
        # dictionary of SQL queries
193
        queries = {
194
            WWTP: f"""
195
                    SELECT bus_id AS id, geom, type AS ka_id
196
                    FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]}
197
                    WHERE carrier in ('O2') AND scn_name = '{SCENARIO_NAME}'
198
                    """,
199
            H2: f"""
200
                    SELECT bus_id AS id, geom 
201
                    FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]}
202
                    WHERE carrier in ('H2_grid', 'H2')
203
                    AND scn_name = '{SCENARIO_NAME}'
204
                    AND country = 'DE'
205
                    """,
206
            H2GRID: f"""
207
                    SELECT link_id, geom, bus0, bus1
208
                    FROM {sources["links"]["schema"]}.{sources["links"]["table"]}
209
                    WHERE carrier in ('H2_grid') AND scn_name  = '{SCENARIO_NAME}'
210
                    """,
211
            AC: f"""
212
                    SELECT bus_id AS id, geom
213
                    FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]}
214
                    WHERE carrier in ('AC')
215
                    AND scn_name = '{SCENARIO_NAME}'
216
                    AND v_nom = '110'
217
                    """,
218
            ACSUB_HVMV: f"""
219
                    SELECT bus_id AS id, point AS geom
220
                    FROM {sources["hvmv_substation"]["schema"]}.{sources["hvmv_substation"]["table"]}
221
                    """,
222
            ACSUB_EHV:f"""
223
                    SELECT bus_id AS id, point AS geom
224
                    FROM {sources["ehv_substation"]["schema"]}.{sources["ehv_substation"]["table"]}
225
                    """,
226
            ACZONE_HVMV: f"""
227
                    SELECT bus_id AS id, ST_Transform(geom, 4326) as geom
228
                    FROM {sources["mv_districts"]["schema"]}.{sources["mv_districts"]["table"]}
229
                    """,
230
            ACZONE_EHV: f"""
231
                    SELECT bus_id AS id, ST_Transform(geom, 4326) as geom
232
                    FROM {sources["ehv_voronoi"]["schema"]}.{sources["ehv_voronoi"]["table"]}
233
                    """,
234
            HEAT_BUS: f"""
235
        			SELECT bus_id AS id, geom
236
        			FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]}
237
        			WHERE carrier in ('central_heat')
238
                    AND scn_name = '{SCENARIO_NAME}'
239
                    AND country = 'DE'
240
                    """,
241
        }
242
243
        dfs = {
244
            key: gpd.read_postgis(queries[key], engine, crs=DATA_CRS).to_crs(METRIC_CRS)
245
            for key in queries.keys()
246
            }
247
        
248
        with engine.connect() as conn:
249
            conn.execute(
250
                        text(
251
                            f"""DELETE FROM {targets["links"]["schema"]}.{targets["links"]["table"]}
252
                            WHERE carrier IN ('power_to_H2', 'H2_to_power', 'PtH2_waste_heat', 'PtH2_O2') 
253
                            AND scn_name = '{SCENARIO_NAME}' AND bus0 IN (
254
                              SELECT bus_id
255
                              FROM {targets["buses"]["schema"]}.{targets["buses"]["table"]}
256
                              WHERE country = 'DE'
257
                            )
258
                            """
259
                        )
260
                    )   
261
            
262
        def prepare_dataframes_for_spartial_queries():
263
               
264
            #filter_out_potential_methanisation_buses
265
            h2_grid_bus_ids=tuple(dfs[H2GRID]['bus1']) + tuple(dfs[H2GRID]['bus0'])
266
            dfs[H2_BUSES_CH4] = dfs[H2][~dfs[H2]['id'].isin(h2_grid_bus_ids)]
267
            
268
            #prepare h2_links for filtering:
269
            # extract geometric data for bus0
270
            merged_link_with_bus0_geom = pd.merge(dfs[H2GRID], dfs[H2], left_on='bus0', right_on='id', how='left')
271
            merged_link_with_bus0_geom = merged_link_with_bus0_geom.rename(columns={'geom_y': 'geom_bus0'}).rename(columns={'geom_x': 'geom_link'})
272
            
273
            # extract geometric data for bus1
274
            merged_link_with_bus1_geom = pd.merge(merged_link_with_bus0_geom, dfs[H2], left_on='bus1', right_on='id', how='left')
275
            merged_link_with_bus1_geom = merged_link_with_bus1_geom.rename(columns={'geom': 'geom_bus1'})
276
            merged_link_with_bus1_geom = merged_link_with_bus1_geom[merged_link_with_bus1_geom['geom_bus1'] != None] #delete all abroad_links
277
            
278
            #prepare heat_buses for filtering
279
            queries[HEAT_AREA]=f"""
280
                     SELECT area_id, geom_polygon as geom
281
                     FROM  {sources["district_heating_area"]["schema"]}.{sources["district_heating_area"]["table"]}  
282
                     WHERE scenario = '{SCENARIO_NAME}'
283
                     """
284
            dfs[HEAT_AREA] = gpd.read_postgis(queries[HEAT_AREA], engine).to_crs(METRIC_CRS)    
285
            
286
            heat_bus_geoms = dfs[HEAT_BUS]['geom'].tolist()
287
            heat_bus_index = STRtree(heat_bus_geoms)
288
            
289
290
            for _, heat_area_row in dfs[HEAT_AREA].iterrows():
291
                heat_area_geom = heat_area_row['geom']
292
                area_id = heat_area_row['area_id']
293
                
294
                potential_matches = heat_bus_index.query(heat_area_geom)
295
                
296
                nearest_bus_idx = None
297
                nearest_distance = float('inf')
298
299
                for bus_idx in potential_matches:
300
                    bus_geom = dfs[HEAT_BUS].at[bus_idx, 'geom']
301
                    
302
                    distance = heat_area_geom.centroid.distance(bus_geom)
303
                    if distance < nearest_distance:
304
                        nearest_distance = distance
305
                        nearest_bus_idx = bus_idx
306
            
307
                if nearest_bus_idx is not None:
308
                    dfs[HEAT_BUS].at[nearest_bus_idx, 'area_id'] = area_id
309
                    dfs[HEAT_BUS].at[nearest_bus_idx, 'area_geom'] = heat_area_geom
310
            
311
                    
312
            
313
            dfs[HEAT_BUS]['area_geom'] = gpd.GeoSeries(dfs[HEAT_BUS]['area_geom'])
314
            
315
            queries[HEAT_LOAD] = f"""
316
                        SELECT bus, load_id 
317
            			FROM {sources["loads"]["schema"]}.{sources["loads"]["table"]}
318
            			WHERE carrier in ('central_heat')
319
                        AND scn_name = '{SCENARIO_NAME}'
320
                        """
321
            dfs[HEAT_LOAD] = pd.read_sql(queries[HEAT_LOAD], engine)
322
            load_ids=tuple(dfs[HEAT_LOAD]['load_id'])
323
324
            queries[HEAT_TIMESERIES] = f"""
325
                SELECT load_id, p_set
326
                FROM {sources["load_timeseries"]["schema"]}.{sources["load_timeseries"]["table"]}
327
                WHERE load_id IN {load_ids}
328
                AND scn_name = '{SCENARIO_NAME}'
329
                """  
330
            dfs[HEAT_TIMESERIES] = pd.read_sql(queries[HEAT_TIMESERIES], engine)
331
            dfs[HEAT_TIMESERIES]['sum_of_p_set'] = dfs[HEAT_TIMESERIES]['p_set'].apply(sum)
332
            dfs[HEAT_TIMESERIES].drop('p_set', axis=1, inplace=True)
333
            dfs[HEAT_TIMESERIES].dropna(subset=['sum_of_p_set'], inplace=True)
334
            dfs[HEAT_LOAD] = pd.merge(dfs[HEAT_LOAD], dfs[HEAT_TIMESERIES], on='load_id')
335
            dfs[HEAT_BUS] = pd.merge(dfs[HEAT_BUS], dfs[HEAT_LOAD], left_on='id', right_on='bus', how='inner')
336
            dfs[HEAT_BUS]['p_mean'] = dfs[HEAT_BUS]['sum_of_p_set'].apply(lambda x: x / 8760)
337
            dfs[HEAT_BUS]['buffer'] = dfs[HEAT_BUS]['p_mean'].apply(lambda x: x*buffer_heat_factor)
338
            dfs[HEAT_BUS]['buffer'] = dfs[HEAT_BUS]['buffer'].apply(lambda x: x if x < max_buffer_heat else max_buffer_heat)  
339
            
340
            return merged_link_with_bus1_geom, dfs[HEAT_BUS], dfs[H2_BUSES_CH4]
341
342
343
344
        def find_h2_grid_connection(df_AC, df_h2, buffer_h2, buffer_AC, sub_type):
345
            df_h2['buffer'] = df_h2['geom_link'].buffer(buffer_h2)
346
            df_AC['buffer'] = df_AC['geom'].buffer(buffer_AC)
347
            
348
            h2_index = STRtree(df_h2['buffer'].tolist())
349
            
350
            results = []
351
            
352
            for idx, row in df_AC.iterrows():
353
                buffered_AC = row['buffer']
354
                
355
                possible_matches_idx = h2_index.query(buffered_AC)
356
                
357
                nearest_match = None
358
                nearest_distance = float('inf')
359
                
360
                for match_idx in possible_matches_idx:
361
                    h2_row = df_h2.iloc[match_idx] 
362
                    
363
                    if buffered_AC.intersects(h2_row['buffer']):
364
                        intersection = buffered_AC.intersection(h2_row['buffer'])
365
                        
366
                        if not intersection.is_empty:
367
                            distance_AC = row['geom'].distance(intersection.centroid)
368
                            distance_H2 = h2_row['geom_link'].distance(intersection.centroid)
369
                            distance_to_0 = row['geom'].distance(h2_row['geom_bus0'])
370
                            distance_to_1 = row['geom'].distance(h2_row['geom_bus1'])
371
                            
372
                            if distance_to_0 < distance_to_1:
373
                                bus_H2 = h2_row['bus0']
374
                                point_H2 = h2_row['geom_bus0']
375
                            else:
376
                                bus_H2 = h2_row['bus1']
377
                                point_H2 = h2_row['geom_bus1']
378
                            
379
                            if distance_H2 < nearest_distance:
380
                                nearest_distance = distance_H2
381
                                nearest_match = {
382
                                    'bus_h2': bus_H2,
383
                                    'bus_AC': row['id'],
384
                                    'geom_h2': point_H2,
385
                                    'geom_AC': row['geom'],
386
                                    'distance_h2': distance_H2,
387
                                    'distance_ac': distance_AC,
388
                                    'intersection': intersection,
389
                                    'sub_type': sub_type,
390
                                }
391
                
392
                if nearest_match:
393
                    results.append(nearest_match)
394
            
395
            if not results:
396
                return pd.DataFrame(columns=['bus_h2', 'bus_AC', 'geom_h2', 'geom_AC', 'distance_h2', 'distance_ac', 'intersection', 'sub_type'])
397
            else: 
398
                return pd.DataFrame(results)
399
400
401
        def find_h2_bus_connection(df_H2, df_AC, buffer_h2, buffer_AC, sub_type):
402
            
403
            df_H2['buffer'] = df_H2['geom'].buffer(buffer_h2)
404
            df_AC['buffer'] = df_AC['geom'].buffer(buffer_AC)
405
            
406
            h2_index = STRtree(df_H2['buffer'].tolist())
407
            
408
            results = []
409
            for _, row in df_AC.iterrows():
410
                possible_matches_idx = h2_index.query(row['buffer'])
411
                
412
                nearest_match = None
413
                nearest_distance = float('inf')
414
                
415
                for match_idx in possible_matches_idx:
416
                    h2_row = df_H2.iloc[match_idx]  
417
                    
418
                    if row['buffer'].intersects(h2_row['buffer']):
419
                        intersection = row['buffer'].intersection(h2_row['buffer'])
420
                        distance_AC = row['geom'].distance(intersection.centroid)
421
                        distance_H2 = h2_row['geom'].distance(intersection.centroid)
422
                        
423
                        if (distance_AC + distance_H2) < nearest_distance:
424
                            nearest_distance = distance_AC + distance_H2
425
                            nearest_match = {
426
                                'bus_h2': h2_row['id'],
427
                                'bus_AC': row['id'],
428
                                'geom_h2': h2_row['geom'],
429
                                'geom_AC': row['geom'],
430
                                'distance_h2': distance_H2,
431
                                'distance_ac': distance_AC,
432
                                'intersection': intersection,
433
                                'sub_type': sub_type,
434
                            }
435
                
436
                if nearest_match:
437
                    results.append(nearest_match)
438
            
439
            if not results:
440
                return pd.DataFrame(columns=['bus_h2', 'bus_AC', 'geom_h2', 'geom_AC', 'distance_h2', 'distance_ac', 'intersection', 'sub_type'])
441
            else: 
442
                return pd.DataFrame(results)
443
444
445
        def find_h2_connection(df_h2):
446
            ####find H2-HVMV connection:
447
            potential_location_grid = find_h2_grid_connection(dfs[ACSUB_HVMV], df_h2, Buffer['H2_HVMV'], Buffer['HVMV'],'HVMV')
448
            potential_location_grid = potential_location_grid.loc[potential_location_grid.groupby(['bus_h2', 'bus_AC'])['distance_h2'].idxmin()]
449
            
450
            filtered_df_hvmv = dfs[ACSUB_HVMV][~dfs[ACSUB_HVMV]['id'].isin(potential_location_grid['bus_AC'])].copy()
451
            potential_location_buses = find_h2_bus_connection(dfs[H2_BUSES_CH4], filtered_df_hvmv,  Buffer['H2_HVMV'], Buffer['HVMV'], 'HVMV')
452
            potential_location_buses = potential_location_buses.loc[potential_location_buses.groupby(['bus_h2', 'bus_AC'])['distance_h2'].idxmin()]
453
            
454
            potential_location_hvmv = pd.concat([potential_location_grid, potential_location_buses], ignore_index = True)
455
            
456
            ####find H2-EHV connection:
457
            potential_location_grid = find_h2_grid_connection(dfs[ACSUB_EHV], df_h2, Buffer['H2_EHV'], Buffer['EHV'],'EHV')
458
            potential_location_grid = potential_location_grid.loc[potential_location_grid.groupby(['bus_h2', 'bus_AC'])['distance_h2'].idxmin()]
459
            
460
            filtered_df_ehv = dfs[ACSUB_EHV][~dfs[ACSUB_EHV]['id'].isin(potential_location_grid['bus_AC'])].copy()
461
            potential_location_buses = find_h2_bus_connection(dfs[H2_BUSES_CH4], filtered_df_ehv,  Buffer['H2_EHV'], Buffer['EHV'], 'EHV')
462
            potential_location_buses = potential_location_buses.loc[potential_location_buses.groupby(['bus_h2', 'bus_AC'])['distance_h2'].idxmin()]
463
            
464
            potential_location_ehv = pd.concat([potential_location_grid, potential_location_buses], ignore_index = True)
465
466
            ### combined potential ehv- and hvmv-connections:
467
            return pd.concat([potential_location_hvmv, potential_location_ehv], ignore_index = True)
468
469
470
471
        def find_heat_connection(potential_locations):
472
473
            dfs[HEAT_BUS]['buffered_geom'] = dfs[HEAT_BUS]['area_geom'].buffer(dfs[HEAT_BUS]['buffer'])
474
            intersection_index = STRtree(potential_locations['intersection'].tolist())
475
476
            potential_locations['bus_heat'] = None
477
            potential_locations['geom_heat'] = None
478
            potential_locations['distance_heat'] = None
479
            
480
            results= []
481
482
            for _, heat_row in dfs[HEAT_BUS].iterrows():
483
                buffered_geom = heat_row['buffered_geom']
484
485
                potential_matches = intersection_index.query(buffered_geom)
486
                
487
                if len(potential_matches) > 0:
488
                    nearest_distance = float('inf')
489
                    nearest_ac_index = None
490
491
                    for match_idx in potential_matches:
492
                        ac_row = potential_locations.iloc[match_idx]  # Hole die entsprechende Zeile
493
                        
494
                        if buffered_geom.intersects(ac_row['intersection']):
495
                            distance = buffered_geom.centroid.distance(ac_row['intersection'].centroid)
496
                            
497
                            if distance < nearest_distance:
498
                                nearest_distance = distance
499
                                nearest_ac_index = match_idx
500
501
                    if nearest_ac_index is not None:
502
                        results.append({
503
                            'bus_AC': potential_locations.at[nearest_ac_index, 'bus_AC'],
504
                            'bus_heat': heat_row['id'],
505
                            'geom_AC': potential_locations.at[nearest_ac_index, 'geom_AC'],
506
                            'geom_heat': heat_row['geom'],
507
                            'distance_heat': distance
508
                        })
509
                        potential_locations.at[nearest_ac_index, 'bus_heat'] = heat_row['id']
510
                        potential_locations.at[nearest_ac_index, 'geom_heat'] = heat_row['geom']
511
                        potential_locations.at[nearest_ac_index, 'distance_heat'] = nearest_distance
512
513
            return pd.DataFrame(results)
514
515
516
517
518
        def find_o2_connections(df_o2, potential_locations, sub_id):
519
            
520
            df_o2['hvmv_id'] = None
521
            for _, district_row in dfs[ACZONE_HVMV].iterrows():
522
                district_geom = district_row['geom']
523
                district_id = district_row['id']
524
                
525
                mask = df_o2['geom'].apply(lambda x: x.within(district_geom))
526
                df_o2.loc[mask, 'hvmv_id'] = district_id
527
                
528
            df_o2['ehv_id'] = None
529
            for _, district_row in dfs[ACZONE_EHV].iterrows():
530
                district_geom = district_row['geom']
531
                district_id = district_row['id']
532
                
533
                mask = df_o2['geom'].apply(lambda x: x.within(district_geom))
534
                df_o2.loc[mask, 'ehv_id'] = district_id
535
                
536
            intersection_geometries = potential_locations['intersection'].tolist()
537
            intersection_tree = STRtree(intersection_geometries)
538
            
539
            results = []
540
541
            for _, o2_row in df_o2.iterrows():
542
                o2_buffer = o2_row['geom'].buffer(Buffer['O2'])  
543
                o2_id = o2_row['id']  
544
                o2_district_id = o2_row[sub_id] 
545
546
                possible_matches = intersection_tree.query(o2_buffer)
547
548
                for match_idx in possible_matches:
549
                    ac_row = potential_locations.iloc[match_idx]
550
                    intersection_centroid = ac_row['intersection'].centroid
551
552
553
         
554
                    if ac_row['bus_AC'] == o2_district_id and o2_buffer.intersects(ac_row['intersection']):           
555
                        distance = intersection_centroid.distance(o2_buffer.centroid)
556
                        results.append({
557
                            'bus_AC': ac_row['bus_AC'],
558
                            'bus_O2': o2_id,
559
                            'geom_AC':  ac_row['geom_AC'],
560
                            'geom_O2': o2_row['geom'],
561
                            'distance_O2': distance,
562
                            'KA_ID': o2_row['ka_id']
563
                        })
564
565
            return pd.DataFrame(results)
566
567
568
569
        def find_spec_for_ka_id(ka_id):
570
            found_spec = wwtp_spec[wwtp_spec["KA_ID"] == ka_id]
571
            if len(found_spec) > 1:
572
                raise Exception("multiple spec for a ka_id")
573
            found_spec = found_spec.iloc[0]
574
            return {
575
                "pe": found_spec["Nominalbelastung 2020 [EW]"],
576
                "demand_o2": found_spec["Sauerstoff 2035 gesamt [t/a]"],
577
            }
578
579
580
        def calculate_wwtp_capacity(pe):  # [MWh/year]
581
            c = "c2"
582
            if pe > 100_000:
583
                c = "c5"
584
            elif pe > 10_000 and pe <= 100_000:
585
                c = "c4"
586
            elif pe > 2000 and pe <= 10_000:
587
                c = "c3"
588
            return pe * WWTP_SEC[c] / 1000
589
590
        def gas_pipeline_size(gas_volume_y, distance, input_pressure, molar_mass, min_pressure):
591
            """
592
                Parameters
593
                ----------
594
                gas_valume : kg/year
595
                distance : km
596
                input pressure : bar
597
                min pressure : bar
598
                molar mas : kg/mol
599
                Returns
600
            -------
601
            Final pressure drop [bar] & pipeline diameter [m]
602
            """
603
604
            def _calculate_final_pressure(pipeline_diameter):
605
                flow_rate = (
606
                    (gas_volume_y / (8760 * molar_mass))
607
                    * UNIVERSAL_GAS_CONSTANT
608
                    * TEMPERATURE
609
                    / (input_pressure * 100_000)
610
                )  # m3/hour
611
                flow_rate_s = flow_rate / 3600  # m3/second
612
                pipeline_area = math.pi * (pipeline_diameter / 2) ** 2  # m2
613
                gas_velocity = flow_rate_s / pipeline_area  # m/s
614
                gas_density = (input_pressure * 1e5 * molar_mass) / (
615
                    UNIVERSAL_GAS_CONSTANT * TEMPERATURE
616
                )  # kg/m3
617
                reynolds_number = (
618
                    gas_density * gas_velocity * pipeline_diameter
619
                ) / UNIVERSAL_GAS_CONSTANT
620
                # Estimate Darcy friction factor using Moody's approximation
621
                darcy_friction_factor = 0.0055 * (
622
                    1 + (2 * 1e4 * (2.51 / reynolds_number)) ** (1 / 3)
623
                )
624
                # Darcy-Weisbach equation
625
                pressure_drop = (
626
                    (4 * darcy_friction_factor * distance * 1000 * gas_velocity**2)
627
                    / (2 * pipeline_diameter)
628
                ) / 1e5  # bar
629
                return input_pressure - pressure_drop  # bar
630
631
            for diameter in PIPELINE_DIAMETER_RANGE:
632
                final_pressure = _calculate_final_pressure(diameter)
633
                if final_pressure > min_pressure:
634
                    return (round(final_pressure, 4), round(diameter, 4))
635
            raise Exception("couldn't find a final pressure < min_pressure")
636
            
637
            
638
        # O2 pipeline diameter cost range
639
        def get_o2_pipeline_cost(o2_pipeline_diameter):
640
           for diameter in sorted(O2_PIPELINE_COSTS.keys(), reverse=True):
641
               if o2_pipeline_diameter >= float(diameter):
642
                   return O2_PIPELINE_COSTS[diameter]
643
      
644
645
646
        def create_link_dataframes(links_h2, links_heat, links_O2):
647
648
            etrago_columns = [
649
                "scn_name",
650
                "link_id",
651
                "bus0",
652
                "bus1",
653
                "carrier",
654
                "efficiency",
655
                "lifetime",
656
                "p_nom",
657
                "p_nom_max",
658
                "p_nom_extendable",
659
                "capital_cost",
660
                "length",
661
                "geom",
662
                "topo",
663
            ]
664
665
            power_to_H2 = pd.DataFrame(columns=etrago_columns)
666
            H2_to_power = pd.DataFrame(columns=etrago_columns)
667
            power_to_Heat = pd.DataFrame(columns=etrago_columns)
668
            power_to_O2 = pd.DataFrame(columns=etrago_columns)
669
            
670
            max_link_id =  db.next_etrago_id("link")
671
            next_max_link_id = count(start=max_link_id, step=1)
672
            
673
674
            ####poower_to_H2
675
            for idx, row in links_h2.iterrows():
676
                capital_cost_H2 = H2_COST_PIPELINE * row['distance_h2']/1000 + ELZ_CAPEX_STACK + ELZ_CAPEX_SYSTEM + ELZ_OPEX  # [EUR/MW/YEAR]
677
                capital_cost_AC = AC_COST_CABLE * row['distance_ac']/1000 + AC_TRANS # [EUR/MW/YEAR]
678
                capital_cost_PtH2 = capital_cost_AC + capital_cost_H2
679
                
680
                power_to_H2_entry = {
681
                    "scn_name": SCENARIO_NAME,
682
                    "link_id": next(next_max_link_id),
683
                    "bus0": row["bus_AC"],
684
                    "bus1": row["bus_h2"],
685
                    "carrier": "power_to_H2",
686
                    "efficiency": ELZ_EFF,    
687
                    "lifetime": ELZ_LIFETIME_Y,  
688
                    "p_nom": 0,  
689
                    "p_nom_max": 120 if row['sub_type'] == 'HVMV' else 5000, 
690
                    "p_nom_extendable": True,
691
                    "capital_cost": capital_cost_PtH2,   
692
                    "geom": MultiLineString(
693
                        [LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_h2'].x, row['geom_h2'].y)])]
694
                    ),  
695
                    "topo": LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_h2'].x, row['geom_h2'].y)]
696
                    ),  
697
                }
698
                power_to_H2 = pd.concat([power_to_H2, pd.DataFrame([power_to_H2_entry])], ignore_index=True)
699
                
700
                ####H2_to_power
701
                capital_cost_H2 = H2_COST_PIPELINE * row['distance_h2']/1000 + FUEL_CELL_COST # [EUR/MW/YEAR]
702
                capital_cost_AC = AC_COST_CABLE * row['distance_ac']/1000 + AC_TRANS # [EUR/MW/YEAR]
703
                capital_cost_H2tP = capital_cost_AC + capital_cost_H2
704
                H2_to_power_entry = {
705
                    "scn_name": SCENARIO_NAME,
706
                    "link_id": next(next_max_link_id),
707
                    "bus0": row["bus_h2"],
708
                    "bus1": row["bus_AC"],
709
                    "carrier": "H2_to_power",
710
                    "efficiency": FUEL_CELL_EFF,    
711
                    "lifetime": FUEL_CELL_LIFETIME,  
712
                    "p_nom": 0,  
713
                    "p_nom_max": 120 if row['sub_type'] == 'HVMV' else 5000, 
714
                    "p_nom_extendable": True,
715
                    "capital_cost": capital_cost_H2tP,   
716
                    "geom": MultiLineString(
717
                        [LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_h2'].x, row['geom_h2'].y)])]
718
                    ),  
719
                    "topo": LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_h2'].x, row['geom_h2'].y)]
720
                    ),  
721
                }
722
                H2_to_power = pd.concat([H2_to_power, pd.DataFrame([H2_to_power_entry])], ignore_index=True)
723
724
            ###power_to_Heat
725
            for idx, row in links_heat.iterrows():
726
                capital_cost = HEAT_COST_EXCHANGER + HEAT_COST_PIPELINE*row['distance_heat']/1000 #EUR/MW/YEAR
727
                
728
                power_to_heat_entry = {
729
                    "scn_name": SCENARIO_NAME,
730
                    "link_id": next(next_max_link_id),
731
                    "bus0": row["bus_AC"],
732
                    "bus1": row["bus_heat"],
733
                    "carrier": "PtH2_waste_heat",
734
                    "efficiency": 1,  
735
                    "lifetime": 25,  
736
                    "p_nom": 0,  
737
                    "p_nom_max": float('inf'),  
738
                    "p_nom_extendable": True,
739
                    "capital_cost": capital_cost,  
740
                    "geom": MultiLineString(
741
                        [LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_heat'].x, row['geom_heat'].y)])]
742
                    ),  
743
                    "topo": LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_heat'].x, row['geom_heat'].y)]
744
                    ), 
745
                }
746
                power_to_Heat = pd.concat([power_to_Heat, pd.DataFrame([power_to_heat_entry])], ignore_index=True)
747
                
748
              
749
            ####power_to_O2   
750
            for idx, row in links_O2.iterrows():
751
                distance = row['distance_O2']/1000 #km
752
                ka_id = row["KA_ID"]
753
                spec = find_spec_for_ka_id(ka_id)
754
                wwtp_ec = calculate_wwtp_capacity(spec["pe"])  # [MWh/year]
755
                aeration_ec = wwtp_ec * FACTOR_AERATION_EC  # [MWh/year]
756
                o2_ec = aeration_ec * FACTOR_O2_EC  # [MWh/year]
757
                o2_ec_h = o2_ec / 8760  # [MWh/hour]
758
                total_o2_demand =  spec["demand_o2"] * 1000  # kgO2/year pure O2 tonne* 1000
759
                _, o2_pipeline_diameter = gas_pipeline_size(
760
                    total_o2_demand,
761
                    distance,
762
                    O2_PRESSURE_ELZ,
763
                    MOLAR_MASS_O2,
764
                    O2_PRESSURE_MIN,
765
                )
766
                annualized_cost_o2_pipeline = get_o2_pipeline_cost(o2_pipeline_diameter) # [EUR/KM/YEAR]
767
                annualized_cost_o2_component = O2_COST_EQUIPMENT #EUR/MW/YEAR
768
                capital_costs=annualized_cost_o2_pipeline * distance/o2_ec_h + annualized_cost_o2_component
769
                
770
                power_to_o2_entry = {
771
                    "scn_name": SCENARIO_NAME,
772
                    "link_id": next(next_max_link_id),
773
                    "bus0": row["bus_AC"],
774
                    "bus1": row["bus_O2"],
775
                    "carrier": "PtH2_O2",
776
                    "efficiency": 1,  
777
                    "lifetime": 25,  
778
                    "p_nom": o2_ec_h,  
779
                    "p_nom_max": float('inf'),  
780
                    "p_nom_extendable": True,
781
                    "capital_cost": capital_costs,  
782
                    "geom": MultiLineString(
783
                        [LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_O2'].x, row['geom_O2'].y)])]
784
                    ),  
785
                    "topo": LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_O2'].x, row['geom_O2'].y)]),  
786
                }
787
                power_to_O2 = pd.concat([power_to_O2, pd.DataFrame([power_to_o2_entry])], ignore_index=True)
788
789
            return power_to_H2, H2_to_power, power_to_Heat, power_to_O2
790
791
792
793
        def export_links_to_db(df, carrier):
794
            schema=targets["links"]["schema"]
795
            table_name=targets["links"]["table"]
796
797
            gdf = gpd.GeoDataFrame(df, geometry="geom").set_crs(METRIC_CRS)
798
            gdf = gdf.to_crs(epsg=DATA_CRS)
799
            gdf.p_nom = 0
800
            
801
            try:
802
                gdf.to_postgis(
803
                    name=table_name,  
804
                    con=engine,  
805
                    schema=schema,  
806
                    if_exists="append",  
807
                    index=False,  
808
                )
809
                print(f"Links have been exported to {schema}.{table_name}")
810
            except Exception as e:
811
                print(f"Error while exporting link data: {e}")
812
                
813
814
815
        def insert_o2_load_points(df):
816
            new_id = db.next_etrago_id('load')
817
            next_load_id = count(start=new_id, step=1)
818
            schema =  targets["loads"]["schema"]
819
            table_name = targets["loads"]["table"]
820
            with engine.connect() as conn:
821
                conn.execute(
822
                    f"DELETE FROM {schema}.{table_name} WHERE carrier = 'O2' AND scn_name = '{SCENARIO_NAME}'"
823
                )
824
            df = df.copy(deep=True)
825
            df = df.drop_duplicates(subset='bus1', keep='first')
826
            result = []
827
            for _, row in df.iterrows():
828
                load_id = next(next_load_id)
829
                result.append(
830
                    {
831
                        "scn_name": SCENARIO_NAME,
832
                        "load_id": load_id,
833
                        "bus": row["bus1"],
834
                        "carrier": "O2",
835
                        "o2_load_el": row["p_nom"],
836
                    }
837
                )
838
            df = pd.DataFrame(result)
839
            df[['scn_name', 'load_id', 'bus', 'carrier']].to_sql(table_name, engine, schema=schema, if_exists="append", index=False)
840
            print(f"O2 load data exported to: {table_name}")
841
            return df
842
            
843
        def insert_o2_load_timeseries(df):
844
            query_o2_timeseries = f"""
845
                        SELECT load_curve
846
            			FROM {sources["o2_load_profile"]["schema"]}.{sources["o2_load_profile"]["table"]}
847
            			WHERE slp = 'G3' AND wz = 3
848
                        """
849
                        
850
            base_load_profile = pd.read_sql(query_o2_timeseries, engine)['load_curve'].values
851
            base_load_profile = np.array(base_load_profile[0])
852
            
853
            with engine.connect() as conn:
854
                conn.execute(f"""
855
                    DELETE FROM {targets["load_timeseries"]["schema"]}.{targets["load_timeseries"]["table"]} 
856
                    WHERE load_id IN {tuple(df.load_id.values)} 
857
                    AND scn_name = '{SCENARIO_NAME}'
858
                    """
859
                )
860
861
            timeseries_list = []
862
863
            for index, row in df.iterrows():
864
                load_id = row['load_id']  # ID aus der aktuellen Zeile
865
                o2_load_el = row['o2_load_el']  # Nennleistung aus der aktuellen Zeile
866
                
867
                modified_profile = base_load_profile * o2_load_el
868
                
869
                timeseries_list.append({
870
                    'scn_name': SCENARIO_NAME,
871
                    'load_id': load_id,
872
                    'temp_id': 1,
873
                    'p_set': modified_profile,
874
                    'bus': row['bus']
875
                })
876
877
            timeseries_df = pd.DataFrame(timeseries_list)
878
            timeseries_df['p_set'] = timeseries_df['p_set'].apply(lambda x: x.tolist() if isinstance(x, np.ndarray) else x)
879
            timeseries_df[['scn_name', 'load_id', 'temp_id', 'p_set']].to_sql(
880
                targets["load_timeseries"]["table"], 
881
                engine,
882
                schema=targets["load_timeseries"]["schema"], 
883
                if_exists="append", 
884
                index=False)
885
886
            return timeseries_df
887
888
889
        def insert_o2_generators(df):
890
            new_id = db.next_etrago_id("generator")
891
            next_generator_id = count(start=new_id, step=1)
892
            
893
            grid = targets["generators"]["schema"]
894
            table_name = targets["generators"]["table"]
895
            with engine.connect() as conn:
896
                conn.execute(
897
                    f"DELETE FROM {grid}.{table_name} WHERE carrier = 'O2' AND scn_name = '{SCENARIO_NAME}'"
898
                )
899
            df = df.copy(deep=True)
900
            df = df.drop_duplicates(subset='bus1', keep='first')
901
            result = []
902
            for _, row in df.iterrows():
903
                generator_id = next(next_generator_id)
904
                result.append(
905
                    {
906
                        "scn_name": SCENARIO_NAME,
907
                        "generator_id": generator_id,
908
                        "bus": row["bus1"],
909
                        "carrier": "O2",
910
                        "p_nom_extendable": "true",
911
                        "marginal_cost": ELEC_COST, 
912
                    }
913
                )
914
            df = pd.DataFrame(result)
915
            df.to_sql(table_name, engine, schema=grid, if_exists="append", index=False)
916
917
            print(f"generator data exported to: {table_name}")
918
919
920
        
921
        def adjust_ac_load_timeseries(df, o2_timeseries):
922
            #filter out affected ac_loads
923
            queries[AC_LOAD] = f"""
924
                                SELECT bus, load_id 
925
                    			FROM {sources["loads"]["schema"]}.{sources["loads"]["table"]}
926
                                WHERE scn_name = '{SCENARIO_NAME}'
927
                                """
928
            dfs[AC_LOAD] = pd.read_sql(queries[AC_LOAD], engine)
929
            df = df.drop_duplicates(subset='bus1', keep='first')
930
            ac_loads = pd.merge(df, dfs[AC_LOAD], left_on='bus0', right_on='bus')
931
            
932
            #reduce each affected ac_load with o2_timeseries
933
            for _, row in ac_loads.iterrows():
934
                with engine.connect() as conn:
935
936
                    select_query = text(f"""
937
                        SELECT p_set 
938
                        FROM {sources["load_timeseries"]["schema"]}.{sources["load_timeseries"]["table"]}
939
                        WHERE load_id = :load_id and scn_name= :SCENARIO_NAME
940
                        """)
941
                    result = conn.execute(select_query, {"load_id": row["load_id"], "SCENARIO_NAME": SCENARIO_NAME}).fetchone()
942
                    
943
                    if result:
944
                         original_p_set = result["p_set"]                         
945
                         o2_timeseries_row = o2_timeseries.loc[o2_timeseries['bus'] == row['bus1']]
946
                         
947
                         if not o2_timeseries_row.empty:
948
                             o2_p_set = o2_timeseries_row.iloc[0]['p_set']
949
                             
950
                             if len(original_p_set) == len(o2_p_set):
951
                                 # reduce ac_load with o2_load_timeseries
952
                                 adjusted_p_set = (np.array(original_p_set) - np.array(o2_p_set)).tolist()
953
                                 update_query = text(f"""
954
                                     UPDATE {targets["load_timeseries"]["schema"]}.{targets["load_timeseries"]["table"]}
955
                                     SET p_set = :adjusted_p_set
956
                                     WHERE load_id = :load_id AND scn_name = :SCENARIO_NAME
957
                                 """)
958
                                 conn.execute(update_query, {"adjusted_p_set": adjusted_p_set, "load_id": row["load_id"], "SCENARIO_NAME": SCENARIO_NAME})
959
                             else:
960
                                 print(f"Length mismatch for load_id {row['load_id']}: original={len(original_p_set)}, o2={len(o2_p_set)}")
961
                         else:
962
                             print(f"No matching o2_timeseries entry for load_id {row['load_id']}")
963
                             
964
        def delete_unconnected_o2_buses():
965
            with engine.connect() as conn:
966
                conn.execute(f"""
967
                    DELETE FROM {targets['buses']['schema']}.{targets['buses']['table']} 
968
                    WHERE carrier = 'O2' AND scn_name = '{SCENARIO_NAME}'
969
                    AND bus_id NOT IN (SELECT bus1 FROM {targets['links']['schema']}.{targets['links']['table']} 
970
                                       WHERE carrier = 'PtH2_O2')
971
                    """
972
                )
973
                             
974
975
        def execute_PtH2_method():
976
            
977
            h2_grid_geom_df, dfs[HEAT_BUS], dfs[H2_BUSES_CH4] = prepare_dataframes_for_spartial_queries()
978
            potential_locations=find_h2_connection(h2_grid_geom_df)
979
            heat_links = find_heat_connection(potential_locations)
980
            o2_links_hvmv = find_o2_connections(dfs[WWTP], potential_locations[potential_locations.sub_type=='HVMV'], 'hvmv_id')
981
            o2_links_ehv = find_o2_connections(dfs[WWTP], potential_locations[potential_locations.sub_type=='EHV'], 'ehv_id')
982
            o2_links=pd.concat([o2_links_hvmv, o2_links_ehv], ignore_index=True)
983
            power_to_H2, H2_to_power, power_to_Heat, power_to_O2 = create_link_dataframes(potential_locations, heat_links, o2_links)
984
            export_links_to_db(power_to_H2,'power_to_H2')
985
            export_links_to_db(power_to_Heat, 'PtH2_waste_heat')
986
            export_links_to_db(power_to_O2, 'PtH2_O2')
987
            export_links_to_db(H2_to_power, 'H2_to_power')
988
            o2_loads_df = insert_o2_load_points(power_to_O2)
989
            o2_timeseries = insert_o2_load_timeseries(o2_loads_df)
990
            insert_o2_generators(power_to_O2)
991
            adjust_ac_load_timeseries(power_to_O2, o2_timeseries) 
992
            delete_unconnected_o2_buses()
993
          
994
        execute_PtH2_method()
995
                         
996
997