Passed
Pull Request — dev (#1281)
by
unknown
02:02
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(
190
            Path(".")
191
            / "data_bundle_egon_data"
192
            / "hydrogen_network"
193
            / "WWTP_spec.csv"
194
        )
195
        export_o2_buses_to_db(wwtp_spec)  # Call the function with the dataframe
196
        
197
        # dictionary of SQL queries
198
        queries = {
199
            WWTP: f"""
200
                    SELECT bus_id AS id, geom, type AS ka_id
201
                    FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]}
202
                    WHERE carrier in ('O2') AND scn_name = '{SCENARIO_NAME}'
203
                    """,
204
            H2: f"""
205
                    SELECT bus_id AS id, geom 
206
                    FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]}
207
                    WHERE carrier in ('H2_grid', 'H2')
208
                    AND scn_name = '{SCENARIO_NAME}'
209
                    AND country = 'DE'
210
                    """,
211
            H2GRID: f"""
212
                    SELECT link_id, geom, bus0, bus1
213
                    FROM {sources["links"]["schema"]}.{sources["links"]["table"]}
214
                    WHERE carrier in ('H2_grid') AND scn_name  = '{SCENARIO_NAME}'
215
                    """,
216
            AC: f"""
217
                    SELECT bus_id AS id, geom
218
                    FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]}
219
                    WHERE carrier in ('AC')
220
                    AND scn_name = '{SCENARIO_NAME}'
221
                    AND v_nom = '110'
222
                    """,
223
            ACSUB_HVMV: f"""
224
                    SELECT bus_id AS id, point AS geom
225
                    FROM {sources["hvmv_substation"]["schema"]}.{sources["hvmv_substation"]["table"]}
226
                    """,
227
            ACSUB_EHV:f"""
228
                    SELECT bus_id AS id, point AS geom
229
                    FROM {sources["ehv_substation"]["schema"]}.{sources["ehv_substation"]["table"]}
230
                    """,
231
            ACZONE_HVMV: f"""
232
                    SELECT bus_id AS id, ST_Transform(geom, 4326) as geom
233
                    FROM {sources["mv_districts"]["schema"]}.{sources["mv_districts"]["table"]}
234
                    """,
235
            ACZONE_EHV: f"""
236
                    SELECT bus_id AS id, ST_Transform(geom, 4326) as geom
237
                    FROM {sources["ehv_voronoi"]["schema"]}.{sources["ehv_voronoi"]["table"]}
238
                    """,
239
            HEAT_BUS: f"""
240
        			SELECT bus_id AS id, geom
241
        			FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]}
242
        			WHERE carrier in ('central_heat')
243
                    AND scn_name = '{SCENARIO_NAME}'
244
                    AND country = 'DE'
245
                    """,
246
        }
247
248
        dfs = {
249
            key: gpd.read_postgis(queries[key], engine, crs=DATA_CRS).to_crs(METRIC_CRS)
250
            for key in queries.keys()
251
            }
252
        
253
        with engine.connect() as conn:
254
            conn.execute(
255
                        text(
256
                            f"""DELETE FROM {targets["links"]["schema"]}.{targets["links"]["table"]}
257
                            WHERE carrier IN ('power_to_H2', 'H2_to_power', 'PtH2_waste_heat', 'PtH2_O2') 
258
                            AND scn_name = '{SCENARIO_NAME}' AND bus0 IN (
259
                              SELECT bus_id
260
                              FROM {targets["buses"]["schema"]}.{targets["buses"]["table"]}
261
                              WHERE country = 'DE'
262
                            )
263
                            """
264
                        )
265
                    )   
266
            
267
        def prepare_dataframes_for_spartial_queries():
268
               
269
            #filter_out_potential_methanisation_buses
270
            h2_grid_bus_ids=tuple(dfs[H2GRID]['bus1']) + tuple(dfs[H2GRID]['bus0'])
271
            dfs[H2_BUSES_CH4] = dfs[H2][~dfs[H2]['id'].isin(h2_grid_bus_ids)]
272
            
273
            #prepare h2_links for filtering:
274
            # extract geometric data for bus0
275
            merged_link_with_bus0_geom = pd.merge(dfs[H2GRID], dfs[H2], left_on='bus0', right_on='id', how='left')
276
            merged_link_with_bus0_geom = merged_link_with_bus0_geom.rename(columns={'geom_y': 'geom_bus0'}).rename(columns={'geom_x': 'geom_link'})
277
            
278
            # extract geometric data for bus1
279
            merged_link_with_bus1_geom = pd.merge(merged_link_with_bus0_geom, dfs[H2], left_on='bus1', right_on='id', how='left')
280
            merged_link_with_bus1_geom = merged_link_with_bus1_geom.rename(columns={'geom': 'geom_bus1'})
281
            merged_link_with_bus1_geom = merged_link_with_bus1_geom[merged_link_with_bus1_geom['geom_bus1'] != None] #delete all abroad_links
282
            
283
            #prepare heat_buses for filtering
284
            queries[HEAT_AREA]=f"""
285
                     SELECT area_id, geom_polygon as geom
286
                     FROM  {sources["district_heating_area"]["schema"]}.{sources["district_heating_area"]["table"]}  
287
                     WHERE scenario = '{SCENARIO_NAME}'
288
                     """
289
            dfs[HEAT_AREA] = gpd.read_postgis(queries[HEAT_AREA], engine).to_crs(METRIC_CRS)    
290
            
291
            heat_bus_geoms = dfs[HEAT_BUS]['geom'].tolist()
292
            heat_bus_index = STRtree(heat_bus_geoms)
293
            
294
295
            for _, heat_area_row in dfs[HEAT_AREA].iterrows():
296
                heat_area_geom = heat_area_row['geom']
297
                area_id = heat_area_row['area_id']
298
                
299
                potential_matches = heat_bus_index.query(heat_area_geom)
300
                
301
                nearest_bus_idx = None
302
                nearest_distance = float('inf')
303
304
                for bus_idx in potential_matches:
305
                    bus_geom = dfs[HEAT_BUS].at[bus_idx, 'geom']
306
                    
307
                    distance = heat_area_geom.centroid.distance(bus_geom)
308
                    if distance < nearest_distance:
309
                        nearest_distance = distance
310
                        nearest_bus_idx = bus_idx
311
            
312
                if nearest_bus_idx is not None:
313
                    dfs[HEAT_BUS].at[nearest_bus_idx, 'area_id'] = area_id
314
                    dfs[HEAT_BUS].at[nearest_bus_idx, 'area_geom'] = heat_area_geom
315
            
316
                    
317
            
318
            dfs[HEAT_BUS]['area_geom'] = gpd.GeoSeries(dfs[HEAT_BUS]['area_geom'])
319
            
320
            queries[HEAT_LOAD] = f"""
321
                        SELECT bus, load_id 
322
            			FROM {sources["loads"]["schema"]}.{sources["loads"]["table"]}
323
            			WHERE carrier in ('central_heat')
324
                        AND scn_name = '{SCENARIO_NAME}'
325
                        """
326
            dfs[HEAT_LOAD] = pd.read_sql(queries[HEAT_LOAD], engine)
327
            load_ids=tuple(dfs[HEAT_LOAD]['load_id'])
328
329
            queries[HEAT_TIMESERIES] = f"""
330
                SELECT load_id, p_set
331
                FROM {sources["load_timeseries"]["schema"]}.{sources["load_timeseries"]["table"]}
332
                WHERE load_id IN {load_ids}
333
                AND scn_name = '{SCENARIO_NAME}'
334
                """  
335
            dfs[HEAT_TIMESERIES] = pd.read_sql(queries[HEAT_TIMESERIES], engine)
336
            dfs[HEAT_TIMESERIES]['sum_of_p_set'] = dfs[HEAT_TIMESERIES]['p_set'].apply(sum)
337
            dfs[HEAT_TIMESERIES].drop('p_set', axis=1, inplace=True)
338
            dfs[HEAT_TIMESERIES].dropna(subset=['sum_of_p_set'], inplace=True)
339
            dfs[HEAT_LOAD] = pd.merge(dfs[HEAT_LOAD], dfs[HEAT_TIMESERIES], on='load_id')
340
            dfs[HEAT_BUS] = pd.merge(dfs[HEAT_BUS], dfs[HEAT_LOAD], left_on='id', right_on='bus', how='inner')
341
            dfs[HEAT_BUS]['p_mean'] = dfs[HEAT_BUS]['sum_of_p_set'].apply(lambda x: x / 8760)
342
            dfs[HEAT_BUS]['buffer'] = dfs[HEAT_BUS]['p_mean'].apply(lambda x: x*buffer_heat_factor)
343
            dfs[HEAT_BUS]['buffer'] = dfs[HEAT_BUS]['buffer'].apply(lambda x: x if x < max_buffer_heat else max_buffer_heat)  
344
            
345
            return merged_link_with_bus1_geom, dfs[HEAT_BUS], dfs[H2_BUSES_CH4]
346
347
348
349
        def find_h2_grid_connection(df_AC, df_h2, buffer_h2, buffer_AC, sub_type):
350
            df_h2['buffer'] = df_h2['geom_link'].buffer(buffer_h2)
351
            df_AC['buffer'] = df_AC['geom'].buffer(buffer_AC)
352
            
353
            h2_index = STRtree(df_h2['buffer'].tolist())
354
            
355
            results = []
356
            
357
            for idx, row in df_AC.iterrows():
358
                buffered_AC = row['buffer']
359
                
360
                possible_matches_idx = h2_index.query(buffered_AC)
361
                
362
                nearest_match = None
363
                nearest_distance = float('inf')
364
                
365
                for match_idx in possible_matches_idx:
366
                    h2_row = df_h2.iloc[match_idx] 
367
                    
368
                    if buffered_AC.intersects(h2_row['buffer']):
369
                        intersection = buffered_AC.intersection(h2_row['buffer'])
370
                        
371
                        if not intersection.is_empty:
372
                            distance_AC = row['geom'].distance(intersection.centroid)
373
                            distance_H2 = h2_row['geom_link'].distance(intersection.centroid)
374
                            distance_to_0 = row['geom'].distance(h2_row['geom_bus0'])
375
                            distance_to_1 = row['geom'].distance(h2_row['geom_bus1'])
376
                            
377
                            if distance_to_0 < distance_to_1:
378
                                bus_H2 = h2_row['bus0']
379
                                point_H2 = h2_row['geom_bus0']
380
                            else:
381
                                bus_H2 = h2_row['bus1']
382
                                point_H2 = h2_row['geom_bus1']
383
                            
384
                            if distance_H2 < nearest_distance:
385
                                nearest_distance = distance_H2
386
                                nearest_match = {
387
                                    'bus_h2': bus_H2,
388
                                    'bus_AC': row['id'],
389
                                    'geom_h2': point_H2,
390
                                    'geom_AC': row['geom'],
391
                                    'distance_h2': distance_H2,
392
                                    'distance_ac': distance_AC,
393
                                    'intersection': intersection,
394
                                    'sub_type': sub_type,
395
                                }
396
                
397
                if nearest_match:
398
                    results.append(nearest_match)
399
            
400
            if not results:
401
                return pd.DataFrame(columns=['bus_h2', 'bus_AC', 'geom_h2', 'geom_AC', 'distance_h2', 'distance_ac', 'intersection', 'sub_type'])
402
            else: 
403
                return pd.DataFrame(results)
404
405
406
        def find_h2_bus_connection(df_H2, df_AC, buffer_h2, buffer_AC, sub_type):
407
            
408
            df_H2['buffer'] = df_H2['geom'].buffer(buffer_h2)
409
            df_AC['buffer'] = df_AC['geom'].buffer(buffer_AC)
410
            
411
            h2_index = STRtree(df_H2['buffer'].tolist())
412
            
413
            results = []
414
            for _, row in df_AC.iterrows():
415
                possible_matches_idx = h2_index.query(row['buffer'])
416
                
417
                nearest_match = None
418
                nearest_distance = float('inf')
419
                
420
                for match_idx in possible_matches_idx:
421
                    h2_row = df_H2.iloc[match_idx]  
422
                    
423
                    if row['buffer'].intersects(h2_row['buffer']):
424
                        intersection = row['buffer'].intersection(h2_row['buffer'])
425
                        distance_AC = row['geom'].distance(intersection.centroid)
426
                        distance_H2 = h2_row['geom'].distance(intersection.centroid)
427
                        
428
                        if (distance_AC + distance_H2) < nearest_distance:
429
                            nearest_distance = distance_AC + distance_H2
430
                            nearest_match = {
431
                                'bus_h2': h2_row['id'],
432
                                'bus_AC': row['id'],
433
                                'geom_h2': h2_row['geom'],
434
                                'geom_AC': row['geom'],
435
                                'distance_h2': distance_H2,
436
                                'distance_ac': distance_AC,
437
                                'intersection': intersection,
438
                                'sub_type': sub_type,
439
                            }
440
                
441
                if nearest_match:
442
                    results.append(nearest_match)
443
            
444
            if not results:
445
                return pd.DataFrame(columns=['bus_h2', 'bus_AC', 'geom_h2', 'geom_AC', 'distance_h2', 'distance_ac', 'intersection', 'sub_type'])
446
            else: 
447
                return pd.DataFrame(results)
448
449
450
        def find_h2_connection(df_h2):
451
            ####find H2-HVMV connection:
452
            potential_location_grid = find_h2_grid_connection(dfs[ACSUB_HVMV], df_h2, Buffer['H2_HVMV'], Buffer['HVMV'],'HVMV')
453
            potential_location_grid = potential_location_grid.loc[potential_location_grid.groupby(['bus_h2', 'bus_AC'])['distance_h2'].idxmin()]
454
            
455
            filtered_df_hvmv = dfs[ACSUB_HVMV][~dfs[ACSUB_HVMV]['id'].isin(potential_location_grid['bus_AC'])].copy()
456
            potential_location_buses = find_h2_bus_connection(dfs[H2_BUSES_CH4], filtered_df_hvmv,  Buffer['H2_HVMV'], Buffer['HVMV'], 'HVMV')
457
            potential_location_buses = potential_location_buses.loc[potential_location_buses.groupby(['bus_h2', 'bus_AC'])['distance_h2'].idxmin()]
458
            
459
            potential_location_hvmv = pd.concat([potential_location_grid, potential_location_buses], ignore_index = True)
460
            
461
            ####find H2-EHV connection:
462
            potential_location_grid = find_h2_grid_connection(dfs[ACSUB_EHV], df_h2, Buffer['H2_EHV'], Buffer['EHV'],'EHV')
463
            potential_location_grid = potential_location_grid.loc[potential_location_grid.groupby(['bus_h2', 'bus_AC'])['distance_h2'].idxmin()]
464
            
465
            filtered_df_ehv = dfs[ACSUB_EHV][~dfs[ACSUB_EHV]['id'].isin(potential_location_grid['bus_AC'])].copy()
466
            potential_location_buses = find_h2_bus_connection(dfs[H2_BUSES_CH4], filtered_df_ehv,  Buffer['H2_EHV'], Buffer['EHV'], 'EHV')
467
            potential_location_buses = potential_location_buses.loc[potential_location_buses.groupby(['bus_h2', 'bus_AC'])['distance_h2'].idxmin()]
468
            
469
            potential_location_ehv = pd.concat([potential_location_grid, potential_location_buses], ignore_index = True)
470
471
            ### combined potential ehv- and hvmv-connections:
472
            return pd.concat([potential_location_hvmv, potential_location_ehv], ignore_index = True)
473
474
475
476
        def find_heat_connection(potential_locations):
477
478
            dfs[HEAT_BUS]['buffered_geom'] = dfs[HEAT_BUS]['area_geom'].buffer(dfs[HEAT_BUS]['buffer'])
479
            intersection_index = STRtree(potential_locations['intersection'].tolist())
480
481
            potential_locations['bus_heat'] = None
482
            potential_locations['geom_heat'] = None
483
            potential_locations['distance_heat'] = None
484
            
485
            results= []
486
487
            for _, heat_row in dfs[HEAT_BUS].iterrows():
488
                buffered_geom = heat_row['buffered_geom']
489
490
                potential_matches = intersection_index.query(buffered_geom)
491
                
492
                if len(potential_matches) > 0:
493
                    nearest_distance = float('inf')
494
                    nearest_ac_index = None
495
496
                    for match_idx in potential_matches:
497
                        ac_row = potential_locations.iloc[match_idx]  # Hole die entsprechende Zeile
498
                        
499
                        if buffered_geom.intersects(ac_row['intersection']):
500
                            distance = buffered_geom.centroid.distance(ac_row['intersection'].centroid)
501
                            
502
                            if distance < nearest_distance:
503
                                nearest_distance = distance
504
                                nearest_ac_index = match_idx
505
506
                    if nearest_ac_index is not None:
507
                        results.append({
508
                            'bus_AC': potential_locations.at[nearest_ac_index, 'bus_AC'],
509
                            'bus_heat': heat_row['id'],
510
                            'geom_AC': potential_locations.at[nearest_ac_index, 'geom_AC'],
511
                            'geom_heat': heat_row['geom'],
512
                            'distance_heat': distance
513
                        })
514
                        potential_locations.at[nearest_ac_index, 'bus_heat'] = heat_row['id']
515
                        potential_locations.at[nearest_ac_index, 'geom_heat'] = heat_row['geom']
516
                        potential_locations.at[nearest_ac_index, 'distance_heat'] = nearest_distance
517
518
            return pd.DataFrame(results)
519
520
521
522
523
        def find_o2_connections(df_o2, potential_locations, sub_id):
524
            
525
            df_o2['hvmv_id'] = None
526
            for _, district_row in dfs[ACZONE_HVMV].iterrows():
527
                district_geom = district_row['geom']
528
                district_id = district_row['id']
529
                
530
                mask = df_o2['geom'].apply(lambda x: x.within(district_geom))
531
                df_o2.loc[mask, 'hvmv_id'] = district_id
532
                
533
            df_o2['ehv_id'] = None
534
            for _, district_row in dfs[ACZONE_EHV].iterrows():
535
                district_geom = district_row['geom']
536
                district_id = district_row['id']
537
                
538
                mask = df_o2['geom'].apply(lambda x: x.within(district_geom))
539
                df_o2.loc[mask, 'ehv_id'] = district_id
540
                
541
            intersection_geometries = potential_locations['intersection'].tolist()
542
            intersection_tree = STRtree(intersection_geometries)
543
            
544
            results = []
545
546
            for _, o2_row in df_o2.iterrows():
547
                o2_buffer = o2_row['geom'].buffer(Buffer['O2'])  
548
                o2_id = o2_row['id']  
549
                o2_district_id = o2_row[sub_id] 
550
551
                possible_matches = intersection_tree.query(o2_buffer)
552
553
                for match_idx in possible_matches:
554
                    ac_row = potential_locations.iloc[match_idx]
555
                    intersection_centroid = ac_row['intersection'].centroid
556
557
558
         
559
                    if ac_row['bus_AC'] == o2_district_id and o2_buffer.intersects(ac_row['intersection']):           
560
                        distance = intersection_centroid.distance(o2_buffer.centroid)
561
                        results.append({
562
                            'bus_AC': ac_row['bus_AC'],
563
                            'bus_O2': o2_id,
564
                            'geom_AC':  ac_row['geom_AC'],
565
                            'geom_O2': o2_row['geom'],
566
                            'distance_O2': distance,
567
                            'KA_ID': o2_row['ka_id']
568
                        })
569
570
            return pd.DataFrame(results)
571
572
573
574
        def find_spec_for_ka_id(ka_id):
575
            found_spec = wwtp_spec[wwtp_spec["KA_ID"] == ka_id]
576
            if len(found_spec) > 1:
577
                raise Exception("multiple spec for a ka_id")
578
            found_spec = found_spec.iloc[0]
579
            return {
580
                "pe": found_spec["Nominalbelastung 2020 [EW]"],
581
                "demand_o2": found_spec["Sauerstoff 2035 gesamt [t/a]"],
582
            }
583
584
585
        def calculate_wwtp_capacity(pe):  # [MWh/year]
586
            c = "c2"
587
            if pe > 100_000:
588
                c = "c5"
589
            elif pe > 10_000 and pe <= 100_000:
590
                c = "c4"
591
            elif pe > 2000 and pe <= 10_000:
592
                c = "c3"
593
            return pe * WWTP_SEC[c] / 1000
594
595
        def gas_pipeline_size(gas_volume_y, distance, input_pressure, molar_mass, min_pressure):
596
            """
597
                Parameters
598
                ----------
599
                gas_valume : kg/year
600
                distance : km
601
                input pressure : bar
602
                min pressure : bar
603
                molar mas : kg/mol
604
                Returns
605
            -------
606
            Final pressure drop [bar] & pipeline diameter [m]
607
            """
608
609
            def _calculate_final_pressure(pipeline_diameter):
610
                flow_rate = (
611
                    (gas_volume_y / (8760 * molar_mass))
612
                    * UNIVERSAL_GAS_CONSTANT
613
                    * TEMPERATURE
614
                    / (input_pressure * 100_000)
615
                )  # m3/hour
616
                flow_rate_s = flow_rate / 3600  # m3/second
617
                pipeline_area = math.pi * (pipeline_diameter / 2) ** 2  # m2
618
                gas_velocity = flow_rate_s / pipeline_area  # m/s
619
                gas_density = (input_pressure * 1e5 * molar_mass) / (
620
                    UNIVERSAL_GAS_CONSTANT * TEMPERATURE
621
                )  # kg/m3
622
                reynolds_number = (
623
                    gas_density * gas_velocity * pipeline_diameter
624
                ) / UNIVERSAL_GAS_CONSTANT
625
                # Estimate Darcy friction factor using Moody's approximation
626
                darcy_friction_factor = 0.0055 * (
627
                    1 + (2 * 1e4 * (2.51 / reynolds_number)) ** (1 / 3)
628
                )
629
                # Darcy-Weisbach equation
630
                pressure_drop = (
631
                    (4 * darcy_friction_factor * distance * 1000 * gas_velocity**2)
632
                    / (2 * pipeline_diameter)
633
                ) / 1e5  # bar
634
                return input_pressure - pressure_drop  # bar
635
636
            for diameter in PIPELINE_DIAMETER_RANGE:
637
                final_pressure = _calculate_final_pressure(diameter)
638
                if final_pressure > min_pressure:
639
                    return (round(final_pressure, 4), round(diameter, 4))
640
            raise Exception("couldn't find a final pressure < min_pressure")
641
            
642
            
643
        # O2 pipeline diameter cost range
644
        def get_o2_pipeline_cost(o2_pipeline_diameter):
645
           for diameter in sorted(O2_PIPELINE_COSTS.keys(), reverse=True):
646
               if o2_pipeline_diameter >= float(diameter):
647
                   return O2_PIPELINE_COSTS[diameter]
648
      
649
650
651
        def create_link_dataframes(links_h2, links_heat, links_O2):
652
653
            etrago_columns = [
654
                "scn_name",
655
                "link_id",
656
                "bus0",
657
                "bus1",
658
                "carrier",
659
                "efficiency",
660
                "lifetime",
661
                "p_nom",
662
                "p_nom_max",
663
                "p_nom_extendable",
664
                "capital_cost",
665
                "length",
666
                "geom",
667
                "topo",
668
            ]
669
670
            power_to_H2 = pd.DataFrame(columns=etrago_columns)
671
            H2_to_power = pd.DataFrame(columns=etrago_columns)
672
            power_to_Heat = pd.DataFrame(columns=etrago_columns)
673
            power_to_O2 = pd.DataFrame(columns=etrago_columns)
674
            
675
            max_link_id =  db.next_etrago_id("link")
676
            next_max_link_id = count(start=max_link_id, step=1)
677
            
678
679
            ####poower_to_H2
680
            for idx, row in links_h2.iterrows():
681
                capital_cost_H2 = H2_COST_PIPELINE * row['distance_h2']/1000 + ELZ_CAPEX_STACK + ELZ_CAPEX_SYSTEM + ELZ_OPEX  # [EUR/MW/YEAR]
682
                capital_cost_AC = AC_COST_CABLE * row['distance_ac']/1000 + AC_TRANS # [EUR/MW/YEAR]
683
                capital_cost_PtH2 = capital_cost_AC + capital_cost_H2
684
                
685
                power_to_H2_entry = {
686
                    "scn_name": SCENARIO_NAME,
687
                    "link_id": next(next_max_link_id),
688
                    "bus0": row["bus_AC"],
689
                    "bus1": row["bus_h2"],
690
                    "carrier": "power_to_H2",
691
                    "efficiency": ELZ_EFF,    
692
                    "lifetime": ELZ_LIFETIME_Y,  
693
                    "p_nom": 0,  
694
                    "p_nom_max": 120 if row['sub_type'] == 'HVMV' else 5000, 
695
                    "p_nom_extendable": True,
696
                    "capital_cost": capital_cost_PtH2,   
697
                    "geom": MultiLineString(
698
                        [LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_h2'].x, row['geom_h2'].y)])]
699
                    ),  
700
                    "topo": LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_h2'].x, row['geom_h2'].y)]
701
                    ),  
702
                }
703
                power_to_H2 = pd.concat([power_to_H2, pd.DataFrame([power_to_H2_entry])], ignore_index=True)
704
                
705
                ####H2_to_power
706
                capital_cost_H2 = H2_COST_PIPELINE * row['distance_h2']/1000 + FUEL_CELL_COST # [EUR/MW/YEAR]
707
                capital_cost_AC = AC_COST_CABLE * row['distance_ac']/1000 + AC_TRANS # [EUR/MW/YEAR]
708
                capital_cost_H2tP = capital_cost_AC + capital_cost_H2
709
                H2_to_power_entry = {
710
                    "scn_name": SCENARIO_NAME,
711
                    "link_id": next(next_max_link_id),
712
                    "bus0": row["bus_h2"],
713
                    "bus1": row["bus_AC"],
714
                    "carrier": "H2_to_power",
715
                    "efficiency": FUEL_CELL_EFF,    
716
                    "lifetime": FUEL_CELL_LIFETIME,  
717
                    "p_nom": 0,  
718
                    "p_nom_max": 120 if row['sub_type'] == 'HVMV' else 5000, 
719
                    "p_nom_extendable": True,
720
                    "capital_cost": capital_cost_H2tP,   
721
                    "geom": MultiLineString(
722
                        [LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_h2'].x, row['geom_h2'].y)])]
723
                    ),  
724
                    "topo": LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_h2'].x, row['geom_h2'].y)]
725
                    ),  
726
                }
727
                H2_to_power = pd.concat([H2_to_power, pd.DataFrame([H2_to_power_entry])], ignore_index=True)
728
729
            ###power_to_Heat
730
            for idx, row in links_heat.iterrows():
731
                capital_cost = HEAT_COST_EXCHANGER + HEAT_COST_PIPELINE*row['distance_heat']/1000 #EUR/MW/YEAR
732
                
733
                power_to_heat_entry = {
734
                    "scn_name": SCENARIO_NAME,
735
                    "link_id": next(next_max_link_id),
736
                    "bus0": row["bus_AC"],
737
                    "bus1": row["bus_heat"],
738
                    "carrier": "PtH2_waste_heat",
739
                    "efficiency": 1,  
740
                    "lifetime": 25,  
741
                    "p_nom": 0,  
742
                    "p_nom_max": float('inf'),  
743
                    "p_nom_extendable": True,
744
                    "capital_cost": capital_cost,  
745
                    "geom": MultiLineString(
746
                        [LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_heat'].x, row['geom_heat'].y)])]
747
                    ),  
748
                    "topo": LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_heat'].x, row['geom_heat'].y)]
749
                    ), 
750
                }
751
                power_to_Heat = pd.concat([power_to_Heat, pd.DataFrame([power_to_heat_entry])], ignore_index=True)
752
                
753
              
754
            ####power_to_O2   
755
            for idx, row in links_O2.iterrows():
756
                distance = row['distance_O2']/1000 #km
757
                ka_id = row["KA_ID"]
758
                spec = find_spec_for_ka_id(ka_id)
759
                wwtp_ec = calculate_wwtp_capacity(spec["pe"])  # [MWh/year]
760
                aeration_ec = wwtp_ec * FACTOR_AERATION_EC  # [MWh/year]
761
                o2_ec = aeration_ec * FACTOR_O2_EC  # [MWh/year]
762
                o2_ec_h = o2_ec / 8760  # [MWh/hour]
763
                total_o2_demand =  spec["demand_o2"] * 1000  # kgO2/year pure O2 tonne* 1000
764
                _, o2_pipeline_diameter = gas_pipeline_size(
765
                    total_o2_demand,
766
                    distance,
767
                    O2_PRESSURE_ELZ,
768
                    MOLAR_MASS_O2,
769
                    O2_PRESSURE_MIN,
770
                )
771
                annualized_cost_o2_pipeline = get_o2_pipeline_cost(o2_pipeline_diameter) # [EUR/KM/YEAR]
772
                annualized_cost_o2_component = O2_COST_EQUIPMENT #EUR/MW/YEAR
773
                capital_costs=annualized_cost_o2_pipeline * distance/o2_ec_h + annualized_cost_o2_component
774
                
775
                power_to_o2_entry = {
776
                    "scn_name": SCENARIO_NAME,
777
                    "link_id": next(next_max_link_id),
778
                    "bus0": row["bus_AC"],
779
                    "bus1": row["bus_O2"],
780
                    "carrier": "PtH2_O2",
781
                    "efficiency": 1,  
782
                    "lifetime": 25,  
783
                    "p_nom": o2_ec_h,  
784
                    "p_nom_max": float('inf'),  
785
                    "p_nom_extendable": True,
786
                    "capital_cost": capital_costs,  
787
                    "geom": MultiLineString(
788
                        [LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_O2'].x, row['geom_O2'].y)])]
789
                    ),  
790
                    "topo": LineString([(row['geom_AC'].x, row['geom_AC'].y), (row['geom_O2'].x, row['geom_O2'].y)]),  
791
                }
792
                power_to_O2 = pd.concat([power_to_O2, pd.DataFrame([power_to_o2_entry])], ignore_index=True)
793
794
            return power_to_H2, H2_to_power, power_to_Heat, power_to_O2
795
796
797
798
        def export_links_to_db(df, carrier):
799
            schema=targets["links"]["schema"]
800
            table_name=targets["links"]["table"]
801
802
            gdf = gpd.GeoDataFrame(df, geometry="geom").set_crs(METRIC_CRS)
803
            gdf = gdf.to_crs(epsg=DATA_CRS)
804
            gdf.p_nom = 0
805
            
806
            try:
807
                gdf.to_postgis(
808
                    name=table_name,  
809
                    con=engine,  
810
                    schema=schema,  
811
                    if_exists="append",  
812
                    index=False,  
813
                )
814
                print(f"Links have been exported to {schema}.{table_name}")
815
            except Exception as e:
816
                print(f"Error while exporting link data: {e}")
817
                
818
819
820
        def insert_o2_load_points(df):
821
            new_id = db.next_etrago_id('load')
822
            next_load_id = count(start=new_id, step=1)
823
            schema =  targets["loads"]["schema"]
824
            table_name = targets["loads"]["table"]
825
            with engine.connect() as conn:
826
                conn.execute(
827
                    f"DELETE FROM {schema}.{table_name} WHERE carrier = 'O2' AND scn_name = '{SCENARIO_NAME}'"
828
                )
829
            df = df.copy(deep=True)
830
            df = df.drop_duplicates(subset='bus1', keep='first')
831
            result = []
832
            for _, row in df.iterrows():
833
                load_id = next(next_load_id)
834
                result.append(
835
                    {
836
                        "scn_name": SCENARIO_NAME,
837
                        "load_id": load_id,
838
                        "bus": row["bus1"],
839
                        "carrier": "O2",
840
                        "o2_load_el": row["p_nom"],
841
                    }
842
                )
843
            df = pd.DataFrame(result)
844
            df[['scn_name', 'load_id', 'bus', 'carrier']].to_sql(table_name, engine, schema=schema, if_exists="append", index=False)
845
            print(f"O2 load data exported to: {table_name}")
846
            return df
847
            
848
        def insert_o2_load_timeseries(df):
849
            query_o2_timeseries = f"""
850
                        SELECT load_curve
851
            			FROM {sources["o2_load_profile"]["schema"]}.{sources["o2_load_profile"]["table"]}
852
            			WHERE slp = 'G3' AND wz = 3
853
                        """
854
                        
855
            base_load_profile = pd.read_sql(query_o2_timeseries, engine)['load_curve'].values
856
            base_load_profile = np.array(base_load_profile[0])
857
            
858
            with engine.connect() as conn:
859
                conn.execute(f"""
860
                    DELETE FROM {targets["load_timeseries"]["schema"]}.{targets["load_timeseries"]["table"]} 
861
                    WHERE load_id IN {tuple(df.load_id.values)} 
862
                    AND scn_name = '{SCENARIO_NAME}'
863
                    """
864
                )
865
866
            timeseries_list = []
867
868
            for index, row in df.iterrows():
869
                load_id = row['load_id']  # ID aus der aktuellen Zeile
870
                o2_load_el = row['o2_load_el']  # Nennleistung aus der aktuellen Zeile
871
                
872
                modified_profile = base_load_profile * o2_load_el
873
                
874
                timeseries_list.append({
875
                    'scn_name': SCENARIO_NAME,
876
                    'load_id': load_id,
877
                    'temp_id': 1,
878
                    'p_set': modified_profile,
879
                    'bus': row['bus']
880
                })
881
882
            timeseries_df = pd.DataFrame(timeseries_list)
883
            timeseries_df['p_set'] = timeseries_df['p_set'].apply(lambda x: x.tolist() if isinstance(x, np.ndarray) else x)
884
            timeseries_df[['scn_name', 'load_id', 'temp_id', 'p_set']].to_sql(
885
                targets["load_timeseries"]["table"], 
886
                engine,
887
                schema=targets["load_timeseries"]["schema"], 
888
                if_exists="append", 
889
                index=False)
890
891
            return timeseries_df
892
893
894
        def insert_o2_generators(df):
895
            new_id = db.next_etrago_id("generator")
896
            next_generator_id = count(start=new_id, step=1)
897
            
898
            grid = targets["generators"]["schema"]
899
            table_name = targets["generators"]["table"]
900
            with engine.connect() as conn:
901
                conn.execute(
902
                    f"DELETE FROM {grid}.{table_name} WHERE carrier = 'O2' AND scn_name = '{SCENARIO_NAME}'"
903
                )
904
            df = df.copy(deep=True)
905
            df = df.drop_duplicates(subset='bus1', keep='first')
906
            result = []
907
            for _, row in df.iterrows():
908
                generator_id = next(next_generator_id)
909
                result.append(
910
                    {
911
                        "scn_name": SCENARIO_NAME,
912
                        "generator_id": generator_id,
913
                        "bus": row["bus1"],
914
                        "carrier": "O2",
915
                        "p_nom_extendable": "true",
916
                        "marginal_cost": ELEC_COST, 
917
                    }
918
                )
919
            df = pd.DataFrame(result)
920
            df.to_sql(table_name, engine, schema=grid, if_exists="append", index=False)
921
922
            print(f"generator data exported to: {table_name}")
923
924
925
        
926
        def adjust_ac_load_timeseries(df, o2_timeseries):
927
            #filter out affected ac_loads
928
            queries[AC_LOAD] = f"""
929
                                SELECT bus, load_id 
930
                    			FROM {sources["loads"]["schema"]}.{sources["loads"]["table"]}
931
                                WHERE scn_name = '{SCENARIO_NAME}'
932
                                """
933
            dfs[AC_LOAD] = pd.read_sql(queries[AC_LOAD], engine)
934
            df = df.drop_duplicates(subset='bus1', keep='first')
935
            ac_loads = pd.merge(df, dfs[AC_LOAD], left_on='bus0', right_on='bus')
936
            
937
            #reduce each affected ac_load with o2_timeseries
938
            for _, row in ac_loads.iterrows():
939
                with engine.connect() as conn:
940
941
                    select_query = text(f"""
942
                        SELECT p_set 
943
                        FROM {sources["load_timeseries"]["schema"]}.{sources["load_timeseries"]["table"]}
944
                        WHERE load_id = :load_id and scn_name= :SCENARIO_NAME
945
                        """)
946
                    result = conn.execute(select_query, {"load_id": row["load_id"], "SCENARIO_NAME": SCENARIO_NAME}).fetchone()
947
                    
948
                    if result:
949
                         original_p_set = result["p_set"]                         
950
                         o2_timeseries_row = o2_timeseries.loc[o2_timeseries['bus'] == row['bus1']]
951
                         
952
                         if not o2_timeseries_row.empty:
953
                             o2_p_set = o2_timeseries_row.iloc[0]['p_set']
954
                             
955
                             if len(original_p_set) == len(o2_p_set):
956
                                 # reduce ac_load with o2_load_timeseries
957
                                 adjusted_p_set = (np.array(original_p_set) - np.array(o2_p_set)).tolist()
958
                                 update_query = text(f"""
959
                                     UPDATE {targets["load_timeseries"]["schema"]}.{targets["load_timeseries"]["table"]}
960
                                     SET p_set = :adjusted_p_set
961
                                     WHERE load_id = :load_id AND scn_name = :SCENARIO_NAME
962
                                 """)
963
                                 conn.execute(update_query, {"adjusted_p_set": adjusted_p_set, "load_id": row["load_id"], "SCENARIO_NAME": SCENARIO_NAME})
964
                             else:
965
                                 print(f"Length mismatch for load_id {row['load_id']}: original={len(original_p_set)}, o2={len(o2_p_set)}")
966
                         else:
967
                             print(f"No matching o2_timeseries entry for load_id {row['load_id']}")
968
                             
969
        def delete_unconnected_o2_buses():
970
            with engine.connect() as conn:
971
                conn.execute(f"""
972
                    DELETE FROM {targets['buses']['schema']}.{targets['buses']['table']} 
973
                    WHERE carrier = 'O2' AND scn_name = '{SCENARIO_NAME}'
974
                    AND bus_id NOT IN (SELECT bus1 FROM {targets['links']['schema']}.{targets['links']['table']} 
975
                                       WHERE carrier = 'PtH2_O2')
976
                    """
977
                )
978
                             
979
980
        def execute_PtH2_method():
981
            
982
            h2_grid_geom_df, dfs[HEAT_BUS], dfs[H2_BUSES_CH4] = prepare_dataframes_for_spartial_queries()
983
            potential_locations=find_h2_connection(h2_grid_geom_df)
984
            heat_links = find_heat_connection(potential_locations)
985
            o2_links_hvmv = find_o2_connections(dfs[WWTP], potential_locations[potential_locations.sub_type=='HVMV'], 'hvmv_id')
986
            o2_links_ehv = find_o2_connections(dfs[WWTP], potential_locations[potential_locations.sub_type=='EHV'], 'ehv_id')
987
            o2_links=pd.concat([o2_links_hvmv, o2_links_ehv], ignore_index=True)
988
            power_to_H2, H2_to_power, power_to_Heat, power_to_O2 = create_link_dataframes(potential_locations, heat_links, o2_links)
989
            export_links_to_db(power_to_H2,'power_to_H2')
990
            export_links_to_db(power_to_Heat, 'PtH2_waste_heat')
991
            export_links_to_db(power_to_O2, 'PtH2_O2')
992
            export_links_to_db(H2_to_power, 'H2_to_power')
993
            o2_loads_df = insert_o2_load_points(power_to_O2)
994
            o2_timeseries = insert_o2_load_timeseries(o2_loads_df)
995
            insert_o2_generators(power_to_O2)
996
            adjust_ac_load_timeseries(power_to_O2, o2_timeseries) 
997
            delete_unconnected_o2_buses()
998
          
999
        execute_PtH2_method()
1000
                         
1001
1002