buckpy-dev 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,900 @@
1
+ """
2
+ This module contains the pre-processing functions of BuckPy.
3
+ """
4
+
5
+ import time
6
+ import numpy as np
7
+ import pandas as pd
8
+ from scipy.stats import lognorm
9
+ import pysubsea as ss
10
+ from .buckpy_variables import KP_TO
11
+
12
+ def calc_expand_kp(df):
13
+
14
+ '''
15
+ Function to expand the KP array with 1000 intervals from 1000 to nearest maximum KP.
16
+
17
+ Parameters
18
+ ----------
19
+ df : pandas Dataframe
20
+ Dataframe containing the original KP values.
21
+
22
+ Returns
23
+ -------
24
+ df : pandas Dataframe
25
+ Dataframe containing the expanded KP values.
26
+ '''
27
+
28
+ # Rename 'KP To' to 'KP From'
29
+ df = df.rename(columns = {'KP To': 'KP From'})
30
+
31
+ # Expand the KP array with 1000 intervals from 1000 to nearest maximum KP
32
+ max_kp = np.floor(df['KP From'].max() / 1000.0) * 1000.0
33
+ kp_array = np.arange(1000, max_kp + 1.0, 1000)
34
+
35
+ # Create a dataframe for the expanded kp
36
+ df_expand = pd.DataFrame({'Point ID From': [np.nan] * len(kp_array), 'KP From': kp_array})
37
+ df = pd.concat([df, df_expand], ignore_index = True).sort_values(
38
+ by = 'KP From').drop_duplicates('KP From').reset_index(drop = True).ffill()
39
+
40
+ # Calculate relative length between KP and KP To
41
+ df['KP To'] = df['KP From'].shift(-1)
42
+ df = df.dropna()
43
+ df['Length'] = df['KP To'] - df['KP From']
44
+
45
+ # Calculate element number and element size
46
+ df['Elem No.'] = np.ceil(df['Length'] / 100.0)
47
+ df['Elem Size'] = df['Length'] / df['Elem No.']
48
+
49
+ return df
50
+
51
+ def calc_element_array(df):
52
+
53
+ '''
54
+ Function to create element array based on KP, KP TO and element number.
55
+
56
+ Parameters
57
+ ----------
58
+ df : pandas Dataframe
59
+ Dataframe containing the expanded KP values.
60
+
61
+ Returns
62
+ -------
63
+ df : pandas Dataframe
64
+ Dataframe containing the elements between each KP value.
65
+ '''
66
+
67
+ # Create the elements between each KP points
68
+ elem_array = np.empty(0)
69
+ elem_array = df.apply(lambda x: pd.Series(np.append(elem_array, np.linspace(
70
+ x['KP From'], x['KP To'], int(x['Elem No.'] + 1.0)))), axis = 1)
71
+
72
+ # Convert the element dataframe to np array and flatten
73
+ elem_array = elem_array.to_numpy().flatten()
74
+
75
+ # Remove duplicated values at 1000*n and np.nan
76
+ elem_array = np.unique(elem_array)
77
+ elem_array = elem_array[~np.isnan(elem_array)]
78
+
79
+ return elem_array
80
+
81
+ def calc_kp_interpolation(elem_array, df_oper):
82
+
83
+ '''
84
+ Function to interpolate the RLT, pressure and temperature using KP and operating profile.
85
+
86
+ Parameters
87
+ ----------
88
+ elem_array : np Array
89
+ Array containing the kp value of the elements.
90
+ df_oper : pandas Dataframe
91
+ Dataframe containing the original operating profiles data.
92
+
93
+ Returns
94
+ -------
95
+ df : pandas Dataframe
96
+ Dataframe containing the interpolated operating profiles data.
97
+ '''
98
+
99
+ # Interpolate operating profile based on KP
100
+ df = pd.DataFrame({'KP': elem_array})
101
+ df['Pressure Installation'] = np.interp(
102
+ df['KP'], df_oper['KP'], df_oper['Pressure Installation'])
103
+ df['Pressure Hydrotest'] = np.interp(
104
+ df['KP'], df_oper['KP'], df_oper['Pressure Hydrotest'])
105
+ df['Pressure Operation'] = np.interp(
106
+ df['KP'], df_oper['KP'], df_oper['Pressure Operation'])
107
+ df['Temperature Installation'] = np.interp(
108
+ df['KP'], df_oper['KP'], df_oper['Temperature Installation'])
109
+ df['Temperature Hydrotest'] = np.interp(
110
+ df['KP'], df_oper['KP'], df_oper['Temperature Hydrotest'])
111
+ df['Temperature Operation'] = np.interp(
112
+ df['KP'], df_oper['KP'], df_oper['Temperature Operation'])
113
+ df['RLT'] = np.interp(df['KP'], df_oper['KP'], df_oper['RLT'])
114
+
115
+ return df
116
+
117
+ def calc_operating_profiles(df, df_route, pipeline_set, loadcase_set):
118
+
119
+ """
120
+ Calculate operating profiles data and process it.
121
+
122
+ Parameters
123
+ ----------
124
+ df : pandas.DataFrame
125
+ DataFrame containing the operating profiles data.
126
+ df_route : pandas.DataFrame
127
+ DataFrame containing route data and calculated route data.
128
+ pipeline_set : str
129
+ Identifier of the pipeline set.
130
+ loadcase_set : str
131
+ Identifier of the loadcase set.
132
+
133
+ Returns
134
+ -------
135
+ df : pandas.DataFrame
136
+ DataFrame containing the operating profiles data and calculated operating data.
137
+ """
138
+
139
+ # Filter df DataFrame based on pipeline_set and loadcase_set
140
+ df_profile = df.loc[(df['Pipeline'] == pipeline_set) & (df['Loadcase Set'] == loadcase_set)]
141
+
142
+ # Select the 'Point ID From' and 'KP To' columns
143
+ df_route = df_route[['Point ID From', 'KP To']].reset_index(drop = True)
144
+
145
+ # Add the end row of route and the start KP
146
+ end_row = pd.DataFrame({'Point ID From': 'End', 'KP To': np.nan}, index = [99999])
147
+ df_route = pd.concat([df_route, end_row], ignore_index = True)
148
+
149
+ # Shift KP column 1 downwards and assign 0.0 to the first KP
150
+ df_route['KP To'] = df_route['KP To'].shift().fillna(0.0)
151
+
152
+ # Expand the KP array with 1000 intervals from 1000 to nearest maximum KP
153
+ df_route = calc_expand_kp(df_route)
154
+
155
+ # Create the elements between each KP points
156
+ elem_array = calc_element_array(df_route)
157
+
158
+ # Interpolate the RLT, pressure and temperature using KP and operating profile
159
+ df = calc_kp_interpolation(elem_array, df_profile)
160
+
161
+ # Insert pipeline_set and loadcase_set columns as the first and second columns
162
+ df.insert(0, 'Pipeline', [pipeline_set] * df.shape[0])
163
+ df.insert(1, 'Loadcase Set', [loadcase_set] * df.shape[0])
164
+
165
+ return df
166
+
167
+ def calc_route_data(df, layout_set, pipeline_set):
168
+
169
+ """
170
+ Extract and process route data for calculations.
171
+
172
+ Parameters
173
+ ----------
174
+ df : pandas.DataFrame
175
+ DataFrame containing route data.
176
+ layout_set : str
177
+ Identifier of the layout set.
178
+ pipeline_set : str
179
+ Identifier of the pipeline set.
180
+
181
+ Returns
182
+ -------
183
+ df : pandas.DataFrame
184
+ DataFrame containing route data and calculated route data.
185
+ df_ends : pandas.DataFrame
186
+ DataFrame containing end boundary conditions.
187
+
188
+ Notes
189
+ -----
190
+ This function extracts route ends and route data based on pipeline_set and layout_set. It
191
+ selects specific columns for route ends data. Route Type is converted from string to
192
+ float for numerical representation. Route ends data is converted to a NumPy array for
193
+ efficient processing.
194
+ """
195
+
196
+ # Extract route ends and route data based on pipeline_set and layout_set
197
+ df_ends = df.loc[(df['Pipeline'] == pipeline_set) &
198
+ (df['Layout Set'] == layout_set)].iloc[[0, -1]]
199
+ df = df.loc[(df['Pipeline'] == pipeline_set) &
200
+ (df['Layout Set'] == layout_set)].iloc[1:-1]
201
+
202
+ # Select specific columns for route ends data
203
+ df_ends = df_ends[['Route Type', 'KP From', 'KP To', 'Reaction Installation',
204
+ 'Reaction Hydrotest', 'Reaction Operation']]
205
+
206
+ # Convert 'Route Type' from string to float for numerical representation
207
+ df_ends.loc[df_ends['Route Type'] == 'Spool', 'Route Type'] = 1
208
+ df_ends.loc[df_ends['Route Type'] == 'Fixed', 'Route Type'] = 2
209
+ df_ends['Route Type'] = df_ends['Route Type'].astype(float)
210
+
211
+ # Convert KP From and KP to to float
212
+ df[['KP From', 'KP To']] = df[['KP From', 'KP To']].astype(float)
213
+
214
+ return df, df_ends
215
+
216
+ def calc_pipe_data(df, pipeline_set):
217
+
218
+ """
219
+ Calculate properties of pipes for a specific pipeline set.
220
+
221
+ Parameters
222
+ ----------
223
+ df : pandas.DataFrame
224
+ DataFrame containing the pipe data.
225
+ pipeline_set : str
226
+ Identifier of the pipeline set.
227
+
228
+ Returns
229
+ -------
230
+ df : pandas.DataFrame
231
+ DataFrame containing the pipe data and calculated pipe properties.
232
+
233
+ Notes
234
+ -----
235
+ This function filters the df DataFrame based on the pipeline_set. It computes the
236
+ inner diameter (ID), cross-sectional area (As), inner area (Ai), moment of inertia (I),
237
+ hydrotest characteristic buckling force (SChar HT), and operation characteristic buckling
238
+ force (SChar OP) of the pipe.
239
+ """
240
+
241
+ # Compute the inner diameter (ID) of the pipe
242
+ df['ID'] = df['OD'] - 2.0 * df['WT']
243
+
244
+ # Compute the cross-sectional area (As) of the pipe
245
+ df['As'] = np.pi / 4.0 * (df['OD'] ** 2 - df['ID'] ** 2)
246
+
247
+ # Compute the inner area (Ai) of the pipe
248
+ df['Ai'] = np.pi / 4.0 * df['ID'] ** 2
249
+
250
+ # Compute the moment of inertia (I) of the pipe
251
+ df['I'] = np.pi / 64.0 * (df['OD'] ** 4 - df['ID'] ** 4)
252
+
253
+ # Compute the hydrotest characteristic buckling force (SChar HT) of the pipe
254
+ df['SChar HT'] = 2.26 * (df['E'] * df['As']) ** 0.25 * (
255
+ df['E'] * df['I']) ** 0.25 * df['sw Hydrotest'] ** 0.5
256
+
257
+ # Compute the operation characteristic buckling force (SChar OP) of the pipe
258
+ df['SChar OP'] = 2.26 * (df['E'] * df['As']) ** 0.25 * (
259
+ df['E'] * df['I']) ** 0.25 * df['sw Operation'] ** 0.5
260
+
261
+ # Filter df DataFrame based on pipeline_set
262
+ df = df.loc[(df['Pipeline'] == pipeline_set)]
263
+
264
+ return df
265
+
266
+ def calc_oper_data(df, df_route_ends, pipeline_set, loadcase_set):
267
+
268
+ """
269
+ Calculate operating data and process it.
270
+
271
+ Parameters
272
+ ----------
273
+ df : pandas.DataFrame
274
+ DataFrame containing the operating data.
275
+ df_route_ends : pandas.DataFrame
276
+ DataFrame containing the end boundary conditions.
277
+ pipeline_set : str
278
+ Identifier of the pipeline set.
279
+ loadcase_set : str
280
+ Identifier of the loadcase set.
281
+
282
+ Returns
283
+ -------
284
+ df : pandas.DataFrame
285
+ DataFrame containing the operating data and calculated operating data.
286
+
287
+ Notes
288
+ -----
289
+ This function filters df DataFrame based on pipeline_set, loadcase_set, and 'KP To'.
290
+ It calculates rolling mean and difference, assigns the 'Length' column, resets the index, and
291
+ drops rows with NaN values before returning the preprocessed DataFrame.
292
+ """
293
+
294
+ # Filter df DataFrame based on pipeline_set, loadcase_set and 'KP To'
295
+ df = df.loc[(df['Pipeline'] == pipeline_set) &
296
+ (df['Loadcase Set'] == loadcase_set) &
297
+ (df['KP'] <= df_route_ends['KP To'].iloc[-1])]
298
+
299
+ # Calculate the rolling mean of df grouped by Pipeline and Loadcase Set
300
+ df_rolling_mean = df.groupby(['Pipeline', 'Loadcase Set']).rolling(2).mean()
301
+
302
+ # Calculate the rolling difference of df grouped by Pipeline and Loadcase Set
303
+ df_rolling_difference = df.groupby(
304
+ ['Pipeline', 'Loadcase Set']).rolling(2).max() - df.groupby(
305
+ ['Pipeline', 'Loadcase Set']).rolling(2).min()
306
+
307
+ # Assign the 'Length' column in df_rolling_mean
308
+ df_rolling_mean['Length'] = df_rolling_difference['KP']
309
+
310
+ # Reset the index of df_rolling_mean and drop the 'level_2' index level
311
+ df_rolling_mean = df_rolling_mean.reset_index().drop('level_2', axis=1)
312
+
313
+ # Drop rows with NaN values
314
+ df_rolling_mean = df_rolling_mean.dropna()
315
+
316
+ return df_rolling_mean
317
+
318
+ def calc_soil_data(df, pipeline_set):
319
+
320
+ """
321
+ Calculate soil data and axial and lateral friction factor distributions and assign them to
322
+ DataFrame columns.
323
+
324
+ Parameters
325
+ ----------
326
+ df : pandas.DataFrame
327
+ DataFrame containing soil data.
328
+ pipeline_set : str
329
+ Identifier of the pipeline set.
330
+
331
+ Returns
332
+ -------
333
+ df : pandas.DataFrame
334
+ DataFrame containing soil data and calculated friction factor distributions.
335
+
336
+ Notes
337
+ -----
338
+ This function filters df DataFrame based on pipeline_set value. It computes lognormal
339
+ distributions for axial and lateral friction factors and assigns them to DataFrame columns.
340
+ """
341
+
342
+ # Compute lognormal or normal distributions for axial friction and assign arrays to DataFrame columns
343
+ df['muax Array'], df['muax CDF Array'] = zip(
344
+ *df.apply(
345
+ lambda x: calc_lognorm_soil(x['Axial Mean'], x['Axial STD']),
346
+ axis=1
347
+ ).apply(np.array)
348
+ )
349
+
350
+ # Compute lognormal distributions for lateral hydrotest friction and assign arrays to DataFrame columns
351
+ df['mul HT Array'], df['mul HT CDF Array'] = zip(
352
+ *df.apply(
353
+ lambda x: calc_lognorm_soil(x['Lateral Hydrotest Mean'], x['Lateral Hydrotest STD']),
354
+ axis=1
355
+ ).apply(np.array)
356
+ )
357
+
358
+ # Compute lognormal distributions for lateral operation friction and assign arrays to DataFrame columns
359
+ df['mul OP Array'], df['mul OP CDF Array'] = zip(
360
+ *df.apply(
361
+ lambda x: calc_lognorm_soil(x['Lateral Operation Mean'], x['Lateral Operation STD']),
362
+ axis=1
363
+ ).apply(np.array)
364
+ )
365
+
366
+ # Filter soil data based on pipeline set
367
+ df = df[df['Pipeline'] == pipeline_set]
368
+
369
+ return df
370
+
371
+ def calc_scenario_data(df_scen, df_route, df_pipe, df_oper, df_soil):
372
+
373
+ """
374
+ Calculate scenario data based on route, pipe, operating, and soil data.
375
+
376
+ Parameters
377
+ ----------
378
+ df_scen : pandas.DataFrame
379
+ DataFrame containing scenario data.
380
+ df_route : pandas.DataFrame
381
+ DataFrame containing route data.
382
+ df_pipe : pandas.DataFrame
383
+ DataFrame containing pipe data.
384
+ df_oper : pandas.DataFrame
385
+ DataFrame containing operating data.
386
+ df_soil : pandas.DataFrame
387
+ DataFrame containing soil data.
388
+
389
+ Returns
390
+ -------
391
+ df: pandas.DataFrame
392
+ DataFrame containing the calculated scenario data.
393
+
394
+ Notes
395
+ -----
396
+ This function merges route, pipe, operating, and soil data to compute various scenario
397
+ parameters. It calculates various attributes such as lognormal distributions, buckling forces,
398
+ and section counts. The resulting DataFrame includes a subset of calculated columns and is
399
+ filled with 0 for missing values.
400
+ """
401
+
402
+ # Merge operating data with route data based on 'KP'
403
+ df = pd.merge_asof(left=df_oper, right=df_route, left_on='KP', right_on='KP From',
404
+ direction='backward', left_by='Pipeline', right_by='Pipeline')
405
+
406
+ # Merge resulting DataFrame with pipe data
407
+ df = pd.merge(left=df, right=df_pipe, left_on=['Pipeline', 'Pipe Set'],
408
+ right_on=['Pipeline', 'Pipe Set'])
409
+
410
+ # Merge resulting DataFrame with soil data
411
+ df = pd.merge(left=df, right=df_soil, left_on=['Pipeline', 'Friction Set'],
412
+ right_on=['Pipeline', 'Friction Set'])
413
+
414
+ # Compute lognormal distributions for soil properties and assign to DataFrame columns
415
+ df['HOOS X Array'], df['HOOS CDF Array'] = zip(*df.apply(
416
+ lambda x: calc_lognorm_hoos(x['Route Type'], x['Length'], x['HOOS Mean'],
417
+ x['HOOS STD'], x['HOOS Reference Length'], x['RCM Buckling Force']), axis=1)
418
+ .apply(np.array))
419
+
420
+ # Compute various buckling forces based on calculated parameters
421
+ df['FRF HT'] = df['RLT'] + df['E'] * df['Alpha'] * df['As'] * (
422
+ df['Temperature Hydrotest'] - df['Temperature Installation']) + (
423
+ 1 - 2 * df['Poisson']) * (
424
+ df['Pressure Hydrotest'] - df['Pressure Installation']) * df['Ai']
425
+ df['FRF OP'] = df['RLT'] + df['E'] * df['Alpha'] * df['As'] * (
426
+ df['Temperature Operation'] - df['Temperature Installation']) + (
427
+ 1 - 2 * df['Poisson']) * (
428
+ df['Pressure Operation'] - df['Pressure Installation']) * df['Ai']
429
+ df['FRF OP Pressure'] = df['RLT'] + (
430
+ 1 - 2 * df['Poisson']) * df['Pressure Operation'] * df['Ai']
431
+ df['FRF OP Temperature'] = df['E'] * df['As'] * df['Alpha'] * (
432
+ df['Temperature Operation'] - df['Temperature Installation'])
433
+ df['Sv HT'] = 4.0 * np.sqrt(df['E'] * df['I'] * df['sw Hydrotest'] / df['Sleeper Height'])
434
+ df['Sv OP'] = 4.0 * np.sqrt(df['E'] * df['I'] * df['sw Operation'] / df['Sleeper Height'])
435
+
436
+ # Calculate section-related parameters
437
+ df['KP Section'] = df['KP'] - df['KP From']
438
+ df['Reference Section'] = (df['KP Section'] / df['HOOS Reference Length']).apply(np.floor)
439
+ df['Section Count'] = 0.0
440
+ df.loc[
441
+ (df['Route Type'] != df['Route Type'].shift()) |
442
+ (df['Reference Section'] != df['Reference Section'].shift()), 'Section Count'] = 1.0
443
+ df['Section Count'] = df['Section Count'].cumsum()
444
+
445
+ # Select relevant columns and rename them for clarity
446
+ df = df[[
447
+ 'KP', 'Length', 'Route Type', 'KP From', 'KP To', 'Point ID From', 'Point ID To',
448
+ 'Bend Radius', 'muax Array', 'muax CDF Array',
449
+ 'mul HT Array', 'mul HT CDF Array', 'mul OP Array', 'mul OP CDF Array',
450
+ 'HOOS X Array', 'HOOS CDF Array', 'sw Installation', 'sw Hydrotest', 'sw Operation',
451
+ 'SChar HT', 'SChar OP', 'Sv HT', 'Sv OP', 'RCM Buckling Force', 'RLT', 'FRF HT',
452
+ 'FRF OP Pressure', 'FRF OP Temperature', 'FRF OP', 'Residual Buckle Length Hydrotest',
453
+ 'Residual Buckle Force Hydrotest', 'Residual Buckle Length Operation',
454
+ 'Residual Buckle Force Operation', 'Section Count', 'KP Section', 'Reference Section',
455
+ 'Axial Mean', 'Lateral Hydrotest Mean', 'Lateral Operation Mean', 'HOOS Mean'
456
+ ]]
457
+
458
+ df = df.rename(columns={'sw Installation': 'sw IN',
459
+ 'sw Hydrotest': 'sw HT',
460
+ 'sw Operation': 'sw OP',
461
+ 'Residual Buckle Length Hydrotest': 'buckleLength HT',
462
+ 'Residual Buckle Force Hydrotest': 'buckleEAF HT',
463
+ 'Residual Buckle Length Operation': 'buckleLength OP',
464
+ 'Residual Buckle Force Operation': 'buckleEAF OP'})
465
+
466
+ # Convert route type strings to numerical representation
467
+ df.loc[df['Route Type'] == 'Straight', 'Route Type'] = 1
468
+ df.loc[df['Route Type'] == 'Bend', 'Route Type'] = 2
469
+ df.loc[df['Route Type'] == 'Sleeper', 'Route Type'] = 3
470
+ df.loc[df['Route Type'] == 'RCM', 'Route Type'] = 4
471
+ df['Route Type'] = df['Route Type'].astype(float)
472
+
473
+ # Fill missing values with 0
474
+ df = df.fillna(0)
475
+
476
+ # Add scenario parameters to the DataFrame
477
+ df["Pipeline"] = df_scen["Pipeline"].values[0]
478
+ df["Scenario"] = df_scen["Scenario"].values[0]
479
+ df["Layout Set"] = df_scen["Layout Set"].values[0]
480
+ df["Simulations"] = df_scen["Simulations"].values[0]
481
+ df["Friction Sampling"] = df_scen["Friction Sampling"].values[0]
482
+ df["Char. Friction Prob."] = df_scen["Char. Friction Prob."].values[0]
483
+
484
+ return df
485
+
486
+ def calc_monte_carlo_data(df, df_ends):
487
+
488
+ """
489
+ Convert the scenario data and pipeline end boundary conditions data to NumPy arrays for
490
+ Monte Carlo simulations.
491
+
492
+ Parameters
493
+ ----------
494
+ df : pandas.DataFrame
495
+ DataFrame containing the scenario data.
496
+ df_ends : pandas.DataFrame
497
+ DataFrame containing the pipeline end boundary conditions data.
498
+
499
+ Returns
500
+ -------
501
+ np_distr : numpy.ndarray
502
+ 2D array with probabilistic distributions (rows) along the route mesh (columns).
503
+ np_scen : numpy.ndarray
504
+ 2D array with scenario properties (rows) along the route mesh (columns).
505
+ np_ends : numpy.ndarray
506
+ 2D array with end properties (rows) for the pipeline ends.
507
+
508
+ Notes
509
+ -----
510
+ The arrays have the following row layout (index : meaning):
511
+
512
+ np_distr:
513
+ - 0 : MUAX_ARRAY
514
+ - 1 : MUAX_CDF_ARRAY
515
+ - 2 : MULAT_ARRAY_HT
516
+ - 3 : MULAT_CDF_ARRAY_HT
517
+ - 4 : MULAT_ARRAY_OP
518
+ - 5 : MULAT_CDF_ARRAY_OP
519
+ - 6 : HOOS_ARRAY
520
+ - 7 : HOOS_CDF_ARRAY
521
+
522
+ np_scen:
523
+ - 0 : KP
524
+ - 1 : LENGTH
525
+ - 2 : ROUTE_TYPE
526
+ - 3 : BEND_RADIUS
527
+ - 4 : SW_INST
528
+ - 5 : SW_HT
529
+ - 6 : SW_OP
530
+ - 7 : SCHAR_HT
531
+ - 8 : SCHAR_OP
532
+ - 9 : SV_HT
533
+ - 10 : SV_OP
534
+ - 11 : CBF_RCM
535
+ - 12 : RLT
536
+ - 13 : FRF_HT
537
+ - 14 : FRF_P_OP
538
+ - 15 : FRF_T_OP
539
+ - 16 : FRF_OP
540
+ - 17 : L_BUCKLE_HT
541
+ - 18 : EAF_BUCKLE_HT
542
+ - 19 : L_BUCKLE_OP
543
+ - 20 : EAF_BUCKLE_OP
544
+ - 21 : SECTION_ID
545
+ - 22 : SECTION_KP
546
+ - 23 : SECTION_REF
547
+ - 24 : MUAX_MEAN
548
+ - 25 : MULAT_HT_MEAN
549
+ - 26 : MULAT_OP_MEAN
550
+ - 27 : HOOS_MEAN
551
+
552
+ np_ends:
553
+ - 0 : ROUTE_TYPE
554
+ - 1 : KP_FROM
555
+ - 2 : KP_TO
556
+ - 3 : REAC_INST
557
+ - 4 : REAC_HT
558
+ - 5 : REAC_OP
559
+ """
560
+
561
+ # Convert probabilistic distributions to numpy array
562
+ list_temp1 = []
563
+ prob_label_list = [
564
+ 'muax Array', 'muax CDF Array', 'mul HT Array', 'mul HT CDF Array',
565
+ 'mul OP Array', 'mul OP CDF Array', 'HOOS X Array', 'HOOS CDF Array'
566
+ ]
567
+ for array_label in prob_label_list:
568
+ list_temp2 = []
569
+ for i in range(df[array_label].size):
570
+ list_temp2.append(df[array_label][i])
571
+ list_temp1.append(list_temp2)
572
+ np_distr = np.array(list_temp1, dtype='float64')
573
+
574
+ # Add extra columns to remove
575
+ columns_drop = [
576
+ "Pipeline", "Scenario", "Simulations", "Friction Sampling", "Char. Friction Prob.",
577
+ 'KP From', 'KP To', 'Point ID From', 'Point ID To'
578
+ ]
579
+ columns_drop = np.append(columns_drop, prob_label_list)
580
+
581
+ # Convert scenario properties to numpy array
582
+ np_scen = df.drop(columns_drop, axis=1).to_numpy().transpose()
583
+
584
+ # Convert end properties to numpy array
585
+ np_ends = df_ends.to_numpy().transpose()
586
+
587
+ return np_distr, np_scen, np_ends
588
+
589
+ def calc_pp_data(df, np_array, pipeline_id, layout_set):
590
+
591
+ """
592
+ Calculate post-processing data set for a given layout set.
593
+
594
+ Parameters
595
+ ----------
596
+ df : pandas.DataFrame
597
+ DataFrame containing post-processing data.
598
+ np_array : numpy.ndarray
599
+ NumPy array containing pipeline end boundary conditions.
600
+ pipeline_id : str
601
+ Identifier of the pipeline.
602
+ layout_set : str
603
+ Identifier of the layout set.
604
+
605
+ Returns
606
+ -------
607
+ df : pandas.DataFrame
608
+ DataFrame containing calculated post-processing data.
609
+
610
+ Notes
611
+ -----
612
+ This function filters the DataFrame based on the layout set. It resets the index, renames
613
+ columns, and selects relevant columns. Adjusts the last 'KP_to' value if it is smaller
614
+ than the maximum value in np_array. Converts data types of columns to appropriate numeric
615
+ types.
616
+ """
617
+
618
+ # Filter DataFrame based on layout_set
619
+ df = df.loc[(df['Pipeline'] == pipeline_id) & (df['Layout Set'] == layout_set)]
620
+
621
+ # Reset index, rename columns, and select relevant columns
622
+ df = df.reset_index(drop=True).rename(columns={'Post-Processing Set': 'pp_set',
623
+ 'KP From': 'KP_from',
624
+ 'KP To': 'KP_to',
625
+ 'Post-Processing Description': 'description'})
626
+ df = df[['pp_set', 'KP_from', 'KP_to', 'description', 'Characteristic VAS Probability']]
627
+
628
+ # Adjust last 'KP_to' value if necessary
629
+ kp_max = np_array[KP_TO, -1]
630
+ if kp_max > (df['KP_to'].iloc[-1]):
631
+ df.loc[df.index[-1], 'KP_to'] = kp_max
632
+
633
+ # Convert columns to appropriate numeric types
634
+ df['pp_set'] = df['pp_set'].astype(np.int64)
635
+ df['KP_from'] = df['KP_from'].astype(np.float64)
636
+ df['KP_to'] = df['KP_to'].astype(np.float64)
637
+
638
+ return df
639
+
640
+ def run(work_dir, file_name, pipeline_id, scenario_no, bl_verbose):
641
+
642
+ """
643
+ Import scenario data from an Excel file and preprocess it.
644
+
645
+ Parameters
646
+ ----------
647
+ work_dir : str
648
+ Directory where the Excel file is located.
649
+ file_name : str
650
+ Name of the Excel file.
651
+ pipeline_id : str
652
+ Identifier of the pipeline.
653
+ scenario_no : int
654
+ Identifier of the scenario.
655
+
656
+ Returns
657
+ -------
658
+ df_scen : pandas.DataFrame
659
+ Dataframe containing the scenario data
660
+ np_distr : numpy.ndarray
661
+ Array containing the friction factor distributions
662
+ np_scen : numpy.ndarray
663
+ Array containing the scenario data
664
+ np_ends : numpy.ndarray
665
+ Array containing the end boundary conditions
666
+ df_pp : pandas.DataFrame
667
+ Array containing the post-processing data
668
+
669
+ Notes
670
+ -----
671
+ This function reads scenario data from an Excel file and preprocesses it. It extracts layout,
672
+ pipeline, and loadcase sets, and the number of simulations from the Excel file. Postprocesses
673
+ route, pipe, operating, soil, and scenario data. Processes post-processing sets and defines
674
+ the NumPy arrays for Monte Carlo Simulations.
675
+
676
+ Other Parameters
677
+ ----------------
678
+ bl_verbose : boolean, optional
679
+ True if intermediate printouts are required (False by default).
680
+ """
681
+
682
+ # Starting time of the pre-processing module
683
+ start_time = time.time()
684
+
685
+ # Print out in the terminal that the assembly of the main dataframe has started
686
+ if bl_verbose:
687
+ print("1. Assembly of the main dataframe")
688
+
689
+ # Read scenario data from the input Excel file
690
+ df_sens = pd.read_excel(rf'{work_dir}/{file_name}', sheet_name = 'Scenario')
691
+ scenario_no = int(scenario_no)
692
+
693
+ # Define layout, pipeline and loadcase sets and number of simulations
694
+ layout_set = df_sens.loc[(df_sens['Pipeline'] == pipeline_id) &
695
+ (df_sens['Scenario'] == scenario_no), 'Layout Set'].values[0]
696
+ pipeline_set = df_sens.loc[(df_sens['Pipeline'] == pipeline_id) &
697
+ (df_sens['Scenario'] == scenario_no), 'Pipeline'].values[0]
698
+ loadcase_set = df_sens.loc[(df_sens['Pipeline'] == pipeline_id) &
699
+ (df_sens['Scenario'] == scenario_no), 'Loadcase Set'].values[0]
700
+
701
+ # Read route data from the input Excel file and postprocess it
702
+ df_route = pd.read_excel(rf'{work_dir}/{file_name}', sheet_name='Route')
703
+ df_route_input = df_route.copy()
704
+ df_route, df_route_ends = calc_route_data(df_route, layout_set, pipeline_set)
705
+
706
+ # Read pipe data from the input Excel file and postprocess it
707
+ df_pipe = pd.read_excel(rf'{work_dir}/{file_name}', sheet_name = 'Pipe')
708
+ df_pipe = calc_pipe_data(df_pipe, pipeline_set)
709
+
710
+ # Read operating data from the input Excel file and interpolate it
711
+ df_oper = pd.read_excel(rf'{work_dir}/{file_name}', sheet_name = 'Operating')
712
+ df_oper = calc_operating_profiles(df_oper, df_route, pipeline_set, loadcase_set)
713
+ df_oper = calc_oper_data(df_oper, df_route_ends, pipeline_set, loadcase_set)
714
+
715
+ # Read soil data from the input Excel file and postprocess it
716
+ df_soil = pd.read_excel(rf'{work_dir}/{file_name}', sheet_name = 'Soils')
717
+
718
+ # Axial
719
+ df_soil['Axial Mean'], df_soil['Axial STD'] = ss.LBSoilDistributions(
720
+ friction_factor_le=df_soil['Axial LE'],
721
+ friction_factor_be=df_soil['Axial BE'],
722
+ friction_factor_he=df_soil['Axial HE'],
723
+ friction_factor_fit_type=df_soil['Axial Fit Bounds']
724
+ ).friction_distribution_parameters()[:2]
725
+
726
+ # Lateral Hydrotest
727
+ df_soil['Lateral Hydrotest Mean'], df_soil['Lateral Hydrotest STD'] = ss.LBSoilDistributions(
728
+ friction_factor_le=df_soil['Lateral Hydrotest LE'],
729
+ friction_factor_be=df_soil['Lateral Hydrotest BE'],
730
+ friction_factor_he=df_soil['Lateral Hydrotest HE'],
731
+ friction_factor_fit_type=df_soil['Lateral Hydrotest Fit Bounds']
732
+ ).friction_distribution_parameters()[:2]
733
+
734
+ # Lateral Operation
735
+ df_soil['Lateral Operation Mean'], df_soil['Lateral Operation STD'] = ss.LBSoilDistributions(
736
+ friction_factor_le=df_soil['Lateral Operation LE'],
737
+ friction_factor_be=df_soil['Lateral Operation BE'],
738
+ friction_factor_he=df_soil['Lateral Operation HE'],
739
+ friction_factor_fit_type=df_soil['Lateral Operation Fit Bounds']
740
+ ).friction_distribution_parameters()[:2]
741
+
742
+ df_soil = calc_soil_data(df_soil, pipeline_set)
743
+
744
+ # Postprocess scenario data
745
+ df_scen = calc_scenario_data(df_sens, df_route, df_pipe, df_oper, df_soil)
746
+
747
+ # Define the NumPy arrays used in the Monte Carlo Simulations
748
+ np_distr, np_scen, np_ends = calc_monte_carlo_data(df_scen, df_route_ends)
749
+
750
+ # Read post-processing sets from the input Excel file and postprocess them
751
+ df_pp = pd.read_excel(rf'{work_dir}/{file_name}', sheet_name = 'Post-Processing')
752
+ df_pp = calc_pp_data(df_pp, np_ends, pipeline_id, layout_set)
753
+
754
+ # Print out in the terminal time taken to create main dataframe
755
+ if bl_verbose:
756
+ print(f' Time taken to create main dataframe: {time.time() - start_time:.1f}s')
757
+
758
+ return np_distr, np_scen, np_ends, df_scen, df_route_input, df_pp
759
+
760
+ def calc_lognorm_soil(mu_mean, mu_std):
761
+
762
+ """
763
+ Compute the parameters of a lognormal distribution for friction factors (axial or lateral).
764
+
765
+ Parameters
766
+ ----------
767
+ mu_mean : float
768
+ The mean of the friction factor distribution.
769
+ mu_std : float
770
+ The standard deviation of the friction factor distribution.
771
+
772
+ Returns
773
+ -------
774
+ mu_range : numpy.ndarray
775
+ An array of values representing the range of the friction factor distribution
776
+ between probabilities of exceedance between 0.01% and 99.99%.
777
+ cdf_range : numpy.ndarray
778
+ An array of cumulative density function (CDF) values corresponding to `mu_range`.
779
+
780
+ Notes
781
+ -----
782
+ The function calculates the shape and scale parameters of a friction factor lognormal
783
+ distribution based on the provided mean (`mu_mean`) and standard deviation (`mu_std`).
784
+ It then computes the cumulative density function (CDF) for the generated range of values.
785
+
786
+ """
787
+
788
+ # Calculate shape and scale parameters of the lognormal distribution
789
+ mu_shape = np.sqrt(np.log(1 + mu_std**2 / mu_mean**2))
790
+ mu_scale = np.log(mu_mean**2 / np.sqrt(mu_mean**2 + mu_std**2))
791
+
792
+ # Calculate the lower and upper bounds of the distribution
793
+ mu_lower = lognorm(mu_shape, 0.0, np.exp(mu_scale)).ppf(0.0001)
794
+ mu_upper = lognorm(mu_shape, 0.0, np.exp(mu_scale)).ppf(0.9999)
795
+
796
+ # Generate a range of values within the distribution
797
+ mu_range = np.linspace(mu_lower, mu_upper, 10000)
798
+
799
+ # Compute the cumulative density function (CDF) for the generated range
800
+ cdf_range = lognorm.cdf(mu_range, mu_shape, 0.0, np.exp(mu_scale))
801
+
802
+ return mu_range, cdf_range
803
+
804
+ def calc_lognorm_hoos(type_elt, length_elt, hoos_mean, hoos_std, length_ref, rcm_charac):
805
+
806
+ """
807
+ Compute the parameters of the horizontal out-of-straightness (HOOS) lognormal distribution
808
+ for different types of elements (e.g., Straight, Bend, Sleeper, RCM). This function takes into
809
+ account the scaling factor of the HOOS distribution. For RCM, the HOOS factor is not a factor
810
+ but the critical buckling force.
811
+
812
+ Parameters
813
+ ----------
814
+ type_elt : str
815
+ Type of the element.
816
+ length_elt : float
817
+ Length of the element.
818
+ hoos_mean : float
819
+ Mean of the HOOS distribution.
820
+ hoos_std : float
821
+ Standard deviation of the HOOS distribution.
822
+ length_ref : float
823
+ Reference length.
824
+ rcm_charac : float
825
+ Characteristic buckling force for the Residual Curvature Method (RCM).
826
+
827
+ Returns
828
+ -------
829
+ x_range : numpy.ndarray
830
+ An array of values representing the range of the friction factor distribution
831
+ between probabilities of exceedance between 0.01% and 99.99%.
832
+ cdf_range : numpy.ndarray
833
+ An array of cumulative density function (CDF) values corresponding to `x_range`.
834
+
835
+ Notes
836
+ -----
837
+ This function computes the parameters of a lognormal distribution for different types of
838
+ elements such as Straight, Bend, Sleeper, and RCM (Residual Curvature Method). It
839
+ calculates the cumulative density function (CDF) for the generated range of values
840
+ based on the HOOS distribution parameters.
841
+
842
+ """
843
+
844
+ # Extract the type of element (e.g., Straight, Bend, Sleeper, RCM)
845
+ type_elt_split = type_elt.split(' ')[0]
846
+
847
+ # Compute the ratio of the reference length to the element length
848
+ n = length_ref / length_elt
849
+
850
+ if type_elt_split == 'Straight' or type_elt_split == 'Bend':
851
+ # Calculate parameters for straight or bend elements
852
+ shape_hoos = np.sqrt(np.log(1 + hoos_std**2 / hoos_mean**2))
853
+ scale_hoos = np.log(hoos_mean**2 / (np.sqrt(hoos_mean**2 + hoos_std**2)))
854
+
855
+ # Define the range of the HOOS distribution
856
+ hoos_lower = 0.0
857
+ hoos_upper = 20.0
858
+ x = np.linspace(hoos_lower, hoos_upper, 200000)
859
+
860
+ # Calculate the cumulative density function (CDF) considering the scaling factor
861
+ cdf = 1-(1-lognorm.cdf(x, shape_hoos, 0.0, np.exp(scale_hoos)))**(1/n)
862
+
863
+ # Generate a range of CDF values
864
+ cdf_range = np.arange(0.0, 1.0, 0.0001)
865
+
866
+ # Interpolate to get the corresponding values of the distribution
867
+ x_range = np.interp(cdf_range, cdf, x)
868
+
869
+ elif type_elt_split == 'Sleeper':
870
+ # Calculate parameters for sleeper elements
871
+ shape_hoos = np.sqrt(np.log(1 + hoos_std**2 / hoos_mean**2))
872
+ scale_hoos = np.log(hoos_mean**2 / (np.sqrt(hoos_mean**2 + hoos_std**2)))
873
+
874
+ # Calculate the lower and upper bounds of the distribution for sleeper elements
875
+ hoos_lower = lognorm(shape_hoos, 0.0, np.exp(scale_hoos)).ppf(0.0001)
876
+ hoos_upper = lognorm(shape_hoos, 0.0, np.exp(scale_hoos)).ppf(0.9999)
877
+
878
+ # Generate a range of values within the distribution
879
+ x_range = np.linspace(hoos_lower, hoos_upper, 10000)
880
+
881
+ # Compute the cumulative density function (CDF) for the generated range
882
+ cdf_range = lognorm.cdf(x_range, shape_hoos, 0.0, np.exp(scale_hoos))
883
+
884
+ elif type_elt_split == 'RCM':
885
+ # Calculate parameters for RCM elements
886
+ shape_hoos = np.sqrt(np.log(1 + hoos_std**2 / hoos_mean**2))
887
+ scale_hoos = np.log(hoos_mean**2 / (np.sqrt(hoos_mean**2 + hoos_std**2)))
888
+ scale_hoos = scale_hoos + np.log(rcm_charac)
889
+
890
+ # Calculate the lower and upper bounds of the distribution for RCM elements
891
+ hoos_lower = lognorm(shape_hoos, 0.0, np.exp(scale_hoos)).ppf(0.0001)
892
+ hoos_upper = lognorm(shape_hoos, 0.0, np.exp(scale_hoos)).ppf(0.9999)
893
+
894
+ # Generate a range of values within the distribution
895
+ x_range = np.linspace(hoos_lower, hoos_upper, 10000)
896
+
897
+ # Compute the cumulative density function (CDF) for the generated range
898
+ cdf_range = lognorm.cdf(x_range, shape_hoos, 0.0, np.exp(scale_hoos))
899
+
900
+ return x_range, cdf_range