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.
- buckpy/__init__.py +13 -0
- buckpy/_static/logo.png +0 -0
- buckpy/_static/logo.svg +4 -0
- buckpy/buckfast_input_file_writer_.py +1233 -0
- buckpy/buckpy.py +132 -0
- buckpy/buckpy_gui.py +359 -0
- buckpy/buckpy_postprocessing.py +1305 -0
- buckpy/buckpy_preprocessing_current.py +1142 -0
- buckpy/buckpy_preprocessing_legacy.py +900 -0
- buckpy/buckpy_solver.py +777 -0
- buckpy/buckpy_variables.py +98 -0
- buckpy/buckpy_visualisation.py +419 -0
- buckpy_dev-0.0.1.dist-info/METADATA +51 -0
- buckpy_dev-0.0.1.dist-info/RECORD +18 -0
- buckpy_dev-0.0.1.dist-info/WHEEL +5 -0
- buckpy_dev-0.0.1.dist-info/entry_points.txt +2 -0
- buckpy_dev-0.0.1.dist-info/licenses/LICENSE +674 -0
- buckpy_dev-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the pre-processing functions of BuckPy.
|
|
3
|
+
"""
|
|
4
|
+
import time
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from scipy.stats import lognorm
|
|
8
|
+
import pysubsea as ss
|
|
9
|
+
|
|
10
|
+
def calc_lognorm_hoos(type_elt, length_elt, hoos_mean, hoos_std, length_ref, rcm_charac):
|
|
11
|
+
"""
|
|
12
|
+
Compute the parameters of the horizontal out-of-straightness (HOOS) lognormal distribution
|
|
13
|
+
for different types of elements (e.g., Straight, Bend, Sleeper, RCM). This function takes into
|
|
14
|
+
account the scaling factor of the HOOS distribution. For RCM, the HOOS factor is not a factor
|
|
15
|
+
but the critical buckling force.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
type_elt : str
|
|
20
|
+
Type of the element.
|
|
21
|
+
length_elt : float
|
|
22
|
+
Length of the element.
|
|
23
|
+
hoos_mean : float
|
|
24
|
+
Mean of the HOOS distribution.
|
|
25
|
+
hoos_std : float
|
|
26
|
+
Standard deviation of the HOOS distribution.
|
|
27
|
+
length_ref : float
|
|
28
|
+
Reference length.
|
|
29
|
+
rcm_charac : float
|
|
30
|
+
Characteristic buckling force for the Residual Curvature Method (RCM).
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
x_range : numpy.ndarray
|
|
35
|
+
An array of values representing the range of the friction factor distribution
|
|
36
|
+
between probabilities of exceedance between 0.01% and 99.99%.
|
|
37
|
+
cdf_range : numpy.ndarray
|
|
38
|
+
An array of cumulative density function (CDF) values corresponding to `x_range`.
|
|
39
|
+
|
|
40
|
+
Notes
|
|
41
|
+
-----
|
|
42
|
+
This function computes the parameters of a lognormal distribution for different types of
|
|
43
|
+
elements such as Straight, Bend, Sleeper, and RCM (Residual Curvature Method). It
|
|
44
|
+
calculates the cumulative density function (CDF) for the generated range of values
|
|
45
|
+
based on the HOOS distribution parameters.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# Extract the type of element (e.g., Straight, Bend, Sleeper, RCM)
|
|
50
|
+
type_elt_split = type_elt.split(" ")[0]
|
|
51
|
+
|
|
52
|
+
# Compute the ratio of the reference length to the element length
|
|
53
|
+
n = length_ref / length_elt
|
|
54
|
+
|
|
55
|
+
if type_elt_split == "Straight" or type_elt_split == "Bend":
|
|
56
|
+
|
|
57
|
+
# Calculate parameters for straight or bend elements
|
|
58
|
+
shape_hoos = np.sqrt(np.log(1 + hoos_std**2 / hoos_mean**2))
|
|
59
|
+
scale_hoos = np.log(hoos_mean**2 / (np.sqrt(hoos_mean**2 + hoos_std**2)))
|
|
60
|
+
|
|
61
|
+
# Define the range of the HOOS distribution
|
|
62
|
+
hoos_lower = 0.0
|
|
63
|
+
hoos_upper = 20.0
|
|
64
|
+
x = np.linspace(hoos_lower, hoos_upper, 200000)
|
|
65
|
+
|
|
66
|
+
# Calculate the cumulative density function (CDF) considering the scaling factor
|
|
67
|
+
cdf = 1-(1-lognorm.cdf(x, shape_hoos, 0.0, np.exp(scale_hoos)))**(1/n)
|
|
68
|
+
|
|
69
|
+
# Generate a range of CDF values
|
|
70
|
+
cdf_range = np.arange(0.0, 1.0, 0.0001)
|
|
71
|
+
|
|
72
|
+
# Interpolate to get the corresponding values of the distribution
|
|
73
|
+
x_range = np.interp(cdf_range, cdf, x)
|
|
74
|
+
|
|
75
|
+
elif type_elt_split == "Sleeper":
|
|
76
|
+
|
|
77
|
+
# Calculate parameters for sleeper elements
|
|
78
|
+
shape_hoos = np.sqrt(np.log(1 + hoos_std**2 / hoos_mean**2))
|
|
79
|
+
scale_hoos = np.log(hoos_mean**2 / (np.sqrt(hoos_mean**2 + hoos_std**2)))
|
|
80
|
+
|
|
81
|
+
# Calculate the lower and upper bounds of the distribution for sleeper elements
|
|
82
|
+
hoos_lower = lognorm(shape_hoos, 0.0, np.exp(scale_hoos)).ppf(0.0001)
|
|
83
|
+
hoos_upper = lognorm(shape_hoos, 0.0, np.exp(scale_hoos)).ppf(0.9999)
|
|
84
|
+
|
|
85
|
+
# Generate a range of values within the distribution
|
|
86
|
+
x_range = np.linspace(hoos_lower, hoos_upper, 10000)
|
|
87
|
+
|
|
88
|
+
# Compute the cumulative density function (CDF) for the generated range
|
|
89
|
+
cdf_range = lognorm.cdf(x_range, shape_hoos, 0.0, np.exp(scale_hoos))
|
|
90
|
+
|
|
91
|
+
elif type_elt_split == "RCM":
|
|
92
|
+
|
|
93
|
+
# Calculate parameters for RCM elements
|
|
94
|
+
shape_hoos = np.sqrt(np.log(1 + hoos_std**2 / hoos_mean**2))
|
|
95
|
+
scale_hoos = np.log(hoos_mean**2 / (np.sqrt(hoos_mean**2 + hoos_std**2)))
|
|
96
|
+
scale_hoos = scale_hoos + np.log(rcm_charac)
|
|
97
|
+
|
|
98
|
+
# Calculate the lower and upper bounds of the distribution for RCM elements
|
|
99
|
+
hoos_lower = lognorm(shape_hoos, 0.0, np.exp(scale_hoos)).ppf(0.0001)
|
|
100
|
+
hoos_upper = lognorm(shape_hoos, 0.0, np.exp(scale_hoos)).ppf(0.9999)
|
|
101
|
+
|
|
102
|
+
# Generate a range of values within the distribution
|
|
103
|
+
x_range = np.linspace(hoos_lower, hoos_upper, 10000)
|
|
104
|
+
|
|
105
|
+
# Compute the cumulative density function (CDF) for the generated range
|
|
106
|
+
cdf_range = lognorm.cdf(x_range, shape_hoos, 0.0, np.exp(scale_hoos))
|
|
107
|
+
|
|
108
|
+
return x_range, cdf_range
|
|
109
|
+
|
|
110
|
+
class PreProcessor:
|
|
111
|
+
"""
|
|
112
|
+
Class to handle the pre-processing of scenario data for BuckPy simulations. This class reads
|
|
113
|
+
scenario data from an Excel file, extracts and processes route, pipe, operating, and soil data,
|
|
114
|
+
and calculates scenario data. It also converts the scenario data and end boundary conditions
|
|
115
|
+
to NumPy arrays for Monte Carlo simulations and processes post-processing data.
|
|
116
|
+
|
|
117
|
+
The class includes methods for calculating expanded KP values, creating element arrays,
|
|
118
|
+
interpolating distributions, and handling various preprocessing tasks.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(self, work_dir, file_name, pipeline, scenario, bl_verbose):
|
|
122
|
+
"""
|
|
123
|
+
Method to initialize the PreProcessor class with the necessary parameters and attributes.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
work_dir : str
|
|
128
|
+
Directory where the Excel file is located.
|
|
129
|
+
file_name : str
|
|
130
|
+
Name of the Excel file.
|
|
131
|
+
pipeline : str
|
|
132
|
+
Identifier of the pipeline.
|
|
133
|
+
scenario : int
|
|
134
|
+
Identifier of the scenario.
|
|
135
|
+
bl_verbose : bool
|
|
136
|
+
True if intermediate printouts are required (False by default).
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
None
|
|
141
|
+
"""
|
|
142
|
+
# Initialize attributes for data storage
|
|
143
|
+
self.work_dir = work_dir
|
|
144
|
+
self.file_name = file_name
|
|
145
|
+
self.pipeline = pipeline
|
|
146
|
+
self.scenario = scenario
|
|
147
|
+
self.bl_verbose = bl_verbose
|
|
148
|
+
|
|
149
|
+
# Initialize attributes for storing dataframes and arrays
|
|
150
|
+
self.scen_df = None
|
|
151
|
+
self.route_df = None
|
|
152
|
+
self.route_ends_df = None
|
|
153
|
+
self.mitigation_df = None
|
|
154
|
+
self.soil_zoning_df = None
|
|
155
|
+
self.pipe_df = None
|
|
156
|
+
self.soil_df = None
|
|
157
|
+
self.oper_df = None
|
|
158
|
+
self.pp_df = None
|
|
159
|
+
|
|
160
|
+
# Initialize attributes for storing NumPy arrays used in Monte Carlo simulations
|
|
161
|
+
self.scen_np = None
|
|
162
|
+
self.dist_np = None
|
|
163
|
+
self.ends_np = None
|
|
164
|
+
|
|
165
|
+
def run(self):
|
|
166
|
+
"""
|
|
167
|
+
Import scenario data from an Excel file and preprocess it.
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
work_dir : str
|
|
172
|
+
Directory where the Excel file is located.
|
|
173
|
+
file_name : str
|
|
174
|
+
Name of the Excel file.
|
|
175
|
+
pipeline : str
|
|
176
|
+
Identifier of the pipeline.
|
|
177
|
+
scenario : int
|
|
178
|
+
Identifier of the scenario.
|
|
179
|
+
bl_verbose : bool, optional
|
|
180
|
+
True if intermediate printouts are required.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
scen_np : numpy.ndarray
|
|
185
|
+
NumPy array containing the scenario data for Monte Carlo simulations.
|
|
186
|
+
dist_np : numpy.ndarray
|
|
187
|
+
NumPy array containing the distribution data for Monte Carlo simulations.
|
|
188
|
+
ends_np : numpy.ndarray
|
|
189
|
+
NumPy array containing the end boundary conditions for Monte Carlo simulations.
|
|
190
|
+
scen_df : pandas.DataFrame
|
|
191
|
+
DataFrame containing the scenario data for deterministic simulations.
|
|
192
|
+
pp_df : pandas.DataFrame
|
|
193
|
+
DataFrame containing the post-processing data for the scenario.
|
|
194
|
+
|
|
195
|
+
Notes
|
|
196
|
+
-----
|
|
197
|
+
This function reads scenario data from an Excel file, extracts and processes route,
|
|
198
|
+
pipe, operating, and soil data, and calculates scenario data.
|
|
199
|
+
It also converts the scenario data and end boundary conditions to NumPy arrays for
|
|
200
|
+
Monte Carlo simulations and processes post-processing data.
|
|
201
|
+
The function prints out the time taken to create the main dataframe
|
|
202
|
+
if bl_verbose is set to True.
|
|
203
|
+
|
|
204
|
+
Other Parameters
|
|
205
|
+
----------------
|
|
206
|
+
bl_verbose : boolean, optional
|
|
207
|
+
True if intermediate printouts are required (False by default).
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
# Print out in the terminal that the assembly of the main dataframe has started
|
|
211
|
+
if self.bl_verbose:
|
|
212
|
+
print("1. Assembly of the main dataframe")
|
|
213
|
+
|
|
214
|
+
# Starting time of the pre-processing module
|
|
215
|
+
start_time = time.time()
|
|
216
|
+
|
|
217
|
+
# Read data from the input Excel file
|
|
218
|
+
sheets = pd.read_excel(rf"{self.work_dir}/{self.file_name}", sheet_name=None)
|
|
219
|
+
self.scen_df = sheets["Scenario"]
|
|
220
|
+
self.route_df = sheets["Route"]
|
|
221
|
+
self.mitigation_df = sheets["Mitigation"]
|
|
222
|
+
self.soil_zoning_df = sheets["Soil Zoning"]
|
|
223
|
+
self.pipe_df = sheets["Pipe"]
|
|
224
|
+
self.soil_df = sheets["Soils"]
|
|
225
|
+
self.oper_df = sheets["Operating"]
|
|
226
|
+
self.pp_df = sheets["Post-Processing"]
|
|
227
|
+
|
|
228
|
+
# Filter scenario dataframe based on pipeline and scenario
|
|
229
|
+
self.scen_df = self.scen_df.loc[
|
|
230
|
+
(self.scen_df["Pipeline"] == self.pipeline) &
|
|
231
|
+
(self.scen_df["Scenario"] == self.scenario)
|
|
232
|
+
].copy()
|
|
233
|
+
|
|
234
|
+
# Extract simulation parameters from the scenario dataframe
|
|
235
|
+
layout = self.scen_df["Layout Set"].values[0]
|
|
236
|
+
mitigation = self.scen_df["Mitigation Set"].values[0]
|
|
237
|
+
loadcase = self.scen_df["Loadcase Set"].values[0]
|
|
238
|
+
|
|
239
|
+
# Filter route data based on layout
|
|
240
|
+
self.route_df = self.route_df.loc[
|
|
241
|
+
(self.route_df["Pipeline"] == self.pipeline) &
|
|
242
|
+
(self.route_df["Layout Set"] == layout)
|
|
243
|
+
].copy()
|
|
244
|
+
# Ensure mitigation-driven columns exist on route rows before segmentation
|
|
245
|
+
for col in ["Sleeper Height", "RCM Buckling Force"]:
|
|
246
|
+
if col not in self.route_df.columns:
|
|
247
|
+
self.route_df[col] = np.nan
|
|
248
|
+
self.route_df[["KP From", "KP To"]] = (
|
|
249
|
+
self.route_df[["KP From", "KP To"]].astype(float)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Filter mitigation data based on mitigation
|
|
253
|
+
self.mitigation_df = self.mitigation_df.loc[
|
|
254
|
+
(self.mitigation_df["Pipeline"] == self.pipeline) &
|
|
255
|
+
(self.mitigation_df["Mitigation Set"] == mitigation)
|
|
256
|
+
].copy()
|
|
257
|
+
self.mitigation_df[["KP From", "KP To", "Sleeper Height", "RCM Buckling Force"]] = (
|
|
258
|
+
self.mitigation_df[["KP From", "KP To", "Sleeper Height", "RCM Buckling Force"]]
|
|
259
|
+
.astype(float)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Filter soil zoning data based on soil zoning
|
|
263
|
+
self.soil_zoning_df = self.soil_zoning_df.loc[
|
|
264
|
+
(self.soil_zoning_df["Pipeline"] == self.pipeline) &
|
|
265
|
+
(self.soil_zoning_df["Route Layout"] == layout)
|
|
266
|
+
].copy()
|
|
267
|
+
self.soil_zoning_df[["KP From", "KP To"]] = (
|
|
268
|
+
self.soil_zoning_df[["KP From", "KP To"]].astype(float)
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Postprocess route data based on route, mitigation and soil zoning data
|
|
272
|
+
self.calc_route_data()
|
|
273
|
+
|
|
274
|
+
# Postprocess pipe data and calculate pipe properties
|
|
275
|
+
self.pipe_df = self.pipe_df.loc[
|
|
276
|
+
(self.pipe_df["Pipeline"] == self.pipeline)
|
|
277
|
+
].copy()
|
|
278
|
+
self.calc_pipe_data()
|
|
279
|
+
|
|
280
|
+
# Postprocess soil data and calculate friction factor distributions
|
|
281
|
+
self.soil_df = self.soil_df.loc[
|
|
282
|
+
(self.soil_df["Pipeline"] == self.pipeline)
|
|
283
|
+
].copy()
|
|
284
|
+
self.calc_soil_data()
|
|
285
|
+
|
|
286
|
+
# Postprocess operating data and calculate operating profiles and operating data
|
|
287
|
+
self.oper_df = self.oper_df.loc[
|
|
288
|
+
(self.oper_df["Pipeline"] == self.pipeline) &
|
|
289
|
+
(self.oper_df["Loadcase Set"] == loadcase)
|
|
290
|
+
].copy()
|
|
291
|
+
self.calc_oper_data()
|
|
292
|
+
|
|
293
|
+
# Postprocess scenario data
|
|
294
|
+
self.calc_scenario_data()
|
|
295
|
+
|
|
296
|
+
# Define the NumPy arrays used in the Monte Carlo Simulations
|
|
297
|
+
self.calc_monte_carlo_data()
|
|
298
|
+
|
|
299
|
+
# Process post-processing data based on pipeline, layout and mitigation
|
|
300
|
+
mask = (
|
|
301
|
+
(self.pp_df["Pipeline"] == self.pipeline) &
|
|
302
|
+
(self.pp_df["Layout Set"] == layout)
|
|
303
|
+
)
|
|
304
|
+
if pd.isna(mitigation):
|
|
305
|
+
mask &= self.pp_df["Mitigation Set"].isna()
|
|
306
|
+
else:
|
|
307
|
+
mask &= self.pp_df["Mitigation Set"] == mitigation
|
|
308
|
+
self.pp_df = self.pp_df.loc[mask].copy()
|
|
309
|
+
self.calc_pp_data()
|
|
310
|
+
|
|
311
|
+
# Ensure mitigation-driven columns exist on route rows after segmentation
|
|
312
|
+
if "Sleeper Height" not in self.route_df.columns:
|
|
313
|
+
self.route_df["Sleeper Height"] = np.nan
|
|
314
|
+
if "RCM Buckling Force" not in self.route_df.columns:
|
|
315
|
+
self.route_df["RCM Buckling Force"] = np.nan
|
|
316
|
+
|
|
317
|
+
# Set "Bend Radius" to NaN for rows where "Sleeper Height" or "RCM Buckling Force" are not NaN
|
|
318
|
+
self.route_df.loc[~self.route_df["Sleeper Height"].isna(), "Bend Radius"] = np.nan
|
|
319
|
+
self.route_df.loc[~self.route_df["RCM Buckling Force"].isna(), "Bend Radius"] = np.nan
|
|
320
|
+
|
|
321
|
+
# Select specific columns for route data output
|
|
322
|
+
cols = [
|
|
323
|
+
"Pipeline", "Layout Set", "Pipe Set", "Friction Set", "Route Type", "Point ID From",
|
|
324
|
+
"Point ID To", "KP From", "KP To", "Bend Radius", "Sleeper Height",
|
|
325
|
+
"RCM Buckling Force", "HOOS Mean", "HOOS STD", "HOOS Reference Length",
|
|
326
|
+
"Residual Buckle Force Hydrotest", "Residual Buckle Length Hydrotest",
|
|
327
|
+
"Residual Buckle Force Operation", "Residual Buckle Length Operation",
|
|
328
|
+
"Reaction Installation", "Reaction Hydrotest", "Reaction Operation"
|
|
329
|
+
]
|
|
330
|
+
self.route_df = self.route_df[cols].copy()
|
|
331
|
+
|
|
332
|
+
# Print out in the terminal time taken to create main dataframe
|
|
333
|
+
if self.bl_verbose:
|
|
334
|
+
print(f" Time taken to create main dataframe: {time.time() - start_time:.1f}s")
|
|
335
|
+
|
|
336
|
+
return self.scen_np, self.dist_np, self.ends_np, self.scen_df, self.route_df, self.pp_df
|
|
337
|
+
|
|
338
|
+
def calc_route_data(self):
|
|
339
|
+
"""
|
|
340
|
+
Extract and process route data for calculations.
|
|
341
|
+
|
|
342
|
+
Parameters
|
|
343
|
+
----------
|
|
344
|
+
route_df : pandas.DataFrame
|
|
345
|
+
DataFrame containing route data.
|
|
346
|
+
mitigation_df : pandas.DataFrame
|
|
347
|
+
DataFrame containing mitigation data.
|
|
348
|
+
soil_zoning_df : pandas.DataFrame
|
|
349
|
+
DataFrame containing soil zoning data.
|
|
350
|
+
|
|
351
|
+
Returns
|
|
352
|
+
-------
|
|
353
|
+
route_df : pandas.DataFrame
|
|
354
|
+
DataFrame containing route data and calculated route data.
|
|
355
|
+
route_ends_df : pandas.DataFrame
|
|
356
|
+
DataFrame containing end boundary conditions.
|
|
357
|
+
|
|
358
|
+
Notes
|
|
359
|
+
-----
|
|
360
|
+
This function extracts route ends and route data based on layout,
|
|
361
|
+
mitigation, and soil_zoning. It selects specific columns for route ends data.
|
|
362
|
+
Route Type is converted from string tofloat for numerical representation. Route ends
|
|
363
|
+
data is converted to a NumPy array for efficient processing.
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
# Extract route ends based on layout
|
|
367
|
+
self.route_ends_df = self.route_df.iloc[[0, -1]]
|
|
368
|
+
|
|
369
|
+
# Select specific columns for route ends data
|
|
370
|
+
self.route_ends_df = self.route_ends_df[[
|
|
371
|
+
"Route Type",
|
|
372
|
+
"KP From",
|
|
373
|
+
"KP To",
|
|
374
|
+
"Reaction Installation",
|
|
375
|
+
"Reaction Hydrotest",
|
|
376
|
+
"Reaction Operation"
|
|
377
|
+
]]
|
|
378
|
+
|
|
379
|
+
# Convert "Route Type" from string to float for numerical representation
|
|
380
|
+
self.route_ends_df.loc[self.route_ends_df["Route Type"] == "Spool", "Route Type"] = 1
|
|
381
|
+
self.route_ends_df.loc[self.route_ends_df["Route Type"] == "Fixed", "Route Type"] = 2
|
|
382
|
+
self.route_ends_df["Route Type"] = self.route_ends_df["Route Type"].astype(float)
|
|
383
|
+
|
|
384
|
+
# Extract route data based on layout
|
|
385
|
+
self.route_df = self.route_df.iloc[1:-1].copy()
|
|
386
|
+
|
|
387
|
+
# Combine rows from route and mitigation, then sort by KP From
|
|
388
|
+
self.apply_route_mitigation()
|
|
389
|
+
|
|
390
|
+
# Extract soil zoning data based on soil_zoning
|
|
391
|
+
self.apply_route_soil_zoning()
|
|
392
|
+
|
|
393
|
+
def calc_pipe_data(self):
|
|
394
|
+
"""
|
|
395
|
+
Calculate properties of pipes.
|
|
396
|
+
|
|
397
|
+
Parameters
|
|
398
|
+
----------
|
|
399
|
+
pipe_df : pandas.DataFrame
|
|
400
|
+
DataFrame containing the pipe data.
|
|
401
|
+
|
|
402
|
+
Returns
|
|
403
|
+
-------
|
|
404
|
+
pipe_df : pandas.DataFrame
|
|
405
|
+
DataFrame containing the pipe data and calculated pipe properties.
|
|
406
|
+
|
|
407
|
+
Notes
|
|
408
|
+
-----
|
|
409
|
+
This function computes the inner diameter (ID), cross-sectional area (As), inner area (Ai),
|
|
410
|
+
moment of inertia (I), hydrotest characteristic buckling force (SChar HT),
|
|
411
|
+
and operation characteristic buckling force (SChar OP) of the pipe.
|
|
412
|
+
"""
|
|
413
|
+
|
|
414
|
+
# Compute the inner diameter (ID) of the pipe
|
|
415
|
+
self.pipe_df["ID"] = self.pipe_df["OD"] - 2.0 * self.pipe_df["WT"]
|
|
416
|
+
|
|
417
|
+
# Compute the cross-sectional area (As) of the pipe
|
|
418
|
+
self.pipe_df["As"] = np.pi / 4.0 * (self.pipe_df["OD"] ** 2 - self.pipe_df["ID"] ** 2)
|
|
419
|
+
|
|
420
|
+
# Compute the inner area (Ai) of the pipe
|
|
421
|
+
self.pipe_df["Ai"] = np.pi / 4.0 * self.pipe_df["ID"] ** 2
|
|
422
|
+
|
|
423
|
+
# Compute the moment of inertia (I) of the pipe
|
|
424
|
+
self.pipe_df["I"] = np.pi / 64.0 * (self.pipe_df["OD"] ** 4 - self.pipe_df["ID"] ** 4)
|
|
425
|
+
|
|
426
|
+
# Compute the hydrotest characteristic buckling force (SChar HT) of the pipe
|
|
427
|
+
self.pipe_df["SChar HT"] = 2.26 * (self.pipe_df["E"] * self.pipe_df["As"]) ** 0.25 * (self.pipe_df["E"] * self.pipe_df["I"]) ** 0.25 * self.pipe_df["sw Hydrotest"] ** 0.5
|
|
428
|
+
|
|
429
|
+
# Compute the operation characteristic buckling force (SChar OP) of the pipe
|
|
430
|
+
self.pipe_df["SChar OP"] = 2.26 * (self.pipe_df["E"] * self.pipe_df["As"]) ** 0.25 * (self.pipe_df["E"] * self.pipe_df["I"]) ** 0.25 * self.pipe_df["sw Operation"] ** 0.5
|
|
431
|
+
|
|
432
|
+
def calc_soil_data(self):
|
|
433
|
+
"""
|
|
434
|
+
Calculate soil data and axial and lateral friction factor distributions
|
|
435
|
+
and assign them to DataFrame columns.
|
|
436
|
+
|
|
437
|
+
Parameters
|
|
438
|
+
----------
|
|
439
|
+
soil_df : pandas.DataFrame
|
|
440
|
+
DataFrame containing soil data.
|
|
441
|
+
|
|
442
|
+
Returns
|
|
443
|
+
-------
|
|
444
|
+
soil_df : pandas.DataFrame
|
|
445
|
+
DataFrame containing soil data and calculated friction factor distributions.
|
|
446
|
+
|
|
447
|
+
Notes
|
|
448
|
+
-----
|
|
449
|
+
This function computes lognormal distributions for axial and lateral
|
|
450
|
+
friction factors and assigns them to DataFrame columns.
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
# Compute lognormal distributions for axial friction and assign to DataFrame
|
|
454
|
+
result = ss.LBSoilDistributions(
|
|
455
|
+
friction_factor_le=self.soil_df["Axial LE"],
|
|
456
|
+
friction_factor_be=self.soil_df["Axial BE"],
|
|
457
|
+
friction_factor_he=self.soil_df["Axial HE"],
|
|
458
|
+
friction_factor_fit_type=self.soil_df["Axial Fit Bounds"]
|
|
459
|
+
).friction_distribution_parameters()
|
|
460
|
+
self.soil_df["Axial Mean"], self.soil_df["Axial STD"] = result[:2]
|
|
461
|
+
muax_array = np.asarray(result[-2])
|
|
462
|
+
muax_cdf = np.asarray(result[-1])
|
|
463
|
+
self.soil_df["muax Array"] = list(np.atleast_2d(muax_array))
|
|
464
|
+
self.soil_df["muax CDF Array"] = list(np.atleast_2d(muax_cdf))
|
|
465
|
+
|
|
466
|
+
# Compute lognormal distributions for lateral hydrotest friction and assign to DataFrame
|
|
467
|
+
result = ss.LBSoilDistributions(
|
|
468
|
+
friction_factor_le=self.soil_df["Lateral Hydrotest LE"],
|
|
469
|
+
friction_factor_be=self.soil_df["Lateral Hydrotest BE"],
|
|
470
|
+
friction_factor_he=self.soil_df["Lateral Hydrotest HE"],
|
|
471
|
+
friction_factor_fit_type=self.soil_df["Lateral Hydrotest Fit Bounds"]
|
|
472
|
+
).friction_distribution_parameters()
|
|
473
|
+
self.soil_df["Lateral Hydrotest Mean"], self.soil_df["Lateral Hydrotest STD"] = result[:2]
|
|
474
|
+
mul_ht_array = np.asarray(result[-2])
|
|
475
|
+
mul_ht_cdf = np.asarray(result[-1])
|
|
476
|
+
self.soil_df["mul HT Array"] = list(np.atleast_2d(mul_ht_array))
|
|
477
|
+
self.soil_df["mul HT CDF Array"] = list(np.atleast_2d(mul_ht_cdf))
|
|
478
|
+
|
|
479
|
+
# Compute lognormal distributions for lateral operation friction and assign to DataFrame
|
|
480
|
+
result = ss.LBSoilDistributions(
|
|
481
|
+
friction_factor_le=self.soil_df["Lateral Operation LE"],
|
|
482
|
+
friction_factor_be=self.soil_df["Lateral Operation BE"],
|
|
483
|
+
friction_factor_he=self.soil_df["Lateral Operation HE"],
|
|
484
|
+
friction_factor_fit_type=self.soil_df["Lateral Operation Fit Bounds"]
|
|
485
|
+
).friction_distribution_parameters()
|
|
486
|
+
self.soil_df["Lateral Operation Mean"], self.soil_df["Lateral Operation STD"] = result[:2]
|
|
487
|
+
mul_op_array = np.asarray(result[-2])
|
|
488
|
+
mul_op_cdf = np.asarray(result[-1])
|
|
489
|
+
self.soil_df["mul OP Array"] = list(np.atleast_2d(mul_op_array))
|
|
490
|
+
self.soil_df["mul OP CDF Array"] = list(np.atleast_2d(mul_op_cdf))
|
|
491
|
+
|
|
492
|
+
def calc_oper_data(self):
|
|
493
|
+
"""
|
|
494
|
+
Calculate operating data and process it.
|
|
495
|
+
|
|
496
|
+
Parameters
|
|
497
|
+
----------
|
|
498
|
+
oper_df : pandas.DataFrame
|
|
499
|
+
DataFrame containing the operating data.
|
|
500
|
+
route_ends_df : pandas.DataFrame
|
|
501
|
+
DataFrame containing the end boundary conditions.
|
|
502
|
+
|
|
503
|
+
Returns
|
|
504
|
+
-------
|
|
505
|
+
df : pandas.DataFrame
|
|
506
|
+
DataFrame containing the operating data and calculated operating data.
|
|
507
|
+
|
|
508
|
+
Notes
|
|
509
|
+
-----
|
|
510
|
+
This function filters oper_df DataFrame based on loadcase, and "KP To".
|
|
511
|
+
It calculates rolling mean and difference, assigns the "Length" column,
|
|
512
|
+
resets the index, and drops rows with NaN values before returning the
|
|
513
|
+
preprocessed DataFrame.
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
# Select the "Point ID From" and "KP To" columns
|
|
517
|
+
route_df_temp = self.route_df[["Point ID From", "KP To"]].reset_index(drop = True).copy()
|
|
518
|
+
|
|
519
|
+
# Add the end row of route and the start KP
|
|
520
|
+
end_row = pd.DataFrame({"Point ID From": "End", "KP To": np.nan}, index = [99999])
|
|
521
|
+
route_df_temp = pd.concat([route_df_temp, end_row], ignore_index = True)
|
|
522
|
+
|
|
523
|
+
# Shift KP column 1 downwards and assign 0.0 to the first KP
|
|
524
|
+
route_df_temp["KP To"] = route_df_temp["KP To"].shift().fillna(0.0)
|
|
525
|
+
|
|
526
|
+
# Expand the KP array with 1000 intervals from 1000 to nearest maximum KP
|
|
527
|
+
route_df_temp = self.build_oper_kp_mesh_from_route(route_df_temp)
|
|
528
|
+
|
|
529
|
+
# Create the elements between each KP points
|
|
530
|
+
elem_array_temp = self.build_oper_element_kp_array(route_df_temp)
|
|
531
|
+
|
|
532
|
+
# Interpolate the RLT, pressure and temperature using KP and operating profile
|
|
533
|
+
self.interpolate_oper_profile_on_kp(elem_array_temp)
|
|
534
|
+
|
|
535
|
+
# Filter oper_df DataFrame based on loadcase and "KP To"
|
|
536
|
+
self.oper_df = self.oper_df.loc[
|
|
537
|
+
self.oper_df["KP"] <= self.route_ends_df["KP To"].iloc[-1]
|
|
538
|
+
].copy()
|
|
539
|
+
|
|
540
|
+
# Calculate the rolling mean of oper_df grouped by Loadcase Set
|
|
541
|
+
df_rolling_mean = self.oper_df.rolling(2).mean()
|
|
542
|
+
|
|
543
|
+
# Calculate the rolling difference of oper_df grouped by Loadcase Set
|
|
544
|
+
df_rolling_difference = self.oper_df.rolling(2).max() - self.oper_df.rolling(2).min()
|
|
545
|
+
|
|
546
|
+
# Assign the "Length" column in df_rolling_mean
|
|
547
|
+
df_rolling_mean["Length"] = df_rolling_difference["KP"]
|
|
548
|
+
|
|
549
|
+
# Reset the index of df_rolling_mean and drop the "level_2" index level
|
|
550
|
+
df_rolling_mean = df_rolling_mean.reset_index(drop=True)
|
|
551
|
+
|
|
552
|
+
# Drop rows with NaN values
|
|
553
|
+
df_rolling_mean = df_rolling_mean.dropna()
|
|
554
|
+
|
|
555
|
+
self.oper_df = df_rolling_mean.copy()
|
|
556
|
+
|
|
557
|
+
def calc_scenario_data(self):
|
|
558
|
+
"""
|
|
559
|
+
Calculate scenario data based on route, pipe, operating, and soil data.
|
|
560
|
+
|
|
561
|
+
Parameters
|
|
562
|
+
----------
|
|
563
|
+
route_df : pandas.DataFrame
|
|
564
|
+
DataFrame containing route data.
|
|
565
|
+
pipe_df : pandas.DataFrame
|
|
566
|
+
DataFrame containing pipe data.
|
|
567
|
+
oper_df : pandas.DataFrame
|
|
568
|
+
DataFrame containing operating data.
|
|
569
|
+
soil_df : pandas.DataFrame
|
|
570
|
+
DataFrame containing soil data.
|
|
571
|
+
|
|
572
|
+
Returns
|
|
573
|
+
-------
|
|
574
|
+
df: pandas.DataFrame
|
|
575
|
+
DataFrame containing the calculated scenario data.
|
|
576
|
+
|
|
577
|
+
Notes
|
|
578
|
+
-----
|
|
579
|
+
This function merges route, pipe, operating, and soil data to compute various scenario
|
|
580
|
+
parameters. It calculates various attributes such as lognormal distributions,
|
|
581
|
+
buckling forces, and section counts. The resulting DataFrame includes a subset of
|
|
582
|
+
calculated columns and is filled with 0 for missing values.
|
|
583
|
+
"""
|
|
584
|
+
|
|
585
|
+
# Merge operating data with route data using an asof merge to align KPs and route segments
|
|
586
|
+
temp_df = pd.merge_asof(
|
|
587
|
+
left=self.oper_df,
|
|
588
|
+
right=self.route_df,
|
|
589
|
+
left_on="KP",
|
|
590
|
+
right_on="KP From",
|
|
591
|
+
direction="backward",
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
# Merge resulting DataFrame with pipe data based on Pipe Set
|
|
595
|
+
temp_df = pd.merge(
|
|
596
|
+
left=temp_df,
|
|
597
|
+
right=self.pipe_df,
|
|
598
|
+
left_on="Pipe Set",
|
|
599
|
+
right_on="Pipe Set"
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
# Merge resulting DataFrame with soil data based on Friction Set
|
|
603
|
+
temp_df = pd.merge(
|
|
604
|
+
left=temp_df,
|
|
605
|
+
right=self.soil_df,
|
|
606
|
+
left_on="Friction Set",
|
|
607
|
+
right_on="Friction Set"
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
# Compute lognormal distributions for soil properties and assign to DataFrame columns
|
|
611
|
+
temp_df["HOOS X Array"], temp_df["HOOS CDF Array"] = zip(
|
|
612
|
+
*temp_df.apply(
|
|
613
|
+
lambda x: calc_lognorm_hoos(
|
|
614
|
+
x["Route Type"],
|
|
615
|
+
x["Length"],
|
|
616
|
+
x["HOOS Mean"],
|
|
617
|
+
x["HOOS STD"],
|
|
618
|
+
x["HOOS Reference Length"],
|
|
619
|
+
x.get("RCM Buckling Force", np.nan),
|
|
620
|
+
),
|
|
621
|
+
axis=1
|
|
622
|
+
).apply(np.array)
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
# Compute various buckling forces based on calculated parameters
|
|
626
|
+
temp_df["FRF HT"] = (
|
|
627
|
+
temp_df["RLT"] +
|
|
628
|
+
temp_df["E"] * temp_df["Alpha"] * temp_df["As"] * (temp_df["Temperature Hydrotest"] - temp_df["Temperature Installation"]) +
|
|
629
|
+
(1 - 2 * temp_df["Poisson"]) * (temp_df["Pressure Hydrotest"] - temp_df["Pressure Installation"]) * temp_df["Ai"]
|
|
630
|
+
)
|
|
631
|
+
temp_df["FRF OP"] = (
|
|
632
|
+
temp_df["RLT"] +
|
|
633
|
+
temp_df["E"] * temp_df["Alpha"] * temp_df["As"] * (temp_df["Temperature Operation"] - temp_df["Temperature Installation"]) +
|
|
634
|
+
(1 - 2 * temp_df["Poisson"]) * (temp_df["Pressure Operation"] - temp_df["Pressure Installation"]) * temp_df["Ai"]
|
|
635
|
+
)
|
|
636
|
+
temp_df["FRF OP Pressure"] = (
|
|
637
|
+
temp_df["RLT"] +
|
|
638
|
+
(1 - 2 * temp_df["Poisson"]) * temp_df["Pressure Operation"] * temp_df["Ai"]
|
|
639
|
+
)
|
|
640
|
+
temp_df["FRF OP Temperature"] = (
|
|
641
|
+
temp_df["E"] * temp_df["As"] * temp_df["Alpha"] * (temp_df["Temperature Operation"] - temp_df["Temperature Installation"])
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
# Calculate the hydrotest and operation buckling forces (Sv)
|
|
645
|
+
sleeper_height = temp_df.get("Sleeper Height", pd.Series(np.nan, index=temp_df.index))
|
|
646
|
+
temp_df["Sv HT"] = 4.0 * np.sqrt(temp_df["E"] * temp_df["I"] * temp_df["sw Hydrotest"] / sleeper_height)
|
|
647
|
+
temp_df["Sv OP"] = 4.0 * np.sqrt(temp_df["E"] * temp_df["I"] * temp_df["sw Operation"] / sleeper_height)
|
|
648
|
+
|
|
649
|
+
# Calculate section-related parameters
|
|
650
|
+
temp_df["KP Section"] = temp_df["KP"] - temp_df["KP From"]
|
|
651
|
+
temp_df["Reference Section"] = (temp_df["KP Section"] / temp_df["HOOS Reference Length"]).apply(np.floor)
|
|
652
|
+
temp_df["Section Count"] = 0.0
|
|
653
|
+
temp_df.loc[
|
|
654
|
+
(temp_df["Route Type"] != temp_df["Route Type"].shift()) |
|
|
655
|
+
(temp_df["Reference Section"] != temp_df["Reference Section"].shift()), "Section Count"
|
|
656
|
+
] = 1.0
|
|
657
|
+
temp_df["Section Count"] = temp_df["Section Count"].cumsum()
|
|
658
|
+
|
|
659
|
+
# Calculate the residual buckle length and force for hydrotest and operation
|
|
660
|
+
if "RCM Buckling Force" not in temp_df.columns:
|
|
661
|
+
temp_df["RCM Buckling Force"] = np.nan
|
|
662
|
+
|
|
663
|
+
# Select relevant columns and rename them for clarity
|
|
664
|
+
temp_df = temp_df[[
|
|
665
|
+
"KP", "Length", "Route Type", "KP From", "KP To", "Point ID From", "Point ID To",
|
|
666
|
+
"Bend Radius", "muax Array", "muax CDF Array",
|
|
667
|
+
"mul HT Array", "mul HT CDF Array", "mul OP Array", "mul OP CDF Array",
|
|
668
|
+
"HOOS X Array", "HOOS CDF Array", "sw Installation", "sw Hydrotest", "sw Operation",
|
|
669
|
+
"SChar HT", "SChar OP", "Sv HT", "Sv OP", "RCM Buckling Force", "RLT", "FRF HT",
|
|
670
|
+
"FRF OP Pressure", "FRF OP Temperature", "FRF OP", "Residual Buckle Length Hydrotest",
|
|
671
|
+
"Residual Buckle Force Hydrotest", "Residual Buckle Length Operation",
|
|
672
|
+
"Residual Buckle Force Operation", "Section Count", "KP Section", "Reference Section",
|
|
673
|
+
"Axial Mean", "Lateral Hydrotest Mean", "Lateral Operation Mean", "HOOS Mean"
|
|
674
|
+
]]
|
|
675
|
+
|
|
676
|
+
temp_df = temp_df.rename(columns={
|
|
677
|
+
"sw Installation": "sw IN",
|
|
678
|
+
"sw Hydrotest": "sw HT",
|
|
679
|
+
"sw Operation": "sw OP",
|
|
680
|
+
"Residual Buckle Length Hydrotest": "buckleLength HT",
|
|
681
|
+
"Residual Buckle Force Hydrotest": "buckleEAF HT",
|
|
682
|
+
"Residual Buckle Length Operation": "buckleLength OP",
|
|
683
|
+
"Residual Buckle Force Operation": "buckleEAF OP"
|
|
684
|
+
})
|
|
685
|
+
|
|
686
|
+
# Convert route type strings to numerical representation
|
|
687
|
+
temp_df.loc[temp_df["Route Type"] == "Straight", "Route Type"] = 1
|
|
688
|
+
temp_df.loc[temp_df["Route Type"] == "Bend", "Route Type"] = 2
|
|
689
|
+
temp_df.loc[temp_df["Route Type"] == "Sleeper", "Route Type"] = 3
|
|
690
|
+
temp_df.loc[temp_df["Route Type"] == "RCM", "Route Type"] = 4
|
|
691
|
+
temp_df["Route Type"] = temp_df["Route Type"].astype(float)
|
|
692
|
+
|
|
693
|
+
# Fill missing values with 0
|
|
694
|
+
temp_df = temp_df.fillna(0)
|
|
695
|
+
|
|
696
|
+
# Add scenario parameters to the DataFrame
|
|
697
|
+
temp_df["Pipeline"] = self.scen_df["Pipeline"].values[0]
|
|
698
|
+
temp_df["Scenario"] = self.scen_df["Scenario"].values[0]
|
|
699
|
+
temp_df["Layout Set"] = self.scen_df["Layout Set"].values[0]
|
|
700
|
+
temp_df["Simulations"] = self.scen_df["Simulations"].values[0]
|
|
701
|
+
temp_df["Friction Sampling"] = self.scen_df["Friction Sampling"].values[0]
|
|
702
|
+
temp_df["Char. Friction Prob."] = self.scen_df["Char. Friction Prob."].values[0]
|
|
703
|
+
|
|
704
|
+
self.scen_df = temp_df.copy()
|
|
705
|
+
|
|
706
|
+
def calc_pp_data(self):
|
|
707
|
+
"""
|
|
708
|
+
Calculate post-processing data set for a given layout set.
|
|
709
|
+
|
|
710
|
+
Parameters
|
|
711
|
+
----------
|
|
712
|
+
df : pandas.DataFrame
|
|
713
|
+
DataFrame containing post-processing data.
|
|
714
|
+
np_array : numpy.ndarray
|
|
715
|
+
NumPy array containing pipeline end boundary conditions.
|
|
716
|
+
pipeline_id : str
|
|
717
|
+
Identifier of the pipeline.
|
|
718
|
+
layout_set : str
|
|
719
|
+
Identifier of the layout set.
|
|
720
|
+
|
|
721
|
+
Returns
|
|
722
|
+
-------
|
|
723
|
+
df : pandas.DataFrame
|
|
724
|
+
DataFrame containing calculated post-processing data.
|
|
725
|
+
|
|
726
|
+
Notes
|
|
727
|
+
-----
|
|
728
|
+
This function filters the DataFrame based on the layout set. It resets the index, renames
|
|
729
|
+
columns, and selects relevant columns. Adjusts the last 'KP_to' value if it is smaller
|
|
730
|
+
than the maximum value in np_array. Converts data types of columns to appropriate numeric
|
|
731
|
+
types.
|
|
732
|
+
"""
|
|
733
|
+
|
|
734
|
+
# Reset index, rename columns, and select relevant columns
|
|
735
|
+
self.pp_df = self.pp_df.reset_index(drop=True).rename(columns={
|
|
736
|
+
'Post-Processing Set': 'pp_set',
|
|
737
|
+
'KP From': 'KP_from',
|
|
738
|
+
'KP To': 'KP_to',
|
|
739
|
+
'Post-Processing Description': 'description'
|
|
740
|
+
})
|
|
741
|
+
self.pp_df = self.pp_df[
|
|
742
|
+
['pp_set', 'KP_from', 'KP_to', 'description', 'Characteristic VAS Probability']
|
|
743
|
+
]
|
|
744
|
+
|
|
745
|
+
# Convert columns to appropriate numeric types
|
|
746
|
+
self.pp_df['pp_set'] = self.pp_df['pp_set'].astype(np.int64)
|
|
747
|
+
self.pp_df['KP_from'] = self.pp_df['KP_from'].astype(np.float64)
|
|
748
|
+
self.pp_df['KP_to'] = self.pp_df['KP_to'].astype(np.float64)
|
|
749
|
+
|
|
750
|
+
def calc_monte_carlo_data(self):
|
|
751
|
+
"""
|
|
752
|
+
Convert the scenario data and end boundary conditions data to NumPy arrays for
|
|
753
|
+
Monte Carlo simulations.
|
|
754
|
+
|
|
755
|
+
Parameters
|
|
756
|
+
----------
|
|
757
|
+
scen_df : pandas.DataFrame
|
|
758
|
+
DataFrame containing the scenario data.
|
|
759
|
+
route_ends_df : pandas.DataFrame
|
|
760
|
+
DataFrame containing the end boundary conditions data.
|
|
761
|
+
|
|
762
|
+
Returns
|
|
763
|
+
-------
|
|
764
|
+
dist_np : numpy.ndarray
|
|
765
|
+
2D array with probabilistic distributions (rows) along the route mesh (columns).
|
|
766
|
+
scen_np : numpy.ndarray
|
|
767
|
+
2D array with scenario properties (rows) along the route mesh (columns).
|
|
768
|
+
ends_np : numpy.ndarray
|
|
769
|
+
2D array with end properties (rows) for the ends.
|
|
770
|
+
|
|
771
|
+
Notes
|
|
772
|
+
-----
|
|
773
|
+
The arrays have the following row layout (index : meaning):
|
|
774
|
+
|
|
775
|
+
scen_np:
|
|
776
|
+
- 0 : KP
|
|
777
|
+
- 1 : LENGTH
|
|
778
|
+
- 2 : ROUTE_TYPE
|
|
779
|
+
- 3 : BEND_RADIUS
|
|
780
|
+
- 4 : SW_INST
|
|
781
|
+
- 5 : SW_HT
|
|
782
|
+
- 6 : SW_OP
|
|
783
|
+
- 7 : SCHAR_HT
|
|
784
|
+
- 8 : SCHAR_OP
|
|
785
|
+
- 9 : SV_HT
|
|
786
|
+
- 10 : SV_OP
|
|
787
|
+
- 11 : CBF_RCM
|
|
788
|
+
- 12 : RLT
|
|
789
|
+
- 13 : FRF_HT
|
|
790
|
+
- 14 : FRF_P_OP
|
|
791
|
+
- 15 : FRF_T_OP
|
|
792
|
+
- 16 : FRF_OP
|
|
793
|
+
- 17 : L_BUCKLE_HT
|
|
794
|
+
- 18 : EAF_BUCKLE_HT
|
|
795
|
+
- 19 : L_BUCKLE_OP
|
|
796
|
+
- 20 : EAF_BUCKLE_OP
|
|
797
|
+
- 21 : SECTION_ID
|
|
798
|
+
- 22 : SECTION_KP
|
|
799
|
+
- 23 : SECTION_REF
|
|
800
|
+
- 24 : MUAX_MEAN
|
|
801
|
+
- 25 : MULAT_HT_MEAN
|
|
802
|
+
- 26 : MULAT_OP_MEAN
|
|
803
|
+
- 27 : HOOS_MEAN
|
|
804
|
+
|
|
805
|
+
dist_np:
|
|
806
|
+
- 0 : MUAX_ARRAY
|
|
807
|
+
- 1 : MUAX_CDF_ARRAY
|
|
808
|
+
- 2 : MULAT_ARRAY_HT
|
|
809
|
+
- 3 : MULAT_CDF_ARRAY_HT
|
|
810
|
+
- 4 : MULAT_ARRAY_OP
|
|
811
|
+
- 5 : MULAT_CDF_ARRAY_OP
|
|
812
|
+
- 6 : HOOS_ARRAY
|
|
813
|
+
- 7 : HOOS_CDF_ARRAY
|
|
814
|
+
|
|
815
|
+
ends_np:
|
|
816
|
+
- 0 : ROUTE_TYPE
|
|
817
|
+
- 1 : KP_FROM
|
|
818
|
+
- 2 : KP_TO
|
|
819
|
+
- 3 : REAC_INST
|
|
820
|
+
- 4 : REAC_HT
|
|
821
|
+
- 5 : REAC_OP
|
|
822
|
+
"""
|
|
823
|
+
|
|
824
|
+
# Create a list to store the distribution arrays and define their column labels
|
|
825
|
+
dist_list = []
|
|
826
|
+
dist_list_columns = [
|
|
827
|
+
"muax Array",
|
|
828
|
+
"muax CDF Array",
|
|
829
|
+
"mul HT Array",
|
|
830
|
+
"mul HT CDF Array",
|
|
831
|
+
"mul OP Array",
|
|
832
|
+
"mul OP CDF Array",
|
|
833
|
+
"HOOS X Array",
|
|
834
|
+
"HOOS CDF Array"
|
|
835
|
+
]
|
|
836
|
+
|
|
837
|
+
# Loop through the distribution columns and convert each column to a list
|
|
838
|
+
for list_label in dist_list_columns:
|
|
839
|
+
dist_list_temp = []
|
|
840
|
+
for i in range(self.scen_df[list_label].size):
|
|
841
|
+
dist_list_temp.append(self.scen_df[list_label][i])
|
|
842
|
+
dist_list.append(dist_list_temp)
|
|
843
|
+
|
|
844
|
+
# Convert the list of distribution arrays to a NumPy array
|
|
845
|
+
self.dist_np = np.array(dist_list, dtype="float64")
|
|
846
|
+
|
|
847
|
+
# Add extra columns to remove
|
|
848
|
+
dist_array_columns_drop = [
|
|
849
|
+
"Pipeline", "Scenario", "Simulations", "Friction Sampling", "Char. Friction Prob.",
|
|
850
|
+
"KP From", "KP To", "Point ID From", "Point ID To"
|
|
851
|
+
]
|
|
852
|
+
dist_array_columns_drop = np.append(dist_array_columns_drop, dist_list_columns)
|
|
853
|
+
|
|
854
|
+
# Convert scenario properties to numpy array
|
|
855
|
+
self.scen_np = self.scen_df.drop(dist_array_columns_drop, axis=1).to_numpy().transpose()
|
|
856
|
+
|
|
857
|
+
# Convert end properties to numpy array
|
|
858
|
+
self.ends_np = self.route_ends_df.to_numpy().transpose()
|
|
859
|
+
|
|
860
|
+
def apply_route_mitigation(self):
|
|
861
|
+
"""
|
|
862
|
+
Function to combine rows from route and mitigation, then sort by KP From.
|
|
863
|
+
|
|
864
|
+
Parameters
|
|
865
|
+
----------
|
|
866
|
+
route_df : pandas Dataframe
|
|
867
|
+
Dataframe containing the route data.
|
|
868
|
+
mitigation_df : pandas Dataframe
|
|
869
|
+
Dataframe containing the mitigation data.
|
|
870
|
+
|
|
871
|
+
Returns
|
|
872
|
+
-------
|
|
873
|
+
route_df : pandas Dataframe
|
|
874
|
+
Dataframe containing the combined route and mitigation data, sorted by KP From.
|
|
875
|
+
"""
|
|
876
|
+
|
|
877
|
+
rows = []
|
|
878
|
+
|
|
879
|
+
for _, r in self.route_df.iterrows():
|
|
880
|
+
|
|
881
|
+
# Route segment start and end KP and point IDs
|
|
882
|
+
seg_start = r["KP From"]
|
|
883
|
+
seg_end = r["KP To"]
|
|
884
|
+
seg_from_point = r["Point ID From"]
|
|
885
|
+
|
|
886
|
+
# Mitigation rows that overlap this route segment
|
|
887
|
+
overlaps = self.mitigation_df[
|
|
888
|
+
(self.mitigation_df["KP To"] > seg_start) &
|
|
889
|
+
(self.mitigation_df["KP From"] < seg_end)
|
|
890
|
+
].sort_values("KP From")
|
|
891
|
+
|
|
892
|
+
for _, m in overlaps.iterrows():
|
|
893
|
+
|
|
894
|
+
# Calculate the overlapping KP range between the route and mitigation
|
|
895
|
+
m_from = max(seg_start, m["KP From"])
|
|
896
|
+
m_to = min(seg_end, m["KP To"])
|
|
897
|
+
if m_to <= m_from:
|
|
898
|
+
continue
|
|
899
|
+
|
|
900
|
+
# Part before mitigation
|
|
901
|
+
if m_from > seg_start:
|
|
902
|
+
pre = r.copy()
|
|
903
|
+
pre["KP From"] = seg_start
|
|
904
|
+
pre["KP To"] = m_from
|
|
905
|
+
pre["Point ID From"] = seg_from_point
|
|
906
|
+
pre["Point ID To"] = m["Point ID From"]
|
|
907
|
+
rows.append(pre)
|
|
908
|
+
|
|
909
|
+
# Mitigation part (override key fields from mitigation)
|
|
910
|
+
mid = r.copy()
|
|
911
|
+
mid["KP From"] = m_from
|
|
912
|
+
mid["KP To"] = m_to
|
|
913
|
+
|
|
914
|
+
# Copy every mitigation column except the KP boundaries, which are determined
|
|
915
|
+
# by the overlap with the route segment.
|
|
916
|
+
for col in m.index:
|
|
917
|
+
if col not in {"KP From", "KP To"}:
|
|
918
|
+
mid[col] = m[col]
|
|
919
|
+
|
|
920
|
+
rows.append(mid)
|
|
921
|
+
|
|
922
|
+
seg_start = m_to
|
|
923
|
+
seg_from_point = m["Point ID To"]
|
|
924
|
+
|
|
925
|
+
# Part after last mitigation
|
|
926
|
+
if seg_start < seg_end:
|
|
927
|
+
post = r.copy()
|
|
928
|
+
post["KP From"] = seg_start
|
|
929
|
+
post["KP To"] = seg_end
|
|
930
|
+
post["Point ID From"] = seg_from_point
|
|
931
|
+
rows.append(post)
|
|
932
|
+
|
|
933
|
+
self.route_df = (
|
|
934
|
+
pd.DataFrame(rows)
|
|
935
|
+
.sort_values("KP From", kind="mergesort")
|
|
936
|
+
.reset_index(drop=True)
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
def apply_route_soil_zoning(self):
|
|
940
|
+
"""
|
|
941
|
+
Function to combine rows from route and soil zoning, then sort by KP From.
|
|
942
|
+
|
|
943
|
+
Parameters
|
|
944
|
+
----------
|
|
945
|
+
route_df : pandas Dataframe
|
|
946
|
+
Dataframe containing the route data.
|
|
947
|
+
soil_zoning_df : pandas Dataframe
|
|
948
|
+
Dataframe containing the soil zoning data.
|
|
949
|
+
|
|
950
|
+
Returns
|
|
951
|
+
-------
|
|
952
|
+
route_df : pandas Dataframe
|
|
953
|
+
Dataframe containing the combined route and soil zoning data, sorted by KP From.
|
|
954
|
+
"""
|
|
955
|
+
|
|
956
|
+
# Copy the route and soil zoning dataframes
|
|
957
|
+
route = self.route_df.copy()
|
|
958
|
+
zones_all = self.soil_zoning_df.copy()
|
|
959
|
+
zones = zones_all.iloc[1:].copy()
|
|
960
|
+
|
|
961
|
+
base_friction = zones_all.iloc[0]["Friction Set"]
|
|
962
|
+
|
|
963
|
+
rows = []
|
|
964
|
+
|
|
965
|
+
for _, r in route.iterrows():
|
|
966
|
+
|
|
967
|
+
original_start = r["KP From"]
|
|
968
|
+
original_end = r["KP To"]
|
|
969
|
+
|
|
970
|
+
seg_start = r["KP From"]
|
|
971
|
+
seg_end = r["KP To"]
|
|
972
|
+
current_friction = base_friction
|
|
973
|
+
|
|
974
|
+
# Zones overlapping this route segment
|
|
975
|
+
overlaps = zones[
|
|
976
|
+
(zones["KP To"] > seg_start) &
|
|
977
|
+
(zones["KP From"] < seg_end)
|
|
978
|
+
].sort_values("KP From")
|
|
979
|
+
|
|
980
|
+
# No overlap: keep whole segment with current/base friction
|
|
981
|
+
if overlaps.empty:
|
|
982
|
+
row = r.copy()
|
|
983
|
+
row["Friction Set"] = current_friction
|
|
984
|
+
rows.append(row)
|
|
985
|
+
continue
|
|
986
|
+
|
|
987
|
+
for _, z in overlaps.iterrows():
|
|
988
|
+
z_from = max(seg_start, z["KP From"])
|
|
989
|
+
z_to = min(seg_end, z["KP To"])
|
|
990
|
+
if z_to <= z_from:
|
|
991
|
+
continue
|
|
992
|
+
|
|
993
|
+
# Before zone: keep previous friction
|
|
994
|
+
if z_from > seg_start:
|
|
995
|
+
pre = r.copy()
|
|
996
|
+
pre["KP From"] = seg_start
|
|
997
|
+
pre["KP To"] = z_from
|
|
998
|
+
pre["Friction Set"] = current_friction
|
|
999
|
+
pre["Point ID From"] = (
|
|
1000
|
+
r["Point ID From"] if seg_start == original_start else "Soil Change"
|
|
1001
|
+
)
|
|
1002
|
+
pre["Point ID To"] = "Soil Change"
|
|
1003
|
+
rows.append(pre)
|
|
1004
|
+
|
|
1005
|
+
# Inside zone: apply zone friction
|
|
1006
|
+
mid = r.copy()
|
|
1007
|
+
mid["KP From"] = z_from
|
|
1008
|
+
mid["KP To"] = z_to
|
|
1009
|
+
mid["Friction Set"] = z["Friction Set"]
|
|
1010
|
+
mid["Point ID From"] = (
|
|
1011
|
+
r["Point ID From"] if z_from == original_start else "Soil Change"
|
|
1012
|
+
)
|
|
1013
|
+
mid["Point ID To"] = (
|
|
1014
|
+
r["Point ID To"] if z_to == original_end else "Soil Change"
|
|
1015
|
+
)
|
|
1016
|
+
rows.append(mid)
|
|
1017
|
+
|
|
1018
|
+
seg_start = z_to
|
|
1019
|
+
current_friction = z["Friction Set"]
|
|
1020
|
+
|
|
1021
|
+
# Tail after last overlapping zone
|
|
1022
|
+
if seg_start < seg_end:
|
|
1023
|
+
post = r.copy()
|
|
1024
|
+
post["KP From"] = seg_start
|
|
1025
|
+
post["KP To"] = seg_end
|
|
1026
|
+
post["Friction Set"] = current_friction
|
|
1027
|
+
post["Point ID From"] = (
|
|
1028
|
+
r["Point ID From"] if seg_start == original_start else "Soil Change"
|
|
1029
|
+
)
|
|
1030
|
+
post["Point ID To"] = r["Point ID To"]
|
|
1031
|
+
rows.append(post)
|
|
1032
|
+
|
|
1033
|
+
self.route_df = (
|
|
1034
|
+
pd.DataFrame(rows)
|
|
1035
|
+
.sort_values("KP From", kind="mergesort")
|
|
1036
|
+
.reset_index(drop=True)
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
def build_oper_kp_mesh_from_route(self, route_df):
|
|
1040
|
+
"""
|
|
1041
|
+
Function to expand the KP array with 1000 intervals from 1000 to nearest maximum KP.
|
|
1042
|
+
|
|
1043
|
+
Parameters
|
|
1044
|
+
----------
|
|
1045
|
+
route_df : pandas Dataframe
|
|
1046
|
+
Dataframe containing the route data.
|
|
1047
|
+
|
|
1048
|
+
Returns
|
|
1049
|
+
-------
|
|
1050
|
+
route_df : pandas Dataframe
|
|
1051
|
+
Dataframe containing the route data with expanded KP values, calculated lengths,
|
|
1052
|
+
element numbers, and element sizes.
|
|
1053
|
+
"""
|
|
1054
|
+
|
|
1055
|
+
# Rename kp_col to "KP From"
|
|
1056
|
+
route_df = route_df.rename(columns = {"KP To": "KP From"})
|
|
1057
|
+
|
|
1058
|
+
# Expand the KP array with 1000 intervals from 1000 to nearest maximum KP
|
|
1059
|
+
max_kp = np.floor(route_df["KP From"].max() / 1000.0) * 1000.0
|
|
1060
|
+
kp_array = np.arange(1000, max_kp + 1.0, 1000)
|
|
1061
|
+
|
|
1062
|
+
# Create a dataframe for the expanded kp
|
|
1063
|
+
expand_df = pd.DataFrame({"Point ID From": [np.nan] * len(kp_array), "KP From": kp_array})
|
|
1064
|
+
route_df = pd.concat(
|
|
1065
|
+
[route_df, expand_df], ignore_index = True
|
|
1066
|
+
).sort_values(by = "KP From").drop_duplicates("KP From").reset_index(drop = True).ffill()
|
|
1067
|
+
|
|
1068
|
+
# Calculate relative length between KP and KP To
|
|
1069
|
+
route_df["KP To"] = route_df["KP From"].shift(-1)
|
|
1070
|
+
route_df = route_df.dropna()
|
|
1071
|
+
route_df["Length"] = route_df["KP To"] - route_df["KP From"]
|
|
1072
|
+
|
|
1073
|
+
# Calculate element number and element size
|
|
1074
|
+
route_df["Elem No."] = np.ceil(route_df["Length"] / 100.0)
|
|
1075
|
+
route_df["Elem Size"] = route_df["Length"] / route_df["Elem No."]
|
|
1076
|
+
|
|
1077
|
+
return route_df
|
|
1078
|
+
|
|
1079
|
+
def build_oper_element_kp_array(self, route_df):
|
|
1080
|
+
"""
|
|
1081
|
+
Function to create element array based on KP, KP TO and element number.
|
|
1082
|
+
|
|
1083
|
+
Parameters
|
|
1084
|
+
----------
|
|
1085
|
+
route_df : pandas Dataframe
|
|
1086
|
+
Dataframe containing the route data with expanded KP values, calculated lengths,
|
|
1087
|
+
element numbers, and element sizes.
|
|
1088
|
+
|
|
1089
|
+
Returns
|
|
1090
|
+
-------
|
|
1091
|
+
elem_array : numpy Array
|
|
1092
|
+
"""
|
|
1093
|
+
|
|
1094
|
+
# Create the elements between each KP points
|
|
1095
|
+
elem_values = []
|
|
1096
|
+
|
|
1097
|
+
for _, x in route_df.iterrows():
|
|
1098
|
+
elem_values.extend(
|
|
1099
|
+
np.linspace(x["KP From"], x["KP To"], int(x["Elem No."] + 1.0))
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
# Convert the list of element values to a NumPy array, remove duplicates and NaN values
|
|
1103
|
+
elem_array = np.array(elem_values, dtype=float)
|
|
1104
|
+
elem_array = np.unique(elem_array)
|
|
1105
|
+
elem_array = elem_array[~np.isnan(elem_array)]
|
|
1106
|
+
|
|
1107
|
+
return elem_array
|
|
1108
|
+
|
|
1109
|
+
def interpolate_oper_profile_on_kp(self, elem_array):
|
|
1110
|
+
"""
|
|
1111
|
+
Function to interpolate the RLT, pressure and temperature using KP and operating profile.
|
|
1112
|
+
|
|
1113
|
+
Parameters
|
|
1114
|
+
----------
|
|
1115
|
+
elem_array : numpy Array
|
|
1116
|
+
Array containing the KP values for interpolation.
|
|
1117
|
+
|
|
1118
|
+
Returns
|
|
1119
|
+
-------
|
|
1120
|
+
oper_df : pandas Dataframe
|
|
1121
|
+
Dataframe containing the interpolated RLT, pressure and temperature values based on KP and operating profile.
|
|
1122
|
+
"""
|
|
1123
|
+
|
|
1124
|
+
# Define the columns to interpolate
|
|
1125
|
+
interp_columns = [
|
|
1126
|
+
"Pressure Installation",
|
|
1127
|
+
"Pressure Hydrotest",
|
|
1128
|
+
"Pressure Operation",
|
|
1129
|
+
"Temperature Installation",
|
|
1130
|
+
"Temperature Hydrotest",
|
|
1131
|
+
"Temperature Operation",
|
|
1132
|
+
"RLT",
|
|
1133
|
+
]
|
|
1134
|
+
|
|
1135
|
+
# Create a dataframe for the interpolated values
|
|
1136
|
+
interp_df= pd.DataFrame({"KP": elem_array})
|
|
1137
|
+
|
|
1138
|
+
# Interpolate the RLT, pressure and temperature using KP and operating profile
|
|
1139
|
+
for column in interp_columns:
|
|
1140
|
+
interp_df[column] = np.interp(interp_df["KP"], self.oper_df["KP"], self.oper_df[column])
|
|
1141
|
+
|
|
1142
|
+
self.oper_df = interp_df.copy()
|