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,1305 @@
1
+ """
2
+ This module contains the post-processing functions of BuckPy.
3
+ """
4
+
5
+ import time
6
+ import numpy as np
7
+ import pandas as pd
8
+ import pandas.io.formats.excel
9
+ import matplotlib.pyplot as plt
10
+ from matplotlib.gridspec import GridSpec
11
+ from pathlib import Path
12
+ from openpyxl import load_workbook, writer
13
+
14
+ def pp_comb_prob(df_in, df_buckle, col_no, n_sim):
15
+ """
16
+ Count the number of set combinations based on given set number
17
+
18
+ Parameters
19
+ ----------
20
+ df_in : pandas DataFrame
21
+ DataFrame containing the set number of combination with buckles along the pipeline.
22
+ df_buckle : pandas DataFrame
23
+ DataFrame containing the count and probability of set number of combination with buckles.
24
+ col_no : String
25
+ Column name of the set number
26
+ n_sim : int
27
+ Number of simulations.
28
+
29
+ Returns
30
+ -------
31
+ df_out : pandas DataFrame
32
+ DataFrame containing post-processed statistics about the number of set combination
33
+ with buckles along the pipeline.
34
+ """
35
+
36
+ def pp_count_comb(row, df, col):
37
+ # Add 1 to the value each time there is a buckle at the current set
38
+ df.iloc[int(row.loc['isim']), int(row.loc[col])] += 1.0
39
+
40
+ # Row number is the total simulation number and column number is the total set number
41
+ n_col = df_in.unique().size
42
+ df_out = pd.DataFrame(0, index = np.arange(int(n_sim)),
43
+ columns = np.arange(int(n_col + 1.0)))
44
+
45
+ # Add 1 to the value in df_out each time there is a buckle
46
+ df_buckle.apply(lambda row: pp_count_comb(row, df_out, col_no), axis = 1)
47
+
48
+ # Delete the all 0 rows and the first column, and add prefix to column names
49
+ df_out = df_out[(df_out.T != 0).any()].iloc[:, 1:].add_prefix('Set_')
50
+
51
+ # Count the number of unique set combinations
52
+ col_list = df_out.columns.values.tolist()
53
+ df_out = df_out.groupby(col_list).size().reset_index().rename(
54
+ columns = {0: 'Number of Simulations'})
55
+
56
+ # Calculate probability and sort values in descending order based on count and reset index
57
+ df_out['Probability of Combination'] = df_out['Number of Simulations'] / n_sim
58
+ df_out = df_out.sort_values(
59
+ by = 'Number of Simulations', ascending = False).reset_index(drop = True)
60
+
61
+ return df_out
62
+
63
+ def pp_rename_columns(df_in, df_out):
64
+ """
65
+ Rename columns in the post-processing DataFrame.
66
+
67
+ Parameters
68
+ ----------
69
+ df_in : pandas DataFrame
70
+ DataFrame containing the set number of combination with buckles along the pipeline.
71
+ df_out : pandas DataFrame
72
+ DataFrame containing post-processed statistics about the number of set combination
73
+ with buckles along the pipeline.
74
+
75
+ Returns
76
+ -------
77
+ df_out : pandas DataFrame
78
+ DataFrame containing post-processed statistics about the number of set combination
79
+ with buckles along the pipeline.
80
+
81
+ Notes
82
+ -----
83
+ Use the Python built-in ``set`` for unique labels, e.g., ``set(labels)``.
84
+ """
85
+
86
+ # Change column names from 'Set_1' to predefined names
87
+ new_col_list = np.array(df_in['col_name'].values).tolist()
88
+ df_out.columns = np.concatenate(
89
+ np.array([new_col_list, ['Number of Simulations', 'Probability of Combination']],
90
+ dtype = object)).tolist()
91
+
92
+ # Create new column name list sorted by KP values and reorder columns
93
+ df_out['Combination Id'] = df_out.index + 1
94
+ col_list = np.array(df_in.sort_values(by = 'index').loc[:, 'col_name'].values).tolist()
95
+ cols = np.concatenate(
96
+ np.array([['Combination Id', 'Number of Simulations', 'Probability of Combination'],
97
+ col_list], dtype = object)).tolist()
98
+ df_out = df_out[cols]
99
+
100
+ # Create a double header
101
+ header_first_line = [''] * 3 + ['Number of Buckles'] * (len(cols) - 3)
102
+ header_turples = list(zip(header_first_line, cols))
103
+ double_header = pd.MultiIndex.from_tuples(header_turples)
104
+ df_out.columns = double_header
105
+
106
+ return df_out
107
+
108
+ def pp_comb_buckles_per_set(df_pp, df_pp_set, n_sim):
109
+ """
110
+ Count the number of set combinations and sort based on the most frequent set combinations
111
+ based on post-processing set.
112
+
113
+ Parameters
114
+ ----------
115
+ df_pp : pandas DataFrame
116
+ DataFrame containing pipeline element analysis results.
117
+ df_pp_set : pandas DataFrame
118
+ DataFrame containing the definition of the post-processed sets.
119
+ n_sim : int
120
+ Number of simulations.
121
+
122
+ Returns
123
+ -------
124
+ df_set_comb : pandas DataFrame
125
+ DataFrame containing post-processed statistics about the number of set combination
126
+ with buckles along the pipeline.
127
+ """
128
+
129
+ # Create index columns for the original index and the sorted pp set value index
130
+ df_pp_set = df_pp_set[['pp_set', 'KP_from', 'KP_to']].sort_values(
131
+ by = ['pp_set', 'KP_from']).reset_index()
132
+ df_pp_set = df_pp_set.reset_index().rename(columns = {'level_0': 'index_sorted'})
133
+
134
+ # Select the number of simulation and post-precessing set number with a buckle
135
+ df_set_buckle_no = df_pp[['isim', 'pp_set']].sort_values(by = ['isim', 'pp_set'])
136
+
137
+ # Count the number of set combinations based on given set number
138
+ df_set_comb = pp_comb_prob(df_pp_set['pp_set'], df_set_buckle_no, 'pp_set', n_sim)
139
+
140
+ # Insert the extra column of duplicated set number into df_set_comb
141
+ df_duplicated = df_pp_set[df_pp_set.duplicated(subset = ['pp_set'])].reset_index(drop = True)
142
+ df_duplicated.apply(lambda row: df_set_comb.insert(
143
+ int(row['index_sorted']), f"Duplicate_{int(row['pp_set'])}",
144
+ df_set_comb.iloc[:, int(row['index_sorted'] - 1)]), axis = 1)
145
+
146
+ # Create new column name using KP from and KP to
147
+ df_pp_set[['KP_from', 'KP_to']] = df_pp_set[['KP_from', 'KP_to']].astype(int).astype(str)
148
+ df_pp_set['col_name'] = 'KP ' + df_pp_set['KP_from'] + ' to ' + df_pp_set['KP_to']
149
+
150
+ # Rename column names from 'Set_' to certain column names
151
+ df_set_comb = pp_rename_columns(df_pp_set, df_set_comb)
152
+
153
+ return df_set_comb
154
+
155
+ def pp_comb_buckles_per_section(df_pp, df_scen, n_sim):
156
+ """
157
+ Count the number of set combinations and sort based on the most frequent set combinations
158
+ based on section number in the route data.
159
+
160
+ Parameters
161
+ ----------
162
+ df_pp : pandas DataFrame
163
+ DataFrame containing pipeline element analysis results.
164
+ df_scen : DataFrame
165
+ Dataframe containing the design data along the pipeline route (mesh) that remains
166
+ constant among deterministic and Monte-Carlo simulations.
167
+ n_sim : int
168
+ Number of simulations.
169
+
170
+ Returns
171
+ -------
172
+ df_set_comb : pandas DataFrame
173
+ DataFrame containing post-processed statistics about the number of set combination
174
+ with buckles along the pipeline.
175
+ """
176
+
177
+ def pp_section_no(kp, kp_list):
178
+ # Add the current KP to the kp list and sort it
179
+ kp_list = np.append(kp_list, kp)
180
+ kp_list.sort()
181
+
182
+ # Find the index of the current KP
183
+ section_no = np.where(kp_list == kp)[0][0]
184
+
185
+ return section_no
186
+
187
+ # Select the number of simulation and post-precessing KP number with a buckle
188
+ df_buckle_kp = df_pp[['isim', 'KP']].sort_values(by = ['isim', 'KP'])
189
+
190
+ # Use KP range to group KP into sections and rename columns
191
+ df_section = df_scen[['KP From', 'KP To', 'Point ID From', 'Point ID To']].drop_duplicates(
192
+ subset = ['KP From']).reset_index(drop = True)
193
+ df_section.columns = ['KP_from', 'KP_to', 'point_id_from', 'point_id_to']
194
+
195
+ # Find the unique KP value and create section number column in df_buckle_kp
196
+ df_kp = pd.DataFrame({'KP': df_buckle_kp['KP'].unique()})
197
+ df_kp['Section No'] = df_kp.apply(lambda row: pp_section_no(
198
+ row['KP'], df_section['KP_from'].unique()), axis = 1)
199
+
200
+ # Merge the Section Number column to df_buckle_kp
201
+ df_buckle_kp = pd.merge(df_buckle_kp, df_kp, on = 'KP', how = 'left')
202
+
203
+ # Count the number of set combinations based on given set number
204
+ df_set_comb = pp_comb_prob(df_section['KP_from'], df_buckle_kp, 'Section No', n_sim)
205
+
206
+ # Create new column name using Point ID
207
+ df_section = df_section.sort_values(by = 'KP_from').reset_index()
208
+ df_section['col_name'] = df_section['point_id_from'] + ' to ' + df_section['point_id_to']
209
+
210
+ # Rename column names from 'Set_' to certain column names
211
+ df_set_comb = pp_rename_columns(df_section, df_set_comb)
212
+
213
+ return df_set_comb
214
+
215
+ def pp_char_vas(df_pp, df_buckling, df_set):
216
+ """
217
+ Determine the characteristic VAS.
218
+
219
+ Parameters
220
+ ----------
221
+ df_pp : pandas DataFrame
222
+ DataFrame containing pipeline element analysis results.
223
+ df_buckling : pandas DataFrame
224
+ DataFrame containing buckling data.
225
+ df_set : pandas DataFrame
226
+ DataFrame containing sets along the pipeline route.
227
+
228
+ Returns
229
+ -------
230
+ df_set : pandas DataFrame
231
+ Updated DataFrame containing characteristic VAS information.
232
+ """
233
+
234
+ df_merged = pd.merge(df_pp, df_buckling, on='pp_set')
235
+ df_merged = df_merged.sort_values(by = ['pp_set', 'VAS_op'])
236
+ df_merged['VAS_op'] = df_merged['VAS_op'].round(1)
237
+
238
+ # Dataframe grouping the VAS in ascending order at each set along the pipeline route
239
+ df_char_vas = df_merged.groupby(by = ['pp_set', 'VAS_op', 'prob_buckling'], as_index = False) \
240
+ .agg(VAS_occurrence = ('VAS_op', 'count'), no_buckles = ('no_buckles', 'max'))
241
+
242
+ # Add the probability associated to the characteristic VAS
243
+ df_set_temp = df_set.copy().drop_duplicates(subset = 'pp_set', keep = 'first')
244
+ df_char_vas = df_char_vas.merge(df_set_temp[['pp_set', 'Characteristic VAS Probability']],
245
+ on = 'pp_set', how = 'inner')
246
+
247
+ # Cumulative distributions of the VAS (conditional and unconditional)
248
+ df_char_vas['VAS_prob_cond'] = df_char_vas['VAS_occurrence'] / df_char_vas['no_buckles']
249
+ df_char_vas['VAS_cumsum_prob_cond'] = df_char_vas[['pp_set', 'VAS_prob_cond']] \
250
+ .groupby(by = 'pp_set').cumsum()
251
+
252
+ # Lines below associate VAS=0 to cases not buckling
253
+ df_char_vas['VAS_cumsum_prob_uncond'] = (1.0 - df_char_vas['prob_buckling']) \
254
+ + df_char_vas['VAS_cumsum_prob_cond'] * df_char_vas['prob_buckling']
255
+
256
+ # Complementary distributions of the VAS (conditional and unconditional)
257
+ df_char_vas['prob_exceedance_cond'] = 1.0 - df_char_vas['VAS_cumsum_prob_cond']
258
+ df_char_vas['prob_exceedance_uncond'] = 1.0 - df_char_vas['VAS_cumsum_prob_uncond']
259
+
260
+ # Difference bewteen the complementary distributions and target probabilities of excedance
261
+ df_char_vas['delta_prob_exceedance_cond'] = \
262
+ (df_char_vas['prob_exceedance_cond'] - \
263
+ df_char_vas['Characteristic VAS Probability']).abs()
264
+ df_char_vas['delta_prob_exceedance_uncond'] = \
265
+ (df_char_vas['prob_exceedance_uncond'] - \
266
+ df_char_vas['Characteristic VAS Probability']).abs()
267
+
268
+ # NumPy array with the rows containing the characteristic VAS of each pp_set
269
+ vas_cond_indices = df_char_vas.groupby(
270
+ by = 'pp_set')['delta_prob_exceedance_cond'].idxmin().to_numpy()
271
+ vas_uncond_indices = df_char_vas.groupby(
272
+ by = 'pp_set')['delta_prob_exceedance_uncond'].idxmin().to_numpy()
273
+
274
+ # Find conditional VAS by pp_set
275
+ df_vas_cond = df_char_vas.loc[vas_cond_indices, ['pp_set', 'VAS_op']]
276
+ df_vas_cond.rename(columns = {'VAS_op': 'VAS_charac_conditional'}, inplace = True)
277
+
278
+ # Find unconditional VAS by pp_set
279
+ df_vas_uncond = df_char_vas.loc[vas_uncond_indices, ['pp_set', 'VAS_op']]
280
+ df_vas_uncond.rename(columns = {'VAS_op': 'VAS_charac_unconditional'}, inplace = True)
281
+
282
+ # Assign conditional and unconditional VAS by pp_set
283
+ df_set = pd.merge(df_set, df_vas_cond, on = 'pp_set', how = 'outer')
284
+ df_set = pd.merge(df_set, df_vas_uncond, on = 'pp_set', how = 'outer')
285
+ df_set.loc[df_set['prob_buckling'] < df_set['Characteristic VAS Probability'],
286
+ 'VAS_charac_unconditional'] = 0.0
287
+
288
+ return df_set
289
+
290
+ def pp_char_fric(df_pp, df_buckling, df_set, prob_exceed_char_fric):
291
+ """
292
+ Determine the characteristic lateral breakout friction.
293
+
294
+ Parameters
295
+ ----------
296
+ df_pp : pandas DataFrame
297
+ DataFrame containing pipeline element analysis results.
298
+ df_buckling : pandas DataFrame
299
+ DataFrame containing buckling data.
300
+ df_set : pandas DataFrame
301
+ DataFrame containing sets along the pipeline route.
302
+ prob_exceed_char_fric : float
303
+ Probability of exceeding characteristic lateral breakout friction.
304
+
305
+ Returns
306
+ -------
307
+ df_set : pandas DataFrame
308
+ Updated DataFrame containing characteristic lateral breakout friction information.
309
+ """
310
+
311
+ # Dataframe containing the list of frictions in ascending order at each set
312
+ df_merged = pd.merge(df_pp, df_buckling, on='pp_set')
313
+ df_merged = df_merged.sort_values(by = ['pp_set', 'mulat_op'])
314
+
315
+ # Dataframe grouping the frictions in ascending ordet at each set along the pipeline route
316
+ df_char_fric = df_merged.groupby(
317
+ by = ['pp_set', 'mulat_op', 'prob_buckling'], as_index = False) \
318
+ .agg(mulat_occurrence = ('mulat_op', 'count'), no_buckles = ('no_buckles', 'max'))
319
+
320
+ # Cumulative distributions of the friction (conditional and unconditional)
321
+ df_char_fric['mulat_prob_cond'] = df_char_fric['mulat_occurrence'] / df_char_fric['no_buckles']
322
+ df_char_fric['mulat_cumsum_prob_cond'] = df_char_fric[['pp_set', 'mulat_prob_cond']] \
323
+ .groupby(by = 'pp_set').cumsum()
324
+
325
+ # Lines below associate friction=0 to cases not buckling
326
+ df_char_fric['mulat_cumsum_prob_uncond'] = (1.0 - df_char_fric['prob_buckling']) \
327
+ + df_char_fric['mulat_cumsum_prob_cond'] * df_char_fric['prob_buckling']
328
+
329
+ # Complementary distributions of the friction (conditional and unconditional)
330
+ df_char_fric['prob_exceedance_cond'] = 1.0 - df_char_fric['mulat_cumsum_prob_cond']
331
+ df_char_fric['prob_exceedance_uncond'] = 1.0 - df_char_fric['mulat_cumsum_prob_uncond']
332
+
333
+ # Difference between the complementary distributions and target probabilities of excedance
334
+ df_char_fric['delta_prob_exceedance_cond'] = (df_char_fric['prob_exceedance_cond']
335
+ - prob_exceed_char_fric).abs()
336
+ df_char_fric['delta_prob_exceedance_uncond'] = (df_char_fric['prob_exceedance_uncond']
337
+ - prob_exceed_char_fric).abs()
338
+
339
+ # NumPy array with the rows containing the characteristic friction of each pp_set
340
+ indices_mulat_cond = df_char_fric.groupby(by = 'pp_set')['delta_prob_exceedance_cond'] \
341
+ .idxmin().to_numpy()
342
+ indices_mulat_uncond = df_char_fric.groupby(by = 'pp_set')['delta_prob_exceedance_uncond'] \
343
+ .idxmin().to_numpy()
344
+
345
+ # Find conditional friction by pp_set
346
+ df_fric_cond = df_char_fric.loc[indices_mulat_cond, ['pp_set', 'prob_buckling', 'mulat_op']]
347
+ df_fric_cond.rename(columns = {'mulat_op': 'mulat_charac_conditional'}, inplace = True)
348
+
349
+ # Find unconditional friction by pp_set
350
+ df_fric_uncond = df_char_fric.loc[indices_mulat_uncond, ['pp_set', 'prob_buckling', 'mulat_op']]
351
+ df_fric_uncond.rename(columns = {'mulat_op': 'mulat_charac_unconditional'}, inplace = True)
352
+
353
+ # Assign conditional and unconditional friction by pp_set
354
+ df_set = pd.merge(df_set, df_fric_cond, on = 'pp_set', how = 'outer')
355
+ df_set = pd.merge(df_set, df_fric_uncond, on = 'pp_set', how = 'outer')
356
+ df_set.loc[df_set['prob_buckling'] < prob_exceed_char_fric,
357
+ 'mulat_charac_unconditional'] = 0.0
358
+
359
+ return df_set
360
+
361
+ def pp_elem(df_pp, n_sim):
362
+ """
363
+ Perform post-processing of the probability of buckling, VAS and lateral breakout
364
+ friction at each element along the pipeline route.
365
+
366
+ Parameters
367
+ ----------
368
+ df_pp : pandas DataFrame
369
+ DataFrame containing the results of the analyses.
370
+ n_sim : int
371
+ Total number of simulations.
372
+
373
+ Returns
374
+ -------
375
+ df_elem : pandas DataFrame
376
+ DataFrame containing post-processed probabilities, VAS and lateral breakout friction
377
+ for each pipeline element.
378
+ """
379
+
380
+ # Probability of buckling at each element along the pipeline route
381
+ df_buckling = df_pp[['KP', 'isim']].groupby('KP', as_index = False).agg(
382
+ no_buckles = ('isim', 'nunique'))
383
+ df_buckling['prob_buckling'] = df_buckling['no_buckles'] / n_sim
384
+ df_buckling['prob_not_buckling'] = 1.0 - df_buckling['prob_buckling']
385
+
386
+ # VAS at each element along the pipeline route
387
+ df_vas = df_pp[['KP', 'VAS_op']].groupby('KP', as_index = False).agg(
388
+ VAS_mean = ('VAS_op', 'mean'), VAS_std = ('VAS_op', 'std'),
389
+ VAS_min = ('VAS_op', 'min'), VAS_max = ('VAS_op', 'max'))
390
+
391
+ # Lateral breakout friction at each element along the pipeline route
392
+ df_friction = df_pp[['KP', 'mulat_op']].groupby('KP', as_index = False).agg(
393
+ mulat_mean = ('mulat_op', 'mean'), mulat_std = ('mulat_op', 'std'),
394
+ mulat_min = ('mulat_op', 'min'), mulat_max = ('mulat_op', 'max'))
395
+
396
+ # Merge df_buckling and df_vas on 'KP'
397
+ df_elem = pd.merge(df_buckling, df_vas, on = 'KP')
398
+
399
+ # Merge merged_df and df_friction on 'KP'
400
+ df_elem = pd.merge(df_elem, df_friction, on = 'KP')
401
+
402
+ # Change labels for print-out
403
+ df_elem = df_elem.rename(columns = {
404
+ 'KP': 'Centroid of the Element (m)',
405
+ 'no_buckles': 'Number of Simulations with a Buckle',
406
+ 'prob_buckling': 'Probability of Buckling',
407
+ 'prob_not_buckling': 'Probability of not Buckling',
408
+ 'VAS_mean': 'Mean of the VAS (m)',
409
+ 'VAS_std': 'Standard Deviation of the VAS (m)',
410
+ 'VAS_min': 'Minimum VAS (m)',
411
+ 'VAS_max': 'Maximum VAS (m)',
412
+ 'mulat_mean': 'Mean of the Lateral Breakout Friction',
413
+ 'mulat_std': 'Standard Deviation of the Lateral Breakout Friction',
414
+ 'mulat_min': 'Minimum Lateral Breakout Friction',
415
+ 'mulat_max': 'Maximum Lateral Breakout Friction'})
416
+
417
+ df_elem = df_elem.fillna(0.0)
418
+
419
+ return df_elem
420
+
421
+ def pp_no_buckles(df_pp, n_sim):
422
+ """
423
+ Perform post-processing of the number of buckles along the pipeline.
424
+
425
+ Parameters
426
+ ----------
427
+ df_pp : pandas DataFrame
428
+ DataFrame containing pipeline element analysis results.
429
+ n_sim : int
430
+ Total number of simulations.
431
+
432
+ Returns
433
+ -------
434
+ df_no_buckles : pandas DataFrame
435
+ DataFrame containing post-processed statistics about the number of buckles
436
+ along the pipeline.
437
+ """
438
+
439
+ # Number of buckles per simulation
440
+ df_grouped = df_pp[['isim', 'KP']].groupby('isim', as_index = False).agg(
441
+ no_buckles = ('KP', 'count'))
442
+
443
+ # Number of simulations as a function of the number of buckles along the pipeline
444
+ df_no_buckles = df_grouped.pivot_table(columns = ['no_buckles'], aggfunc = 'size')
445
+ df_no_buckles = df_no_buckles.reset_index()
446
+ df_no_buckles = df_no_buckles.rename(columns={
447
+ 'n_buckle': 'Total Number of Buckles', 0: 'Occurrence'})
448
+
449
+ # Accounting for cases without buckle in cumsum
450
+ new_row = {'no_buckles': 0, 'Occurrence': n_sim - df_no_buckles['Occurrence'].sum()}
451
+ df_no_buckles = pd.concat([df_no_buckles, pd.DataFrame(new_row, index=[0])], ignore_index=True)
452
+ df_no_buckles = df_no_buckles.sort_values(by = ['no_buckles']).reset_index(drop=True)
453
+
454
+ # Distribution of the expected number of buckles along the pipeline
455
+ df_no_buckles['Probability'] = df_no_buckles['Occurrence'] / n_sim
456
+ df_no_buckles['Cumulative Probability'] = df_no_buckles['Probability'].cumsum()
457
+
458
+ # Change labels for print-out
459
+ df_no_buckles = df_no_buckles.rename(columns = {
460
+ 'no_buckles': 'Number of Buckles',
461
+ 'Occurrence': 'Number of Simulations',
462
+ 'Probability': 'Probability of Buckling',
463
+ 'Cumulative Probability': 'Cumulative Probability of Buckling'})
464
+
465
+ return df_no_buckles
466
+
467
+ def pp_sets(df_pp, n_sim, prob_exceed_char_fric, df_pp_set, df_scen):
468
+ """
469
+ Perform post-processing of the probability of buckling, VAS and lateral breakout
470
+ friction at each post-processing set along the pipeline route.
471
+
472
+ Parameters
473
+ ----------
474
+ df_pp : pandas DataFrame
475
+ DataFrame containing pipeline element analysis results.
476
+ n_sim : int
477
+ Total number of simulations.
478
+ prob_exceed_char_fric : float
479
+ Probability of exceedance of the characteristic lateral breakout friction.
480
+ df_pp_set : DataFrame
481
+ Definition of element sets for post-processing outputs.
482
+ df_scen : DataFrame
483
+ Dataframe containing the design data along the pipeline
484
+ route (mesh) that remains constant among deterministic and
485
+ Monte-Carlo simulations.
486
+
487
+ Returns
488
+ -------
489
+ df_set : pandas DataFrame
490
+ DataFrame containing post-processed statistics about pipeline sets.
491
+ """
492
+
493
+ # Probability of buckling at each set along the pipeline route
494
+ df_pp_set = df_pp_set[['pp_set', 'KP_from', 'KP_to', 'Characteristic VAS Probability']]
495
+
496
+ # Probability of buckling at each set along the pipeline route
497
+ df_buckling = df_pp[['pp_set', 'KP_from', 'KP_to', 'isim', 'VAS_op']].groupby(
498
+ 'pp_set', as_index = False).agg(
499
+ no_simulations_with_buckles = ('isim', 'nunique'), no_buckles = ('VAS_op', 'count'))
500
+ df_buckling['prob_buckling'] = df_buckling['no_simulations_with_buckles'] / n_sim
501
+ df_buckling['prob_not_buckling'] = 1.0 - df_buckling['prob_buckling']
502
+
503
+ # VAS at each set along the pipeline route
504
+ df_vas = df_pp[['pp_set', 'VAS_op']].groupby('pp_set', as_index = False).agg(
505
+ VAS_mean = ('VAS_op', 'mean'), VAS_std = ('VAS_op', 'std'),
506
+ VAS_min = ('VAS_op', 'min'), VAS_max = ('VAS_op', 'max'))
507
+
508
+ # Lateral breakout friction at each set along the pipeline route
509
+ df_friction = df_pp[['pp_set', 'mulat_op']].groupby('pp_set', as_index = False).agg(
510
+ mulat_mean = ('mulat_op', 'mean'), mulat_std = ('mulat_op', 'std'),
511
+ mulat_min = ('mulat_op', 'min'), mulat_max = ('mulat_op', 'max'))
512
+
513
+ # Merge df_buckling and df_vas on 'pp_set'
514
+ df_set = pd.merge(df_pp_set, df_buckling, on = 'pp_set', how = 'outer')
515
+
516
+ # Merge df_buckling and df_vas on 'pp_set'
517
+ df_set = pd.merge(df_set, df_vas, on = 'pp_set', how = 'outer')
518
+ df_set['VAS_mean'] = df_set['VAS_mean'].fillna(0.0)
519
+ df_set['VAS_std'] = df_set['VAS_std'].fillna(0.0)
520
+ df_set['VAS_min'] = df_set['VAS_min'].fillna(0.0)
521
+ df_set['VAS_max'] = df_set['VAS_max'].fillna(0.0)
522
+
523
+ # Determine characteristic VAS
524
+ df_set = pp_char_vas(df_pp, df_buckling, df_set)
525
+
526
+ # Merge merged_df and df_friction on 'pp_set' and determine characteristic friction
527
+ df_set = pd.merge(df_set, df_friction, on = 'pp_set', how = 'outer')
528
+
529
+ # Determine characteristic lateral breakout friction
530
+ df_set = pp_char_fric(df_pp, df_buckling, df_set, prob_exceed_char_fric)
531
+
532
+ # Change labels for print-out
533
+ df_set = df_set.rename(columns = {
534
+ 'pp_set': 'Set Label',
535
+ 'KP_from': 'KP From (m)',
536
+ 'KP_to': 'KP To (m)',
537
+ 'no_simulations_with_buckles': 'Number of Simulations with Buckles per Set',
538
+ 'no_buckles': 'Number of Buckles per Set',
539
+ 'prob_buckling': 'Probability of Buckling',
540
+ 'prob_not_buckling': 'Probability of not Buckling',
541
+ 'VAS_mean': 'Mean of the VAS (m)',
542
+ 'VAS_std': 'Standard Deviation of the VAS (m)',
543
+ 'VAS_min': 'Minimum VAS (m)',
544
+ 'VAS_max': 'Maximum VAS (m)',
545
+ 'VAS_charac_conditional': 'Characteristic VAS, Conditional (m)',
546
+ 'VAS_charac_unconditional': 'Characteristic VAS, Unconditional (m)',
547
+ 'mulat_mean': 'Mean of the Lateral Breakout Friction',
548
+ 'mulat_std': 'Standard Deviation of the Lateral Breakout Friction',
549
+ 'mulat_min': 'Minimum Lateral Breakout Friction',
550
+ 'mulat_max': 'Maximum Lateral Breakout Friction',
551
+ 'mulat_charac_unconditional': 'Characteristic Lateral Breakout Friction, Buckles'})
552
+
553
+ # Drop column for print-out
554
+ df_set = df_set.drop(columns = 'mulat_charac_conditional')
555
+
556
+ # Sort by KP From
557
+ df_set = df_set.sort_values(by = 'KP From (m)')
558
+
559
+ # Fill characteristic frictions at planned buckles with zero
560
+ df_set['Characteristic Lateral Breakout Friction Probability'] = \
561
+ prob_exceed_char_fric
562
+ df_set.loc[df_set['Characteristic Lateral Breakout Friction, Buckles'] == 0.0,
563
+ 'Characteristic Lateral Breakout Friction Probability'] = 0.0
564
+
565
+ # Define HE of the geotechnical friction
566
+ df_scen['Lateral Breakout Friction, HE, Geotech'] = \
567
+ df_scen.apply(lambda x: np.interp(1.0 - prob_exceed_char_fric,
568
+ x['mul OP CDF Array'],
569
+ x['mul OP Array']), axis = 1)
570
+ df_set['Lateral Breakout Friction, HE, Geotech'] = \
571
+ df_set.apply(lambda x: np.interp(x['KP From (m)'],
572
+ df_scen['KP'],
573
+ df_scen['Lateral Breakout Friction, HE, Geotech']),
574
+ axis = 1)
575
+
576
+ # Convert route type strings to descriptive representation
577
+ df_scen_temp = df_scen.copy()
578
+
579
+ df_scen_temp.loc[df_scen_temp['Route Type'] == 1, 'Route'] = 'Straight'
580
+ df_scen_temp.loc[df_scen_temp['Route Type'] == 2, 'Route'] = 'Bend'
581
+ df_scen_temp.loc[df_scen_temp['Route Type'] == 3, 'Route'] = 'Sleeper'
582
+ df_scen_temp.loc[df_scen_temp['Route Type'] == 4, 'Route'] = 'RCM'
583
+
584
+ # Assign route type to df_set
585
+ for index, row in df_set.iterrows():
586
+ df_set.loc[index, 'Route'] = df_scen_temp.loc[
587
+ (df_scen_temp['KP'] > row['KP From (m)']) &
588
+ (df_scen_temp['KP'] < row['KP To (m)']),
589
+ 'Route'].values[0]
590
+
591
+ df_set.loc[(df_set['Route'] == 'Sleeper') | (df_set['Route'] == 'RCM'),
592
+ 'Characteristic Lateral Breakout Friction, Buckles'] = 0.0
593
+
594
+ # Re-arrange columns
595
+ df_set = df_set[['Set Label', 'KP From (m)', 'KP To (m)',
596
+ 'Number of Simulations with Buckles per Set',
597
+ 'Number of Buckles per Set',
598
+ 'Probability of Buckling',
599
+ 'Probability of not Buckling',
600
+ 'Mean of the VAS (m)', 'Standard Deviation of the VAS (m)',
601
+ 'Minimum VAS (m)', 'Maximum VAS (m)',
602
+ 'Characteristic VAS Probability',
603
+ 'Characteristic VAS, Conditional (m)',
604
+ 'Characteristic VAS, Unconditional (m)',
605
+ 'Mean of the Lateral Breakout Friction',
606
+ 'Standard Deviation of the Lateral Breakout Friction',
607
+ 'Minimum Lateral Breakout Friction',
608
+ 'Maximum Lateral Breakout Friction',
609
+ 'Characteristic Lateral Breakout Friction Probability',
610
+ 'Characteristic Lateral Breakout Friction, Buckles',
611
+ 'Lateral Breakout Friction, HE, Geotech']]
612
+
613
+ df_set = df_set.fillna(0.0)
614
+ df_set['Probability of not Buckling'] = 1.0 - df_set['Probability of Buckling']
615
+
616
+ return df_set
617
+
618
+ def pp_eaf(df_pp_plot):
619
+ """
620
+ Converts units and renames columns for pipeline EAF plot DataFrame.
621
+
622
+ Parameters
623
+ ----------
624
+ df_pp_plot : pandas DataFrame
625
+ DataFrame containing pipeline plot data.
626
+
627
+ Returns
628
+ -------
629
+ df_pp_plot: pandas DataFrame
630
+ DataFrame with converted units and renamed columns.
631
+ """
632
+
633
+ # Convert the units of 'df_pp_plot' (N to kN)
634
+ df_pp_plot[['CBF_ht', 'CBF_op', 'EAF_inst', 'EAF_ht',
635
+ 'EAF_p_op', 'EAF_op', 'EAF_op_unbuck']] /= 1000.0
636
+
637
+ # Change labels for print-out
638
+ df_pp_plot = df_pp_plot.rename(columns = {
639
+ 'KP': 'KP (m)',
640
+ 'CBF_ht': 'CBF Hydrotest (kN)',
641
+ 'CBF_op': 'CBF Operation (kN)',
642
+ 'EAF_inst': 'EAF Installation [RLT] (kN)',
643
+ 'EAF_ht': 'EAF Hydrotest (kN)',
644
+ 'EAF_p_op': 'EAF Operation [Pressure Only] (kN)',
645
+ 'EAF_op': 'EAF Operation (kN)',
646
+ 'EAF_op_unbuck': 'EAF Operation [without Buckling] (kN)'
647
+ })
648
+
649
+ # Drop column for print-out
650
+ df_pp_plot = df_pp_plot.drop(columns = 'beta2')
651
+
652
+ return df_pp_plot
653
+
654
+ def pp_raw(df):
655
+ """
656
+ Converts units and renames columns of the DataFrame containing the raw data
657
+ from the BuckPy simulations.
658
+
659
+ Parameters
660
+ ----------
661
+ df : pandas DataFrame
662
+ DataFrame containing raw data from the BuckPy simulations.
663
+
664
+ Returns
665
+ -------
666
+ df : pandas DataFrame
667
+ DataFrame with converted units and renamed columns.
668
+ """
669
+
670
+ # Change labels for print-out
671
+ df = df.rename(columns = {
672
+ 'isim': 'Simulation Number',
673
+ 'KP': 'KP (m)',
674
+ 'route_type': 'Section Type',
675
+ 'muax': 'Axial Residual Friction Factor, Operation',
676
+ 'mulat_op': 'Lateral Breakout Friction Factor, Operation',
677
+ 'HOOS': 'HOOS Factor',
678
+ 'CBF_op': 'CBF Operation (kN)',
679
+ 'VAS_op': 'VAS Operation (m)'
680
+ })
681
+
682
+ # Convert units of the CBF (N to kN)
683
+ df['CBF Operation (kN)'] /= 1000.0
684
+
685
+ # Rename section types
686
+ df['Section Type'] = df['Section Type'].astype(object)
687
+ df.loc[df['Section Type'] == 1, 'Section Type'] = 'Straight'
688
+ df.loc[df['Section Type'] == 2, 'Section Type'] = 'Bend'
689
+ df.loc[df['Section Type'] == 3, 'Section Type'] = 'Sleeper'
690
+ df.loc[df['Section Type'] == 4, 'Section Type'] = 'RCM'
691
+
692
+ # Select the first 10000 rows to optimise the size of the Excel file
693
+ #TODO: why? if Excel is limited, let's export it to something else as it could be useful to have all case for separate post-processing
694
+ df = df.iloc[:10000]
695
+
696
+ return df
697
+
698
+ def pp_save(output_combination, output_file_name, *args):
699
+ """
700
+ Saves DataFrames to an Excel file with specified formatting.
701
+
702
+ Parameters
703
+ ----------
704
+ output_combination : Boolean
705
+ Switch to write the most frequent combination set of buckles in the result file.
706
+ output_file_name : str
707
+ Name of the output Excel file.
708
+ df_route : pandas DataFrame
709
+ DataFrame containing route data.
710
+ df_elem : pandas DataFrame
711
+ DataFrame containing element data.
712
+ df_sets : pandas DataFrame
713
+ DataFrame containing set data.
714
+ df_no_buckles : pandas DataFrame
715
+ DataFrame containing data related to the number of buckles.
716
+ df_pp_plot : pandas DataFrame
717
+ DataFrame containing force profile data.
718
+ df_pp_buckle_prop : pandas DataFrame
719
+ DataFrame containing raw data related to buckling properties.
720
+ df_comb_per_set : pandas DataFrame
721
+ DataFrame containing raw data related to KP set combinations based on post-processing set.
722
+ df_comb_per_section : pandas DataFrame
723
+ DataFrame containing raw data related to KP set combinations based on route point id.
724
+
725
+ Returns
726
+ -------
727
+ None
728
+ """
729
+
730
+ # Unpack DataFrames from args based on the output_combination flag
731
+ if output_combination:
732
+ df_route, df_elem, df_sets, df_no_buckles, df_pp_plot, df_pp_buckle_prop,\
733
+ df_comb_per_set, df_comb_per_section = args
734
+ else:
735
+ df_route, df_elem, df_sets, df_no_buckles, df_pp_plot, df_pp_buckle_prop = args
736
+
737
+ writer = pd.ExcelWriter(output_file_name)
738
+ pandas.io.formats.excel.ExcelFormatter.header_style = None
739
+
740
+ # Convert DataFrames to Excel objects
741
+ df_route.to_excel(
742
+ writer, sheet_name = 'Route', index = False, startrow = 1, header = False
743
+ )
744
+ df_elem.to_excel(
745
+ writer, sheet_name = 'Elements', index = False, startrow = 1, header = False
746
+ )
747
+ df_sets.to_excel(
748
+ writer, sheet_name = 'Sets', index = False, startrow = 1, header = False
749
+ )
750
+ df_no_buckles.to_excel(
751
+ writer, sheet_name = 'No Buckles', index = False, startrow = 1, header = False
752
+ )
753
+ df_pp_plot.to_excel(
754
+ writer, sheet_name = 'Force Profiles', index = False, startrow = 1, header = False
755
+ )
756
+ df_pp_buckle_prop.to_excel(
757
+ writer, sheet_name = 'Raw Data', index = False, startrow = 1, header = False
758
+ )
759
+
760
+ if output_combination:
761
+ df_comb_per_set.to_excel(
762
+ writer, sheet_name = 'Comb Buckles per Set', startrow = 1, header = False
763
+ )
764
+ df_comb_per_section.to_excel(
765
+ writer, sheet_name = 'Comb Buckles per Section', startrow = 1, header = False
766
+ )
767
+
768
+ # Get the workbook and worksheet objects.
769
+ workbook = writer.book
770
+ worksheet0 = writer.sheets['Route']
771
+ worksheet1 = writer.sheets['Elements']
772
+ worksheet2 = writer.sheets['Sets']
773
+ worksheet3 = writer.sheets['No Buckles']
774
+ worksheet4 = writer.sheets['Force Profiles']
775
+ worksheet5 = writer.sheets['Raw Data']
776
+
777
+ if output_combination:
778
+ worksheet6 = writer.sheets['Comb Buckles per Set']
779
+ worksheet7 = writer.sheets['Comb Buckles per Section']
780
+
781
+ # Add generic cell formats to Excel file
782
+ formatc1 = workbook.add_format({
783
+ 'num_format': '#,##0', 'align': 'center', 'valign': 'vcenter', 'text_wrap': True
784
+ })
785
+ formatc2 = workbook.add_format({
786
+ 'num_format': '#,##0.0', 'align': 'center', 'valign': 'vcenter', 'text_wrap': True
787
+ })
788
+ formatc3 = workbook.add_format({
789
+ 'num_format': '0.000', 'align': 'center', 'valign': 'vcenter', 'text_wrap': True
790
+ })
791
+ formath1 = workbook.add_format({
792
+ 'num_format': '#,###', 'bold': True, 'border': 1, 'bg_color': '#C0C0C0',
793
+ 'align': 'center', 'valign': 'vcenter', 'text_wrap': True
794
+ })
795
+
796
+ # Set the column width and format of the Excel worksheets
797
+ worksheet0.set_column('A:J', 12.5, formatc1)
798
+ worksheet0.set_column('K:L', 12.5, formatc2)
799
+ worksheet0.set_column('M:N', 12.5, formatc3)
800
+ worksheet0.set_column('O:V', 12.5, formatc1)
801
+
802
+ worksheet1.set_column('A:B', 12.5, formatc1)
803
+ worksheet1.set_column('C:D', 12.5, formatc3)
804
+ worksheet1.set_column('E:H', 12.5, formatc2)
805
+ worksheet1.set_column('I:L', 12.5, formatc3)
806
+
807
+ worksheet2.set_column('A:E', 12.5, formatc1)
808
+ worksheet2.set_column('F:G', 12.5, formatc3)
809
+ worksheet2.set_column('H:K', 12.5, formatc2)
810
+ worksheet2.set_column('L:L', 12.5, formatc3)
811
+ worksheet2.set_column('M:N', 12.5, formatc2)
812
+ worksheet2.set_column('O:U', 12.5, formatc3)
813
+
814
+ worksheet3.set_column('A:B', 12.5, formatc1)
815
+ worksheet3.set_column('C:D', 12.5, formatc3)
816
+
817
+ worksheet4.set_column('A:A', 12.5, formatc1)
818
+ worksheet4.set_column('B:H', 12.5, formatc2)
819
+
820
+ worksheet5.set_column('A:C', 12.5, formatc1)
821
+ worksheet5.set_column('D:F', 12.5, formatc3)
822
+ worksheet5.set_column('G:H', 12.5, formatc2)
823
+
824
+ if output_combination:
825
+ worksheet6.set_column('A:C', 12.5, formatc1)
826
+ worksheet6.set_column('D:D', 12.5, formatc3)
827
+ worksheet6.set_column('E:AZ', 12.5, formatc1)
828
+
829
+ worksheet7.set_column('A:C', 12.5, formatc1)
830
+ worksheet7.set_column('D:D', 12.5, formatc3)
831
+ worksheet7.set_column('E:AZ', 12.5, formatc1)
832
+
833
+ # Write the column hearders with the defined format
834
+ for col_num, value in enumerate(df_route.columns.values):
835
+ worksheet0.write(0, col_num, value, formath1)
836
+ for col_num, value in enumerate(df_elem.columns.values):
837
+ worksheet1.write(0, col_num, value, formath1)
838
+ for col_num, value in enumerate(df_sets.columns.values):
839
+ worksheet2.write(0, col_num, value, formath1)
840
+ for col_num, value in enumerate(df_no_buckles.columns.values):
841
+ worksheet3.write(0, col_num, value, formath1)
842
+ for col_num, value in enumerate(df_pp_plot.columns.values):
843
+ worksheet4.write(0, col_num, value, formath1)
844
+ for col_num, value in enumerate(df_pp_buckle_prop.columns.values):
845
+ worksheet5.write(0, col_num, value, formath1)
846
+
847
+ if output_combination:
848
+ for col_num, value in enumerate(df_comb_per_set.columns.values):
849
+ worksheet6.write(0, col_num + 1, value[0], formath1)
850
+ worksheet6.write(1, col_num + 1, value[1], formath1)
851
+ # Merge header cells for the 3 columns in the first and second row
852
+ if col_num <= 2:
853
+ worksheet6.merge_range(0, col_num + 1, 1, col_num + 1, value[1], formath1)
854
+ # Merge header cells for the 'Number of Buckles' columns in the first row
855
+ worksheet6.merge_range(
856
+ 0, 4, 0, len(df_comb_per_set.columns.values), df_comb_per_set.columns.levels[0][1], formath1
857
+ )
858
+
859
+ for col_num, value in enumerate(df_comb_per_section.columns.values):
860
+ worksheet7.write(0, col_num + 1, value[0], formath1)
861
+ worksheet7.write(1, col_num + 1, value[1], formath1)
862
+ # Merge header cells for the 3 columns in the first and second row
863
+ if col_num <= 2:
864
+ worksheet7.merge_range(0, col_num + 1, 1, col_num + 1, value[1], formath1)
865
+ # Merge header cells for the 'Number of Buckles' columns in the first row
866
+ worksheet7.merge_range(
867
+ 0, 4, 0, len(df_comb_per_section.columns.values),
868
+ df_comb_per_section.columns.levels[0][1], formath1
869
+ )
870
+
871
+ # Close the Excel writer and output the Excel file
872
+ writer.close()
873
+
874
+ def pp_outputs(output_file_name, n_sim, output_combination, df_pp_buckle_prop,
875
+ df_pp_set, df_pp_plot, prob_exceed_char_fric, df_scen, df_route):
876
+ """
877
+ Perform post-processing of analysis outputs.
878
+
879
+ Parameters
880
+ ----------
881
+ output_file_name : str
882
+ Name of the output Excel file
883
+ n_sim : int
884
+ Number of simulations.
885
+ output_combination : Boolean
886
+ Switch to write the most frequent combination set of buckles in the result file.
887
+ df_pp_buckle_prop : pandas DataFrame
888
+ DataFrame containing post-processed buckling properties.
889
+ df_pp_set : DataFrame
890
+ Definition of element sets for post-processing outputs.
891
+ df_pp_plot : pandas DataFrame
892
+ DataFrame containing post-processed plot data.
893
+ prob_exceed_char_fric : float
894
+ Probability of exceedance associated with the characteristic lateral breakout friction.
895
+ df_pp_set : DataFrame
896
+ Definition of element sets for post-processing outputs.
897
+ df_scen : DataFrame
898
+ Dataframe containing the design data along the pipeline
899
+ route (mesh) that remains constant among deterministic and
900
+ Monte-Carlo simulations.
901
+ df_route : DataFrame
902
+ Dataframe containing the route data of the pipeline.
903
+
904
+ Returns
905
+ -------
906
+ df_no_buckles : pandas DataFrame
907
+ DataFrame containing post-processed statistics about the number of buckles
908
+ along the pipeline.
909
+ df_sets : pandas DataFrame
910
+ DataFrame containing post-processed statistics grouped by pipeline sets.
911
+ """
912
+
913
+ # Dataframe containing the raw data
914
+ df_pp_buckle_prop = df_pp_buckle_prop.sort_values(by = ['KP', 'isim'])
915
+ df_pp = pd.merge_asof(left = df_pp_buckle_prop, right = df_pp_set,
916
+ left_on = 'KP', right_on = 'KP_from')
917
+
918
+ # Probability of buckling, VAS and lateral breakout friction sorted by element
919
+ df_elem = pp_elem(df_pp, n_sim)
920
+
921
+ # Distribution of the expected number of buckles along the pipeline
922
+ df_no_buckles = pp_no_buckles(df_pp, n_sim)
923
+
924
+ # Probability of buckling, VAS and lateral breakout friction sorted by post-processing set
925
+ df_sets = pp_sets(df_pp, n_sim, prob_exceed_char_fric, df_pp_set, df_scen)
926
+
927
+ # Post-processing of 'df_pp_plot' for print-out
928
+ df_pp_plot = pp_eaf(df_pp_plot)
929
+
930
+ # Post-processing of 'df_pp_buckle_prop' for print-out
931
+ df_pp_buckle_prop = pp_raw(df_pp_buckle_prop)
932
+
933
+ if output_combination:
934
+ # Calculate the most frequent combination of KP set with buckles
935
+ df_comb_per_set = pp_comb_buckles_per_set(df_pp, df_pp_set, n_sim)
936
+ df_comb_per_section = pp_comb_buckles_per_section(df_pp, df_scen, n_sim)
937
+
938
+ # Save key outputs to Excel file
939
+ pp_save(output_combination, output_file_name, df_route, df_elem, df_sets, df_no_buckles,
940
+ df_pp_plot, df_pp_buckle_prop, df_comb_per_set, df_comb_per_section)
941
+ else:
942
+ # Save key outputs to Excel file
943
+ pp_save(output_combination, output_file_name, df_route, df_elem, df_sets, df_no_buckles,
944
+ df_pp_plot, df_pp_buckle_prop)
945
+
946
+ return df_no_buckles, df_sets
947
+
948
+ def pp_plots(plot_file_name, df_scen, df_plot, df_vap_plot, df_no_buckles, df_sets,
949
+ prob_exceed_char_fric):
950
+ """
951
+ Plot deterministic and probabilistic results
952
+
953
+ Parameters
954
+ ----------
955
+ plot_file_name : str
956
+ Name of the file
957
+ df_scen : DataFrame
958
+ Dataframe containing the design data along the pipeline
959
+ route (mesh) that remains constant among deterministic and
960
+ Monte-Carlo simulations.
961
+ df_plot : DataFrame
962
+ Definition on assessed mesh of CBF and EAF in different conditions.
963
+ for case to plot
964
+ df_vap_plot : DataFrame
965
+ Definition of virtual anchor points for case to plot.
966
+ Columns: ['ielt VAP', 'KP VAP', 'ESF VAP'].
967
+ df_no_buckles : DataFrame
968
+ Probability of number of buckles over pipeline.
969
+ df_sets : DataFrame
970
+ Probability of buckling and characteristic VAS and friction factors by set.
971
+ prob_exceed_char_fric : float
972
+ Probability of exceedance for characteristic friction factors.
973
+ plot_file_name : str
974
+ File name where plots are to be saved.
975
+ """
976
+
977
+ # Convert the units of 'df_plot', 'df_vap_plot' & 'df_scen' (m to km and N to kN)
978
+ df_plot[['KP']] /= 1000.0
979
+ df_vap_plot[['KP VAP', 'ESF VAP']] /= 1000.0
980
+ df_scen[['KP', 'FRF OP Pressure', 'FRF OP Temperature']] /= 1000.0
981
+
982
+ # Create arrays to plot buckling probability, characteristic VAS and characteristic friction
983
+ np_kp = np.array([df_sets.iloc[0]['KP From (m)'] / 1000.0])
984
+ np_prob = np.array([0.0])
985
+ np_vas_cond = np.array([0.0])
986
+ np_vas_uncond = np.array([0.0])
987
+ np_mul_buckle = np.array([0.0])
988
+ for index, row in df_sets.iterrows():
989
+ np_kp = np.append(np_kp, np.append(
990
+ row['KP From (m)'] / 1000.0,
991
+ row['KP To (m)'] / 1000.0))
992
+ np_prob = np.append(np_prob, np.append(
993
+ 100.0 * row['Probability of Buckling'],
994
+ 100.0 * row['Probability of Buckling']))
995
+ np_vas_cond = np.append(np_vas_cond, np.append(
996
+ row['Characteristic VAS, Conditional (m)'],
997
+ row['Characteristic VAS, Conditional (m)']))
998
+ np_vas_uncond = np.append(np_vas_uncond, np.append(
999
+ row['Characteristic VAS, Unconditional (m)'],
1000
+ row['Characteristic VAS, Unconditional (m)']))
1001
+ np_mul_buckle = np.append(np_mul_buckle, np.append(
1002
+ row['Characteristic Lateral Breakout Friction, Buckles'],
1003
+ row['Characteristic Lateral Breakout Friction, Buckles']))
1004
+ if index == df_sets.shape[0] - 1:
1005
+ np_kp = np.append(np_kp, row['KP To (m)'] / 1000.0)
1006
+ np_prob = np.append(np_prob, 0.0)
1007
+ np_vas_cond = np.append(np_vas_cond, 0.0)
1008
+ np_vas_uncond = np.append(np_vas_uncond, 0.0)
1009
+ np_mul_buckle = np.append(np_mul_buckle, 0.0)
1010
+
1011
+ # Create an array to plot the HE of the geotechnical lateral breakdown friction
1012
+ np_mul_kp = np.empty(0)
1013
+ np_mul_geotech = np.empty(0)
1014
+ for index, row in df_scen.iterrows():
1015
+ # df_scen has at least 2 points, no need to double the points in append functions
1016
+ np_mul_kp = np.append(np_mul_kp, row['KP'])
1017
+ mul_geotech = np.interp(1.0 - prob_exceed_char_fric,
1018
+ row['mul OP CDF Array'], row['mul OP Array'])
1019
+ np_mul_geotech = np.append(np_mul_geotech, mul_geotech)
1020
+
1021
+ # Generate matplolib figure
1022
+ fig = plt.figure()
1023
+ dpi_size = 110
1024
+ fig.set_size_inches(19.2 * 100 / dpi_size, 10.8 * 100 / dpi_size)
1025
+ gs = GridSpec(nrows = 2, ncols = 3)
1026
+
1027
+ # Plot effective axial force profiles from selected case
1028
+ a1=fig.add_subplot(gs[0, :])
1029
+ a1.plot(df_plot['KP'], df_plot['EAF_inst'],
1030
+ label = 'EAF Installation', color = 'C1')
1031
+ a1.plot(df_plot['KP'], df_plot['EAF_ht'],
1032
+ label = 'EAF Hydrotest', color = 'C2')
1033
+ a1.plot(df_plot['KP'], df_plot['EAF_p_op'],
1034
+ label = 'EAF Operation (Pressure Only)', color = 'C3')
1035
+ a1.plot(df_plot['KP'], df_plot['EAF_op'],
1036
+ label = 'EAF Operation', color = 'C4')
1037
+ a1.plot(df_plot['KP'], df_plot['EAF_op_unbuck'],
1038
+ label = 'EAF Operation (without Buckling)', color = 'C4', linestyle = ':')
1039
+
1040
+ # Plot intermediate force profile during temperature application
1041
+ for i in range(1, 21):
1042
+ # Shouldnt overwrite existing df_plot['EAF_inst'], separate dataframe used instead
1043
+ eaf_interim = df_scen['FRF OP Pressure'] + (i / 20) * df_scen['FRF OP Temperature']
1044
+ eaf_interim=np.where(eaf_interim < df_plot['EAF_op_unbuck'].to_numpy(), eaf_interim, np.nan)
1045
+ a1.plot(df_plot['KP'], eaf_interim, color = 'C4', linestyle = '--', alpha = 0.25)
1046
+
1047
+ #TODO: Indeed, however different sections (routes, straight, sleepers...) can have different ones
1048
+ # and it's interresting to see if area buckle "earlier" than other
1049
+ #? Block commented as all buckling forces per section are constant in the deterministic case
1050
+ # Plot buckling susceptibility areas and actual buckle locations
1051
+ # a1.scatter(df_plot.loc[
1052
+ # df_plot['CBF_op'] < df_plot['EAF_op_unbuck'], 'KP'],
1053
+ # df_plot.loc[
1054
+ # df_plot['CBF_op'] < df_plot['EAF_op_unbuck'], 'CBF_op'],
1055
+ # label = 'CBF operation', marker = '.', color = 'C4')
1056
+
1057
+ # Plot VAP
1058
+ a1.scatter(df_vap_plot['KP VAP'], df_vap_plot['ESF VAP'],
1059
+ marker = '8', c = 'none', edgecolors = 'C3', label = 'VAP')
1060
+ a1.set_xlabel('KP [km]')
1061
+ a1.set_ylabel('Effective Force [kN]')
1062
+ a1.legend()
1063
+ a1.grid()
1064
+
1065
+ # Plot distribution of number of buckles
1066
+ a2 = fig.add_subplot(gs[1, 0])
1067
+ a2.plot(df_no_buckles['Number of Buckles'],
1068
+ 100.0 * df_no_buckles['Probability of Buckling'], color = 'C1')
1069
+ a2.set_xlabel('Number of Buckles')
1070
+ a2.set_ylabel('Probability [%]')
1071
+ a2.grid()
1072
+
1073
+ # Plot lateral friction factor versus location
1074
+ a3 = fig.add_subplot(gs[1, 1])
1075
+ a3.plot(np_kp, np_mul_buckle,
1076
+ label = f'P{int(100 * prob_exceed_char_fric)} Buckle Friction', color = 'C1')
1077
+ a3.plot(np_mul_kp, np_mul_geotech,
1078
+ label = f'P{int(100 * prob_exceed_char_fric)} Geotech Friction', color = 'C2')
1079
+ a3.set_xlabel('KP [km]')
1080
+ a3.set_ylabel('Lateral Breakout Friction Factor (Operation)')
1081
+ a3.legend()
1082
+ a3.grid()
1083
+
1084
+ # Plot probability of buckling and characteristic VAS
1085
+ a4 = fig.add_subplot(gs[1, 2])
1086
+ a4_twin = a4.twinx()
1087
+ # a4.plot(np_kp, np_vas_cond, label = 'Conditional VAS',
1088
+ # color = 'C1')
1089
+ a4.plot(np_kp, np_vas_uncond, label = 'Unconditional VAS',
1090
+ color = 'C2')
1091
+ a4_twin.plot(np_kp, np_prob, label = 'Probability of Buckling', linestyle = ':',
1092
+ color = 'C3')
1093
+ a4.set_xlabel('KP [km]')
1094
+ a4.set_ylabel('Characteristic VAS [m]')
1095
+ a4_twin.set_ylabel('Buckling Probability [%]')
1096
+ line4, label4 = a4.get_legend_handles_labels()
1097
+ line4_twin, label4_twin = a4_twin.get_legend_handles_labels()
1098
+ a4.legend(line4 + line4_twin, label4 + label4_twin)
1099
+ a4.grid()
1100
+
1101
+ fig_manager=plt.get_current_fig_manager()
1102
+ try:
1103
+ fig_manager.window.state('zoomed')
1104
+ except AttributeError:
1105
+ pass
1106
+ try:
1107
+ fig_manager.window.showMaximized()
1108
+ except AttributeError:
1109
+ pass
1110
+ plt.subplots_adjust(
1111
+ left = 0.1, bottom = 0.1, right = 0.95, top = 0.925, wspace = 0.225, hspace = 0.2)
1112
+ plt.savefig(plot_file_name, dpi = dpi_size)
1113
+ # plt.show()
1114
+ plt.close()
1115
+
1116
+ def replace_route_sheet(work_dir, input_file_name, df_scen, df_route):
1117
+ """
1118
+ Replace the Route sheet in the Excel file with a new dataframe.
1119
+
1120
+ Parameters
1121
+ ----------
1122
+ work_dir : str
1123
+ Directory where the input file is located.
1124
+ input_file_name : str
1125
+ Name of the input Excel file.
1126
+ df_scen : DataFrame
1127
+ DataFrame containing the design data along the pipeline
1128
+ route (mesh) that remains constant among deterministic and
1129
+ Monte-Carlo simulations.
1130
+ df_route : DataFrame
1131
+ DataFrame containing the new Route data.
1132
+
1133
+ Returns
1134
+ -------
1135
+ None
1136
+ """
1137
+
1138
+ # Extract key scenario information from 'df_scen'
1139
+ pipeline_id = df_scen["Pipeline"].values[0]
1140
+ scenario_no = df_scen["Scenario"].values[0]
1141
+ route_layout = df_scen["Layout Set"].values[0]
1142
+
1143
+ # Define input and output file paths
1144
+ input_path = Path(work_dir) / input_file_name
1145
+ output_name = f"{Path(input_file_name).stem}_{pipeline_id}_scen{scenario_no}_buckfast.xlsx"
1146
+ output_path = Path(work_dir) / output_name
1147
+
1148
+ # Create the output workbook from the input file, then remove old sheets
1149
+ workbook = load_workbook(input_path)
1150
+ for sheet_name in ["Route", "Mitigation", "Soil Zoning"]:
1151
+ if sheet_name in workbook.sheetnames:
1152
+ del workbook[sheet_name]
1153
+ workbook.save(output_path)
1154
+
1155
+ # Write the new Route sheet
1156
+ with pd.ExcelWriter(
1157
+ output_path,
1158
+ engine="openpyxl",
1159
+ mode="a",
1160
+ if_sheet_exists="replace",
1161
+ ) as writer:
1162
+ # Load the original Route sheet to get the first and last rows
1163
+ path = Path(work_dir) / input_file_name
1164
+ df_route_sheet = pd.read_excel(path, sheet_name="Route")
1165
+ df_filtered = df_route_sheet.loc[
1166
+ (df_route_sheet["Pipeline"] == pipeline_id) &
1167
+ (df_route_sheet["Layout Set"] == route_layout)
1168
+ ].copy()
1169
+ first_row = df_filtered.iloc[0].reindex(df_route.columns)
1170
+ last_row = df_filtered.iloc[-1].reindex(df_route.columns)
1171
+ df_out = pd.concat(
1172
+ [first_row.to_frame().T, df_route, last_row.to_frame().T],
1173
+ ignore_index=True
1174
+ )
1175
+ df_out.to_excel(writer, sheet_name="Route", index=False)
1176
+ # Apply formatting to the Route sheet using openpyxl
1177
+ from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
1178
+ from openpyxl.utils import get_column_letter
1179
+ # Apply formatting to the Route sheet
1180
+ wb = writer.book
1181
+ ws = wb["Route"]
1182
+ target_index = 1 # 0-based: second tab
1183
+ current_index = wb.worksheets.index(ws)
1184
+ if current_index != target_index:
1185
+ wb.move_sheet(ws, offset=target_index - current_index)
1186
+ # Define cell formats for the header and data cells
1187
+ header_font = Font(bold=True)
1188
+ header_fill = PatternFill(fill_type="solid", fgColor="C0C0C0")
1189
+ header_border = Border(
1190
+ left=Side(style="thin"), right=Side(style="thin"),
1191
+ top=Side(style="thin"), bottom=Side(style="thin")
1192
+ )
1193
+ cell_alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
1194
+ for cell in ws[1]:
1195
+ cell.font = header_font
1196
+ cell.fill = header_fill
1197
+ cell.border = header_border
1198
+ cell.alignment = cell_alignment
1199
+ # Set the column width and format of the Route sheet
1200
+ def format_cols(start_col, end_col, num_fmt):
1201
+ for col in range(start_col, end_col + 1):
1202
+ col_letter = get_column_letter(col)
1203
+ ws.column_dimensions[col_letter].width = 12
1204
+ for row in range(2, ws.max_row + 1):
1205
+ c = ws.cell(row=row, column=col)
1206
+ c.number_format = num_fmt
1207
+ c.alignment = cell_alignment
1208
+ # Apply specific number formats to columns
1209
+ format_cols(1, 10, "#,##0") # A:J
1210
+ format_cols(11, 12, "#,##0.0") # K:L
1211
+ format_cols(13, 14, "0.000") # M:N
1212
+ format_cols(15, 22, "#,##0") # O:V
1213
+
1214
+ def pp_buckpy(work_dir, input_file_name, df_scen, df_route, df_pp_set, df_pp_plot, df_vap_plot, df_pp_buckle_prop, output_combination, bl_verbose, excel_format):
1215
+ """
1216
+ Post-processing of probabilistic buckling results.
1217
+
1218
+ Parameters
1219
+ ----------
1220
+ work_dir : str
1221
+ Directory where the output files are saved.
1222
+ input_file_name : str
1223
+ Name of the input file.
1224
+ df_scen : DataFrame
1225
+ Dataframe containing the design data along the pipeline
1226
+ route (mesh) that remains constant among deterministic and
1227
+ Monte-Carlo simulations.
1228
+ df_route : DataFrame
1229
+ Dataframe containing the route data of the pipeline.
1230
+ df_pp_set : DataFrame
1231
+ Definition of element sets for post-processing outputs.
1232
+ df_pp_plot : DataFrame
1233
+ DataFrame containing post-processed plot data.
1234
+ df_vap_plot : DataFrame
1235
+ DataFrame containing post-processed virtual anchor point data.
1236
+ df_pp_buckle_prop : pandas DataFrame
1237
+ DataFrame containing post-processed buckling properties.
1238
+ output_combination : Boolean
1239
+ Switch to write the most frequent combination set of buckles in the result file.
1240
+ bl_verbose : Boolean
1241
+ Switch to print in the terminal the time taken to post-process results.
1242
+ excel_format : Boolean
1243
+ Switch to write outputs in Excel format with Legacy or Current.
1244
+
1245
+ Returns
1246
+ -------
1247
+ None
1248
+ """
1249
+
1250
+ # Convert 'output_combination' to boolean if it is a string
1251
+ if isinstance(output_combination, str):
1252
+ output_combination = output_combination.strip().lower() in {"true", "1", "yes", "y", "on"}
1253
+ else:
1254
+ output_combination = bool(output_combination)
1255
+
1256
+ # Extract key scenario information from 'df_scen'
1257
+ scenario_no = df_scen["Scenario"].values[0]
1258
+ pipeline_id = df_scen["Pipeline"].values[0]
1259
+ prob_exceed_char_fric = df_scen["Char. Friction Prob."].values[0]
1260
+ n_sim = df_scen["Simulations"].values[0]
1261
+
1262
+ # Starting time of the post-processing module
1263
+ start_time = time.time()
1264
+
1265
+ # Print in the terminal that the post-processing of the results has started
1266
+ if bl_verbose:
1267
+ print("4. Post-process results")
1268
+
1269
+ # Calculate probabilistic outputs and save outputs to Excel file
1270
+ output_file_name = (
1271
+ f"{work_dir}/{input_file_name.split('.')[0]}_{pipeline_id}_scen{scenario_no}_outputs.xlsx"
1272
+ )
1273
+ df_prob_n_buckle, df_prob_set = pp_outputs(
1274
+ output_file_name,
1275
+ n_sim,
1276
+ output_combination,
1277
+ df_pp_buckle_prop,
1278
+ df_pp_set,
1279
+ df_pp_plot,
1280
+ prob_exceed_char_fric,
1281
+ df_scen,
1282
+ df_route
1283
+ )
1284
+
1285
+ # Print in the terminal the time taken to post-process results
1286
+ if bl_verbose:
1287
+ print(f' Time taken to post-process results: {time.time() - start_time:.1f}s')
1288
+
1289
+ # Plot post-processed results and save figure to file
1290
+ plot_file_name = (
1291
+ f"{work_dir}/{input_file_name.split('.')[0]}_{pipeline_id}_scen{scenario_no}_plots-1.png"
1292
+ )
1293
+ pp_plots(
1294
+ plot_file_name,
1295
+ df_scen,
1296
+ df_pp_plot,
1297
+ df_vap_plot,
1298
+ df_prob_n_buckle,
1299
+ df_prob_set,
1300
+ prob_exceed_char_fric
1301
+ )
1302
+
1303
+ # Replace the Route sheet in the Excel file with the new dataframe
1304
+ if excel_format == "Current":
1305
+ replace_route_sheet(work_dir, input_file_name, df_scen, df_route)