py-pilecore 0.3.4__py3-none-any.whl → 0.4.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.
Potentially problematic release.
This version of py-pilecore might be problematic. Click here for more details.
- {py_pilecore-0.3.4.dist-info → py_pilecore-0.4.1.dist-info}/METADATA +2 -1
- py_pilecore-0.4.1.dist-info/RECORD +24 -0
- {py_pilecore-0.3.4.dist-info → py_pilecore-0.4.1.dist-info}/WHEEL +1 -1
- pypilecore/_version.py +1 -1
- pypilecore/api.py +75 -19
- pypilecore/input/grouper_properties.py +1 -2
- pypilecore/input/multi_cpt.py +1 -1
- pypilecore/input/pile_properties.py +3 -2
- pypilecore/input/soil_properties.py +2 -2
- pypilecore/results/grouper_result.py +282 -59
- pypilecore/results/multi_cpt_results.py +9 -3
- pypilecore/results/post_processing.py +679 -0
- pypilecore/results/single_cpt_results.py +140 -171
- pypilecore/results/soil_properties.py +1 -1
- py_pilecore-0.3.4.dist-info/RECORD +0 -23
- {py_pilecore-0.3.4.dist-info → py_pilecore-0.4.1.dist-info}/LICENSE +0 -0
- {py_pilecore-0.3.4.dist-info → py_pilecore-0.4.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
from typing import Any, Dict, List, Literal, Optional, Sequence, Tuple
|
|
6
|
+
|
|
7
|
+
import matplotlib.patches as patches
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
from matplotlib import pyplot as plt
|
|
11
|
+
from matplotlib.axes import Axes
|
|
12
|
+
from matplotlib.collections import PatchCollection
|
|
13
|
+
from matplotlib.figure import Figure
|
|
14
|
+
from numpy.typing import NDArray
|
|
15
|
+
from scipy.spatial import Delaunay, Voronoi, voronoi_plot_2d
|
|
16
|
+
|
|
17
|
+
from pypilecore.results.soil_properties import SoilProperties, get_soil_layer_handles
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MaxBearingTable:
|
|
21
|
+
"""
|
|
22
|
+
Object that contains the results belonging to the maximum net design bearing capacity (R_c_d_net) for a single CPT.
|
|
23
|
+
|
|
24
|
+
*Not meant to be instantiated by the user.*
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
pile_tip_level_nap: Sequence[float],
|
|
30
|
+
R_c_d_net: Sequence[float],
|
|
31
|
+
F_nk_d: Sequence[float],
|
|
32
|
+
origin: Sequence[str],
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Object that contains the results belonging to the maximum net design bearing capacity (R_c_d_net) for a single CPT.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
pile_tip_level_nap
|
|
40
|
+
The elevation of the pile-tip, in [m] w.r.t. NAP.
|
|
41
|
+
R_c_d_net
|
|
42
|
+
The maximum net design bearing capacity, in [kN].
|
|
43
|
+
F_nk_d
|
|
44
|
+
The net design bearing capacity, in [kN].
|
|
45
|
+
origin
|
|
46
|
+
The origin of the CPT data.
|
|
47
|
+
"""
|
|
48
|
+
self._pile_tip_level_nap = pile_tip_level_nap
|
|
49
|
+
self._R_c_d_net = R_c_d_net
|
|
50
|
+
self._F_nk_d = F_nk_d
|
|
51
|
+
self._origin = origin
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def pile_tip_level_nap(self) -> NDArray[np.float64]:
|
|
55
|
+
"""The elevation of the pile-tip, in [m] w.r.t. NAP."""
|
|
56
|
+
return np.array(self._pile_tip_level_nap).astype(np.float64)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def R_c_d_net(self) -> NDArray[np.float64]:
|
|
60
|
+
"""The maximum net design bearing capacity, in [kN]."""
|
|
61
|
+
return np.array(self._R_c_d_net).astype(np.float64)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def F_nk_d(self) -> NDArray[np.float64]:
|
|
65
|
+
"""The net design bearing capacity, in [kN]."""
|
|
66
|
+
return np.array(self._F_nk_d).astype(np.float64)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def origin(self) -> NDArray[np.str_]:
|
|
70
|
+
"""The origin of the CPT data."""
|
|
71
|
+
return np.array(self._origin).astype(np.str_)
|
|
72
|
+
|
|
73
|
+
@lru_cache
|
|
74
|
+
def to_pandas(self) -> pd.DataFrame:
|
|
75
|
+
"""Get the pandas.DataFrame representation"""
|
|
76
|
+
return pd.DataFrame(
|
|
77
|
+
dict(
|
|
78
|
+
pile_tip_level_nap=self.pile_tip_level_nap,
|
|
79
|
+
R_c_d_net=self.R_c_d_net,
|
|
80
|
+
F_nk_d=self.F_nk_d,
|
|
81
|
+
origin=self.origin,
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class MaxBearingResult:
|
|
88
|
+
"""
|
|
89
|
+
Object that contains the results of a PileCore single-cpt calculation.
|
|
90
|
+
|
|
91
|
+
*Not meant to be instantiated by the user.*
|
|
92
|
+
|
|
93
|
+
Attributes
|
|
94
|
+
----------
|
|
95
|
+
soil_properties
|
|
96
|
+
The object with soil properties
|
|
97
|
+
pile_head_level_nap
|
|
98
|
+
The elevation of the pile-head, in [m] w.r.t. NAP.
|
|
99
|
+
table
|
|
100
|
+
The object with CPT results.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
soil_properties: SoilProperties
|
|
104
|
+
pile_head_level_nap: float
|
|
105
|
+
table: MaxBearingTable
|
|
106
|
+
|
|
107
|
+
def to_pandas(self) -> pd.DataFrame:
|
|
108
|
+
"""Get the pandas.DataFrame representation"""
|
|
109
|
+
return self.table.to_pandas()
|
|
110
|
+
|
|
111
|
+
def plot_bearing_capacities(
|
|
112
|
+
self,
|
|
113
|
+
axes: Optional[Axes] = None,
|
|
114
|
+
figsize: Tuple[float, float] = (8, 10),
|
|
115
|
+
add_legend: bool = True,
|
|
116
|
+
**kwargs: Any,
|
|
117
|
+
) -> Axes:
|
|
118
|
+
"""
|
|
119
|
+
Plot the bearing calculation results on an `Axes' object.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
axes:
|
|
124
|
+
Optional `Axes` object where the bearing capacities can be plotted on.
|
|
125
|
+
If not provided, a new `plt.Figure` will be activated and the `Axes`
|
|
126
|
+
object will be created and returned.
|
|
127
|
+
figsize:
|
|
128
|
+
Size of the activate figure, as the `plt.figure()` argument.
|
|
129
|
+
add_legend:
|
|
130
|
+
Add a legend to the second axes object
|
|
131
|
+
**kwargs:
|
|
132
|
+
All additional keyword arguments are passed to the `pyplot.subplots()` call.
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
axes:
|
|
137
|
+
The `Axes` object where the bearing capacities were plotted on.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
# Create axes objects if not provided
|
|
141
|
+
if axes is not None:
|
|
142
|
+
if not isinstance(axes, Axes):
|
|
143
|
+
raise ValueError(
|
|
144
|
+
"'axes' argument to plot_bearing_capacities() must be a `pyplot.axes.Axes` object or None."
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
kwargs_subplot = {
|
|
148
|
+
"figsize": figsize,
|
|
149
|
+
"tight_layout": True,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
kwargs_subplot.update(kwargs)
|
|
153
|
+
|
|
154
|
+
_, axes = plt.subplots(1, 1, **kwargs_subplot)
|
|
155
|
+
|
|
156
|
+
if not isinstance(axes, Axes):
|
|
157
|
+
raise ValueError(
|
|
158
|
+
"Could not create Axes objects. This is probably due to invalid matplotlib keyword arguments. "
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# add horizontal lines
|
|
162
|
+
axes.axhline(
|
|
163
|
+
y=self.soil_properties.groundwater_level_ref,
|
|
164
|
+
color="tab:blue",
|
|
165
|
+
linestyle="--",
|
|
166
|
+
label="Groundwater level",
|
|
167
|
+
)
|
|
168
|
+
axes.axhline(
|
|
169
|
+
y=self.soil_properties.surface_level_ref,
|
|
170
|
+
color="tab:brown",
|
|
171
|
+
linestyle="--",
|
|
172
|
+
label="Surface level",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# add bearing result subplot
|
|
176
|
+
axes.plot(
|
|
177
|
+
np.array(self.table.F_nk_d),
|
|
178
|
+
self.table.pile_tip_level_nap,
|
|
179
|
+
color="tab:orange",
|
|
180
|
+
label="Fnk;d",
|
|
181
|
+
)
|
|
182
|
+
axes.plot(
|
|
183
|
+
np.array(self.table.R_c_d_net),
|
|
184
|
+
self.table.pile_tip_level_nap,
|
|
185
|
+
label=r"Rc;net;d",
|
|
186
|
+
lw=3,
|
|
187
|
+
color="tab:blue",
|
|
188
|
+
)
|
|
189
|
+
axes.set_xlabel("Force [kN]")
|
|
190
|
+
|
|
191
|
+
# add legend
|
|
192
|
+
if add_legend:
|
|
193
|
+
axes.legend(
|
|
194
|
+
loc="upper left",
|
|
195
|
+
bbox_to_anchor=(1, 1),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# set grid
|
|
199
|
+
axes.grid()
|
|
200
|
+
|
|
201
|
+
return axes
|
|
202
|
+
|
|
203
|
+
def plot_bearing_overview(
|
|
204
|
+
self,
|
|
205
|
+
figsize: Tuple[float, float] = (10.0, 12.0),
|
|
206
|
+
width_ratios: Tuple[float, float, float] = (1, 0.1, 2),
|
|
207
|
+
add_legend: bool = True,
|
|
208
|
+
**kwargs: Any,
|
|
209
|
+
) -> Figure:
|
|
210
|
+
"""
|
|
211
|
+
Plot an overview of the bearing-capacities, including the .
|
|
212
|
+
|
|
213
|
+
Parameters
|
|
214
|
+
----------
|
|
215
|
+
figsize:
|
|
216
|
+
Size of the activate figure, as the `plt.figure()` argument.
|
|
217
|
+
width_ratios:
|
|
218
|
+
Tuple of width-ratios of the subplots, as the `plt.GridSpec` argument.
|
|
219
|
+
add_legend:
|
|
220
|
+
Add a legend to the second axes object
|
|
221
|
+
**kwargs:
|
|
222
|
+
All additional keyword arguments are passed to the `pyplot.subplots()` call.
|
|
223
|
+
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
fig:
|
|
227
|
+
The matplotlib Figure
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
kwargs_subplot = {
|
|
231
|
+
"gridspec_kw": {"width_ratios": width_ratios},
|
|
232
|
+
"sharey": "row",
|
|
233
|
+
"figsize": figsize,
|
|
234
|
+
"tight_layout": True,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
kwargs_subplot.update(kwargs)
|
|
238
|
+
|
|
239
|
+
fig, _ = plt.subplots(
|
|
240
|
+
1,
|
|
241
|
+
3,
|
|
242
|
+
**kwargs_subplot,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
ax_qc, ax_layers, ax_bearing = fig.axes
|
|
246
|
+
ax_rf = ax_qc.twiny()
|
|
247
|
+
assert isinstance(ax_rf, Axes)
|
|
248
|
+
|
|
249
|
+
# Plot bearing capacities
|
|
250
|
+
self.soil_properties.cpt_table.plot_qc(ax_qc, add_legend=False)
|
|
251
|
+
self.soil_properties.cpt_table.plot_friction_ratio(ax_rf, add_legend=False)
|
|
252
|
+
self.soil_properties.plot_layers(ax_layers, add_legend=False)
|
|
253
|
+
self.plot_bearing_capacities(axes=ax_bearing, add_legend=False)
|
|
254
|
+
|
|
255
|
+
if add_legend:
|
|
256
|
+
ax_qc_legend_handles_list = ax_qc.get_legend_handles_labels()[0]
|
|
257
|
+
ax_rf_legend_handles_list = ax_rf.get_legend_handles_labels()[0]
|
|
258
|
+
ax_layers_legend_handles_list = get_soil_layer_handles()
|
|
259
|
+
|
|
260
|
+
# Omit last 2 duplicate "bearing" handles
|
|
261
|
+
# (groundwater_level and surface_level):
|
|
262
|
+
ax_bearing_legend_handles_list = ax_bearing.get_legend_handles_labels()[0][
|
|
263
|
+
2:
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
handles_list = [
|
|
267
|
+
*ax_qc_legend_handles_list,
|
|
268
|
+
*ax_rf_legend_handles_list,
|
|
269
|
+
*ax_layers_legend_handles_list,
|
|
270
|
+
*ax_bearing_legend_handles_list,
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
ax_bearing.legend(
|
|
274
|
+
handles=handles_list,
|
|
275
|
+
loc="upper left",
|
|
276
|
+
bbox_to_anchor=(1, 1),
|
|
277
|
+
title="name: " + self.soil_properties.test_id
|
|
278
|
+
if self.soil_properties.test_id is not None
|
|
279
|
+
else "name: unknown",
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
return fig
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class MaxBearingResults:
|
|
286
|
+
"""Object containing the results for the maximum net design bearing capacity (R_c_d_net) for every CPT."""
|
|
287
|
+
|
|
288
|
+
def __init__(self, cpt_results_dict: Dict[str, MaxBearingResult]):
|
|
289
|
+
"""
|
|
290
|
+
Object containing the results for the maximum net design bearing capacity (R_c_d_net) for every CPT.
|
|
291
|
+
|
|
292
|
+
Parameters
|
|
293
|
+
----------
|
|
294
|
+
cpt_results_dict
|
|
295
|
+
The results for the maximum net design bearing capacity (R_c_d_net) for every CPT.
|
|
296
|
+
"""
|
|
297
|
+
self._cpt_results_dict = cpt_results_dict
|
|
298
|
+
|
|
299
|
+
def __getitem__(self, test_id: str) -> MaxBearingResult:
|
|
300
|
+
if not isinstance(test_id, str):
|
|
301
|
+
raise TypeError(f"Expected a test-id as a string, but got: {type(test_id)}")
|
|
302
|
+
|
|
303
|
+
return self.get_cpt_results(test_id)
|
|
304
|
+
|
|
305
|
+
@property
|
|
306
|
+
def cpt_results_dict(self) -> Dict[str, MaxBearingResult]:
|
|
307
|
+
"""The dictionary with the MaxBearingResult for each CPT."""
|
|
308
|
+
return self._cpt_results_dict
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def test_ids(self) -> List[str]:
|
|
312
|
+
"""The test-ids of the CPTs."""
|
|
313
|
+
return list(self.cpt_results_dict.keys())
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def results(self) -> List[MaxBearingResult]:
|
|
317
|
+
"""The computed results, as a list of MaxBearingResult objects."""
|
|
318
|
+
return list(self.cpt_results_dict.values())
|
|
319
|
+
|
|
320
|
+
def get_cpt_results(self, test_id: str) -> MaxBearingResult:
|
|
321
|
+
"""
|
|
322
|
+
Returns the `MaxBearingResult` object for the provided test_id.
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
if test_id not in self.cpt_results_dict.keys():
|
|
326
|
+
raise ValueError(
|
|
327
|
+
f"No Cpt-results were calculated for this test-id: {test_id}. "
|
|
328
|
+
"Please check the spelling or run a new calculation for this CPT."
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
return self.cpt_results_dict[test_id]
|
|
332
|
+
|
|
333
|
+
def get_results_per_cpt(self, column_name: str) -> pd.DataFrame:
|
|
334
|
+
"""
|
|
335
|
+
Returns a pandas dataframe with a single result-item, organized per CPT
|
|
336
|
+
(test-id) and pile-tip-level-nap.
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
column_name:
|
|
341
|
+
The name of the result-item / column name of the single-cpt-results table.
|
|
342
|
+
"""
|
|
343
|
+
if column_name not in self.to_pandas().columns or column_name in [
|
|
344
|
+
"pile_tip_level_nap",
|
|
345
|
+
"test_id",
|
|
346
|
+
]:
|
|
347
|
+
raise ValueError("Invalid column_name provided.")
|
|
348
|
+
|
|
349
|
+
results = pd.pivot(
|
|
350
|
+
self.to_pandas(),
|
|
351
|
+
values=column_name,
|
|
352
|
+
index="pile_tip_level_nap",
|
|
353
|
+
columns="test_id",
|
|
354
|
+
)
|
|
355
|
+
return results.sort_values("pile_tip_level_nap", ascending=False)
|
|
356
|
+
|
|
357
|
+
@lru_cache
|
|
358
|
+
def to_pandas(self) -> pd.DataFrame:
|
|
359
|
+
"""Returns a total overview of all single-cpt results in a pandas.DataFrame representation."""
|
|
360
|
+
df_list: List[pd.DataFrame] = []
|
|
361
|
+
|
|
362
|
+
for test_id in self.cpt_results_dict:
|
|
363
|
+
df = self.cpt_results_dict[test_id].table.to_pandas()
|
|
364
|
+
df = df.assign(test_id=test_id)
|
|
365
|
+
df = df.assign(x=self.cpt_results_dict[test_id].soil_properties.x)
|
|
366
|
+
df = df.assign(y=self.cpt_results_dict[test_id].soil_properties.y)
|
|
367
|
+
df_list.append(df)
|
|
368
|
+
|
|
369
|
+
cpt_results_df = pd.concat(df_list)
|
|
370
|
+
cpt_results_df = cpt_results_df.assign(
|
|
371
|
+
pile_tip_level_nap=cpt_results_df.pile_tip_level_nap.round(1)
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
return cpt_results_df
|
|
375
|
+
|
|
376
|
+
@lru_cache()
|
|
377
|
+
def triangulation(self, pile_tip_level_nap: float) -> List[Dict[str, list]]:
|
|
378
|
+
"""
|
|
379
|
+
Delaunay tessellation based on the CPT location
|
|
380
|
+
|
|
381
|
+
Returns
|
|
382
|
+
-------
|
|
383
|
+
collection: List
|
|
384
|
+
A list of dictionaries containing the tessellation
|
|
385
|
+
geometry and corresponding cpt names:
|
|
386
|
+
|
|
387
|
+
- geometry: List[Tuple[float, float]]
|
|
388
|
+
- test_id: List[str]
|
|
389
|
+
|
|
390
|
+
"""
|
|
391
|
+
_lookup = {
|
|
392
|
+
(point.soil_properties.x, point.soil_properties.y): key
|
|
393
|
+
for key, point in self.cpt_results_dict.items()
|
|
394
|
+
}
|
|
395
|
+
# select point with valid bearing capacity at pile tip level
|
|
396
|
+
_points = (
|
|
397
|
+
self.to_pandas()
|
|
398
|
+
.loc[
|
|
399
|
+
(self.to_pandas()["pile_tip_level_nap"] == pile_tip_level_nap)
|
|
400
|
+
& (~pd.isna(self.to_pandas()["R_c_d_net"])),
|
|
401
|
+
["x", "y"],
|
|
402
|
+
]
|
|
403
|
+
.to_numpy()
|
|
404
|
+
.tolist()
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# check if enough points Delaunay
|
|
408
|
+
if len(_points) < 4:
|
|
409
|
+
raise ValueError(
|
|
410
|
+
"Not enough points at this pile tip level to construct "
|
|
411
|
+
"the delaunay tessellation based on the CPT location."
|
|
412
|
+
)
|
|
413
|
+
tri = Delaunay(
|
|
414
|
+
_points,
|
|
415
|
+
incremental=False,
|
|
416
|
+
furthest_site=False,
|
|
417
|
+
qhull_options="Qbb",
|
|
418
|
+
)
|
|
419
|
+
geometries = np.array(_points)[tri.simplices]
|
|
420
|
+
|
|
421
|
+
return [
|
|
422
|
+
{
|
|
423
|
+
"geometry": geometry.tolist(),
|
|
424
|
+
"test_id": [_lookup[(xy[0], xy[1])] for xy in geometry],
|
|
425
|
+
}
|
|
426
|
+
for geometry in geometries
|
|
427
|
+
]
|
|
428
|
+
|
|
429
|
+
def plot(
|
|
430
|
+
self,
|
|
431
|
+
projection: Optional[Literal["3d"]] = "3d",
|
|
432
|
+
hue: Literal["colormap", "category"] = "colormap",
|
|
433
|
+
pile_load_uls: float = 100,
|
|
434
|
+
figsize: Tuple[int, int] | None = None,
|
|
435
|
+
**kwargs: Any,
|
|
436
|
+
) -> plt.Figure:
|
|
437
|
+
"""
|
|
438
|
+
Plot a 3D scatterplot of the valid ULS load.
|
|
439
|
+
|
|
440
|
+
Parameters
|
|
441
|
+
----------
|
|
442
|
+
projection
|
|
443
|
+
default is 3d
|
|
444
|
+
The projection type of the subplot. use None to create a 2D plot
|
|
445
|
+
hue
|
|
446
|
+
default is colormap
|
|
447
|
+
The marker colors methode. If colormap is used the colors represent the `R_c_d_net` value.
|
|
448
|
+
The category option sets the colors to valid ULS loads. Please use the pile_load_uls attribute to set
|
|
449
|
+
the required bearing capacity.
|
|
450
|
+
pile_load_uls
|
|
451
|
+
default is 100 kN
|
|
452
|
+
ULS load in kN. Used to determine if a pile tip level configuration is valid.
|
|
453
|
+
figsize:
|
|
454
|
+
Size of the activate figure, as the `plt.figure()` argument.
|
|
455
|
+
**kwargs:
|
|
456
|
+
All additional keyword arguments are passed to the `pyplot.subplots()` call.
|
|
457
|
+
|
|
458
|
+
Returns
|
|
459
|
+
-------
|
|
460
|
+
figure:
|
|
461
|
+
The `Figure` object where the data was plotted on.
|
|
462
|
+
"""
|
|
463
|
+
kwargs_subplot = {
|
|
464
|
+
"figsize": figsize,
|
|
465
|
+
"tight_layout": True,
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
kwargs_subplot.update(kwargs)
|
|
469
|
+
fig = plt.figure(**kwargs_subplot)
|
|
470
|
+
axes = fig.add_subplot(projection=projection)
|
|
471
|
+
df = self.to_pandas().dropna()
|
|
472
|
+
# create color list based on hue option
|
|
473
|
+
if hue == "category":
|
|
474
|
+
colors = [
|
|
475
|
+
"red" if var < pile_load_uls else "green" for var in df["R_c_d_net"]
|
|
476
|
+
]
|
|
477
|
+
else:
|
|
478
|
+
colors = df["R_c_d_net"].tolist()
|
|
479
|
+
# create scatter plot
|
|
480
|
+
if projection == "3d":
|
|
481
|
+
cmap = axes.scatter(
|
|
482
|
+
df["x"],
|
|
483
|
+
df["y"],
|
|
484
|
+
df["pile_tip_level_nap"],
|
|
485
|
+
c=colors,
|
|
486
|
+
)
|
|
487
|
+
axes.set_xlabel("X")
|
|
488
|
+
axes.set_ylabel("Y")
|
|
489
|
+
axes.set_zlabel("Z [m w.r.t NAP]")
|
|
490
|
+
|
|
491
|
+
# set cpt names
|
|
492
|
+
for key, result in self.cpt_results_dict.items():
|
|
493
|
+
axes.text(
|
|
494
|
+
result.soil_properties.x,
|
|
495
|
+
result.soil_properties.y,
|
|
496
|
+
result.table.pile_tip_level_nap.max(),
|
|
497
|
+
key,
|
|
498
|
+
"z",
|
|
499
|
+
)
|
|
500
|
+
else:
|
|
501
|
+
cmap = axes.scatter(
|
|
502
|
+
df["test_id"],
|
|
503
|
+
df["pile_tip_level_nap"],
|
|
504
|
+
c=colors,
|
|
505
|
+
)
|
|
506
|
+
axes.set_ylabel("Z [m w.r.t NAP]")
|
|
507
|
+
axes.tick_params(axis="x", labelrotation=90)
|
|
508
|
+
axes.grid()
|
|
509
|
+
|
|
510
|
+
if hue == "category":
|
|
511
|
+
fig.legend(
|
|
512
|
+
title="$R_{c;d;net}$ [kN]",
|
|
513
|
+
title_fontsize=18,
|
|
514
|
+
fontsize=15,
|
|
515
|
+
loc="lower right",
|
|
516
|
+
handles=[
|
|
517
|
+
patches.Patch(
|
|
518
|
+
facecolor=color,
|
|
519
|
+
label=label,
|
|
520
|
+
alpha=0.9,
|
|
521
|
+
linewidth=2,
|
|
522
|
+
edgecolor="black",
|
|
523
|
+
)
|
|
524
|
+
for label, color in zip(
|
|
525
|
+
[f">= {pile_load_uls}", f"< {pile_load_uls}"],
|
|
526
|
+
["green", "red"],
|
|
527
|
+
)
|
|
528
|
+
],
|
|
529
|
+
)
|
|
530
|
+
else:
|
|
531
|
+
fig.colorbar(cmap, orientation="vertical", label="$R_{c;d;net}$ [kN]")
|
|
532
|
+
|
|
533
|
+
return fig
|
|
534
|
+
|
|
535
|
+
def map(
|
|
536
|
+
self,
|
|
537
|
+
pile_tip_level_nap: float,
|
|
538
|
+
pile_load_uls: float = 100,
|
|
539
|
+
show_delaunay_vertices: bool = True,
|
|
540
|
+
show_voronoi_vertices: bool = False,
|
|
541
|
+
figsize: Tuple[int, int] | None = None,
|
|
542
|
+
**kwargs: Any,
|
|
543
|
+
) -> plt.Figure:
|
|
544
|
+
"""
|
|
545
|
+
Plot a map of the valid ULS load for a given depth.
|
|
546
|
+
|
|
547
|
+
Note
|
|
548
|
+
------
|
|
549
|
+
Based on the Delaunay methode a tessellation is created with
|
|
550
|
+
the location of the CPT's. Each triangle is then colored according to
|
|
551
|
+
the bearing capacity of the CPT its based on. If any of the CPT does
|
|
552
|
+
not meet the required capacity the triangle becomes also invalid.
|
|
553
|
+
|
|
554
|
+
Warnings
|
|
555
|
+
--------
|
|
556
|
+
Please note that this map indication of valid ULS zones is intended as a visual aid to help
|
|
557
|
+
the geotechnical engineer. It does not necessarily comply with the NEN 9997-1+C2:2017 since the NEN is open
|
|
558
|
+
to interpretation. It is therefore that the interpretation provided by this methode must be carefully
|
|
559
|
+
validated by a geotechnical engineer.
|
|
560
|
+
|
|
561
|
+
Parameters
|
|
562
|
+
----------
|
|
563
|
+
pile_tip_level_nap:
|
|
564
|
+
Pile tip level to generate map.
|
|
565
|
+
pile_load_uls
|
|
566
|
+
default is 100 kN
|
|
567
|
+
ULS load in kN. Used to determine if a pile tip level configuration is valid.
|
|
568
|
+
show_delaunay_vertices
|
|
569
|
+
default is True
|
|
570
|
+
Add delaunay vertices to the figure
|
|
571
|
+
show_voronoi_vertices
|
|
572
|
+
default is False
|
|
573
|
+
Add voronoi vertices to the figure
|
|
574
|
+
figsize:
|
|
575
|
+
Size of the activate figure, as the `plt.figure()` argument.
|
|
576
|
+
**kwargs:
|
|
577
|
+
All additional keyword arguments are passed to the `pyplot.subplots()` call.
|
|
578
|
+
|
|
579
|
+
Returns
|
|
580
|
+
-------
|
|
581
|
+
figure:
|
|
582
|
+
The `Figure` object where the data was plotted on.
|
|
583
|
+
"""
|
|
584
|
+
kwargs_subplot = {
|
|
585
|
+
"figsize": figsize,
|
|
586
|
+
"tight_layout": True,
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
kwargs_subplot.update(kwargs)
|
|
590
|
+
fig, axes = plt.subplots(**kwargs_subplot)
|
|
591
|
+
|
|
592
|
+
# filter data
|
|
593
|
+
df = (
|
|
594
|
+
self.to_pandas()
|
|
595
|
+
.loc[self.to_pandas()["pile_tip_level_nap"] == pile_tip_level_nap]
|
|
596
|
+
.dropna()
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
if df.empty:
|
|
600
|
+
raise ValueError(
|
|
601
|
+
"Pile tip level is not valid pile tip level. "
|
|
602
|
+
"Please select one of the following pile tip level: "
|
|
603
|
+
f"[{(self.to_pandas()['pile_tip_level_nap']).unique()}]"
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
df["valid"] = [
|
|
607
|
+
False if var < pile_load_uls else True for var in df["R_c_d_net"]
|
|
608
|
+
]
|
|
609
|
+
|
|
610
|
+
# iterate over geometry
|
|
611
|
+
if show_delaunay_vertices:
|
|
612
|
+
_patches = []
|
|
613
|
+
for tri in self.triangulation(pile_tip_level_nap):
|
|
614
|
+
color = (
|
|
615
|
+
"green"
|
|
616
|
+
if all(
|
|
617
|
+
df.where(df["test_id"].isin(tri["test_id"])).dropna()["valid"]
|
|
618
|
+
)
|
|
619
|
+
else "red"
|
|
620
|
+
)
|
|
621
|
+
_patches.append(
|
|
622
|
+
patches.Polygon(
|
|
623
|
+
np.array(tri["geometry"]), facecolor=color, edgecolor="grey"
|
|
624
|
+
)
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
collection = PatchCollection(_patches, match_original=True)
|
|
628
|
+
axes.add_collection(collection)
|
|
629
|
+
|
|
630
|
+
if show_voronoi_vertices:
|
|
631
|
+
points = [
|
|
632
|
+
(point.soil_properties.x, point.soil_properties.y)
|
|
633
|
+
for point in self.cpt_results_dict.values()
|
|
634
|
+
]
|
|
635
|
+
vor = Voronoi(points)
|
|
636
|
+
voronoi_plot_2d(
|
|
637
|
+
vor,
|
|
638
|
+
show_vertices=False,
|
|
639
|
+
show_points=False,
|
|
640
|
+
ax=axes,
|
|
641
|
+
line_colors="black",
|
|
642
|
+
line_alpha=0.7,
|
|
643
|
+
line_width=0.1,
|
|
644
|
+
point_size=0.0,
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
# add the cpt names
|
|
648
|
+
axes.scatter(
|
|
649
|
+
df["x"],
|
|
650
|
+
df["y"],
|
|
651
|
+
c=["green" if val else "red" for val in df["valid"]],
|
|
652
|
+
)
|
|
653
|
+
for label, x, y in zip(df["test_id"], df["x"], df["y"]):
|
|
654
|
+
axes.annotate(label, xy=(x, y), xytext=(3, 3), textcoords="offset points")
|
|
655
|
+
axes.set_xlabel("X")
|
|
656
|
+
axes.set_ylabel("Y")
|
|
657
|
+
axes.ticklabel_format(useOffset=False)
|
|
658
|
+
fig.legend(
|
|
659
|
+
title="$R_{c;d;net}$ [kN]",
|
|
660
|
+
title_fontsize=18,
|
|
661
|
+
fontsize=15,
|
|
662
|
+
loc="lower right",
|
|
663
|
+
handles=[
|
|
664
|
+
patches.Patch(
|
|
665
|
+
facecolor=color,
|
|
666
|
+
label=label,
|
|
667
|
+
alpha=0.9,
|
|
668
|
+
linewidth=2,
|
|
669
|
+
edgecolor="black",
|
|
670
|
+
)
|
|
671
|
+
for label, color in zip(
|
|
672
|
+
[f">= {pile_load_uls}", f"< {pile_load_uls}"],
|
|
673
|
+
["green", "red"],
|
|
674
|
+
)
|
|
675
|
+
],
|
|
676
|
+
)
|
|
677
|
+
axes.set_title(f"Pile tip level at: {pile_tip_level_nap} [m w.r.t NAP]")
|
|
678
|
+
|
|
679
|
+
return fig
|