py-pilecore 0.4.2__py3-none-any.whl → 0.5.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,435 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from typing import Any, Dict, Literal, Tuple
5
+
6
+ import matplotlib.patches as patches
7
+ import numpy as np
8
+ from matplotlib import pyplot as plt
9
+ from matplotlib.axes import Axes
10
+ from numpy.typing import NDArray
11
+
12
+ from pypilecore.common.piles.geometry.components.common import (
13
+ PrimaryPileComponentDimension,
14
+ _BasePileGeometryComponent,
15
+ get_area_vs_depth,
16
+ get_circum_vs_depth,
17
+ get_component_bounds_nap,
18
+ instantiate_axes,
19
+ )
20
+
21
+
22
+ class RoundPileGeometryComponent(_BasePileGeometryComponent):
23
+ """The RoundPileGeometryComponent class represents a round pile-geometry component."""
24
+
25
+ def __init__(
26
+ self,
27
+ diameter: float,
28
+ primary_dimension: PrimaryPileComponentDimension,
29
+ inner_component: _BasePileGeometryComponent | None = None,
30
+ material: str | None = None,
31
+ ):
32
+ """
33
+ Represents a round pile-geometry component.
34
+
35
+ Parameters:
36
+ -----------
37
+ diameter : float
38
+ The outer-diameter [m] of the pile-geometry component.
39
+ primary_dimension : PrimaryPileComponentDimension
40
+ The primary dimension [m] of the pile-geometry component, which is measured along the primary axis of the pile.
41
+ inner_component : RoundPileGeometryComponent | RectPileGeometryComponent | None, optional
42
+ The component on the inside of the pile-geometry component, by default None.
43
+ material : str, optional
44
+ The material name of the pile-geometry component, by default None.
45
+ """
46
+ self._diameter = diameter
47
+ self._primary_dimension = primary_dimension
48
+ self._inner_component = inner_component
49
+ self._material = material
50
+
51
+ @classmethod
52
+ def from_api_response(
53
+ cls,
54
+ component: dict,
55
+ inner_component: _BasePileGeometryComponent | None = None,
56
+ ) -> RoundPileGeometryComponent:
57
+ """
58
+ Instantiates a RoundPileGeometryComponent from a component object in the API
59
+ response payload.
60
+
61
+ Parameters:
62
+ -----------
63
+ component: dict
64
+ A dictionary that represents the component object retrieved from the API response payload.
65
+ The dictionary should have the following schema:
66
+ {
67
+ "diameter": float, # The diameter of the round pile component.
68
+ "primary_dimension": {
69
+ "length": float, # The length of the primary dimension of the round pile component.
70
+ },
71
+ "material": str # The material of the round pile component.
72
+ }
73
+ inner_component: RoundPileGeometryComponent | RectPileGeometryComponent | None, optional
74
+ The component on the inside of the pile-geometry component, by default None.
75
+
76
+ Returns:
77
+ --------
78
+ RoundPileGeometryComponent
79
+ A round pile-geometry component.
80
+
81
+ Example:
82
+ --------
83
+ >>> component = {
84
+ ... "diameter": 10,
85
+ ... "primary_dimension": {
86
+ ... "length": 20,
87
+ ... "width": 30
88
+ ... },
89
+ ... "material": "concrete"
90
+ ... }
91
+ >>> inner_component = RectPileGeometryComponent(...)
92
+ >>> round_component = RoundPileGeometryComponent.from_api_response(component, inner_component)
93
+ """
94
+ return cls(
95
+ diameter=component["diameter"],
96
+ primary_dimension=PrimaryPileComponentDimension.from_api_response(
97
+ component["primary_dimension"]
98
+ ),
99
+ inner_component=inner_component,
100
+ material=component["material"],
101
+ )
102
+
103
+ @property
104
+ def inner_component(
105
+ self,
106
+ ) -> _BasePileGeometryComponent | None:
107
+ """The component on the inside of the pile-geometry component"""
108
+ return self._inner_component
109
+
110
+ @property
111
+ def outer_shape(self) -> Literal["round"]:
112
+ """The outer shape of the pile-geometry component"""
113
+ return "round"
114
+
115
+ @property
116
+ def material(self) -> str | None:
117
+ """The material name of the pile-geometry component"""
118
+ return self._material
119
+
120
+ @property
121
+ def primary_dimension(self) -> PrimaryPileComponentDimension:
122
+ """
123
+ The primary dimension [m] of the pile-geometry component, which is measured along the primary axis of the pile.
124
+ """
125
+ return self._primary_dimension
126
+
127
+ @property
128
+ def cross_section_bounds(self) -> Tuple[float, float, float, float]:
129
+ """Alias of the diameter [m] of the pile-geometry component"""
130
+ return (
131
+ -self.diameter / 2,
132
+ self.diameter / 2,
133
+ -self.diameter / 2,
134
+ self.diameter / 2,
135
+ )
136
+
137
+ @property
138
+ def diameter(self) -> float:
139
+ """The outer-diameter [m] of the pile-geometry component"""
140
+ return self._diameter
141
+
142
+ @property
143
+ def radius(self) -> float:
144
+ """The outer-radius [m] of the pile-geometry component"""
145
+ return self.diameter / 2
146
+
147
+ @property
148
+ def circumference(self) -> float:
149
+ """The outer-circumference [m] of the pile-geometry component"""
150
+ return self.diameter * math.pi
151
+
152
+ @property
153
+ def equiv_tip_diameter(self) -> float:
154
+ """
155
+ Equivalent diameter [m] of the component at tip-level.
156
+ """
157
+ return self.diameter
158
+
159
+ @property
160
+ def area_full(self) -> float:
161
+ """The full outer-area [m²] of the pile-geometry component, including any potential inner-components"""
162
+ return (self.diameter / 2) ** 2 * math.pi
163
+
164
+ def serialize_payload(
165
+ self,
166
+ ) -> Dict[str, str | float | Dict[str, float | None] | None]:
167
+ """
168
+ Serialize the round pile-geometry component to a dictionary payload for the API.
169
+
170
+ Returns:
171
+ A dictionary payload containing the outer shape, diameter, material, and primary dimension (if set).
172
+ """
173
+ return {
174
+ "outer_shape": self.outer_shape,
175
+ "primary_dimension": self.primary_dimension.serialize_payload(),
176
+ "diameter": self.diameter,
177
+ "material": self.material,
178
+ }
179
+
180
+ def get_component_bounds_nap(
181
+ self,
182
+ pile_tip_level_nap: float | int,
183
+ pile_head_level_nap: float | int,
184
+ ) -> Tuple[float, float]:
185
+ """
186
+ Returns component head and tip level in NAP.
187
+
188
+ Parameters
189
+ ----------
190
+ pile_tip_level_nap : float
191
+ pile tip level in [m] w.r.t. NAP.
192
+ pile_head_level_nap : float
193
+ pile head level in [m] w.r.t. NAP.
194
+
195
+ Returns
196
+ -------
197
+ tuple
198
+ Tuple with component head and tip level in [m] w.r.t. NAP.
199
+ """
200
+ return get_component_bounds_nap(
201
+ pile_tip_level_nap=pile_tip_level_nap,
202
+ pile_head_level_nap=pile_head_level_nap,
203
+ component_primary_length=self.primary_dimension.length,
204
+ )
205
+
206
+ def get_circum_vs_depth(
207
+ self,
208
+ depth_nap: NDArray[np.floating],
209
+ pile_tip_level_nap: float | int,
210
+ pile_head_level_nap: float | int,
211
+ ) -> NDArray[np.floating]:
212
+ """
213
+ Returns component circumferences at requested depths.
214
+
215
+ Parameters
216
+ ----------
217
+ depth_nap : np.array
218
+ Array with depths in [m] w.r.t. NAP.
219
+ pile_tip_level_nap : float
220
+ pile tip level in [m] w.r.t. NAP.
221
+ pile_head_level_nap : float
222
+ pile head level in [m] w.r.t. NAP.
223
+
224
+ Returns
225
+ -------
226
+ np.array
227
+ Array with component circumferences at the depths in the depth parameter.
228
+ """
229
+ return get_circum_vs_depth(
230
+ depth_nap=depth_nap,
231
+ pile_tip_level_nap=pile_tip_level_nap,
232
+ pile_head_level_nap=pile_head_level_nap,
233
+ length=self.primary_dimension.length,
234
+ circumference=self.circumference,
235
+ )
236
+
237
+ def get_inner_area_vs_depth(
238
+ self,
239
+ depth_nap: NDArray[np.floating],
240
+ pile_tip_level_nap: float | int,
241
+ pile_head_level_nap: float | int,
242
+ ) -> NDArray[np.floating]:
243
+ """
244
+ Returns inner component areas at requested depths.
245
+
246
+ Parameters
247
+ ----------
248
+ depth_nap : np.array
249
+ Array with depths in [m] w.r.t. NAP.
250
+ pile_tip_level_nap : float
251
+ pile tip level in [m] w.r.t. NAP.
252
+ pile_head_level_nap : float
253
+ pile head level in [m] w.r.t. NAP.
254
+
255
+ Returns
256
+ -------
257
+ np.array
258
+ Array with inner component areas at the depths in the depth parameter.
259
+ """
260
+ if self.inner_component is None:
261
+ return np.zeros_like(depth_nap)
262
+
263
+ return self.inner_component.get_area_vs_depth(
264
+ depth_nap=depth_nap,
265
+ pile_tip_level_nap=pile_tip_level_nap,
266
+ pile_head_level_nap=pile_head_level_nap,
267
+ )
268
+
269
+ def get_area_vs_depth(
270
+ self,
271
+ depth_nap: NDArray[np.floating],
272
+ pile_tip_level_nap: float | int,
273
+ pile_head_level_nap: float | int,
274
+ ) -> NDArray[np.floating]:
275
+ """
276
+ Returns component areas at requested depths.
277
+
278
+ Parameters
279
+ ----------
280
+ depth_nap : np.array
281
+ Array with depths in [m] w.r.t. NAP.
282
+ pile_tip_level_nap : float
283
+ pile tip level in [m] w.r.t. NAP.
284
+ pile_head_level_nap : float
285
+ pile head level in [m] w.r.t. NAP.
286
+
287
+ Returns
288
+ -------
289
+ np.array
290
+ Array with component areas at the depths in the depth parameter.
291
+ """
292
+
293
+ (
294
+ component_head_level_nap,
295
+ component_tip_level_nap,
296
+ ) = self.get_component_bounds_nap(pile_tip_level_nap, pile_head_level_nap)
297
+
298
+ inner_area = self.get_inner_area_vs_depth(
299
+ depth_nap=depth_nap,
300
+ pile_tip_level_nap=pile_tip_level_nap,
301
+ pile_head_level_nap=pile_head_level_nap,
302
+ )
303
+
304
+ return get_area_vs_depth(
305
+ depth_nap=depth_nap,
306
+ area_full=self.area_full,
307
+ component_head_level_nap=component_head_level_nap,
308
+ component_tip_level_nap=component_tip_level_nap,
309
+ inner_area=inner_area,
310
+ )
311
+
312
+ def plot_cross_section_exterior(
313
+ self,
314
+ figsize: Tuple[float, float] = (6.0, 6.0),
315
+ facecolor: Tuple[float, float, float] | str | None = None,
316
+ axes: Axes | None = None,
317
+ axis_arg: bool | str | Tuple[float, float, float, float] | None = "auto",
318
+ show: bool = True,
319
+ **kwargs: Any,
320
+ ) -> Axes:
321
+ """
322
+ Plot the cross-section of the component at a specified depth.
323
+
324
+ Parameters
325
+ ----------
326
+ figsize : tuple, optional
327
+ The figure size (width, height) in inches, by default (6.0, 6.0).
328
+ facecolor : tuple or str, optional
329
+ The face color of the pile cross-section, by default None.
330
+ axes : Axes
331
+ The axes object to plot the cross-section on.
332
+ axis_arg : bool or str or tuple, optional
333
+ The axis argument to pass to the `axes.axis()` function, by default "auto".
334
+ show : bool, optional
335
+ Whether to display the plot, by default True.
336
+ **kwargs
337
+ Additional keyword arguments to pass to the `plt.subplots()` function.
338
+ """
339
+ axes = instantiate_axes(
340
+ figsize=figsize,
341
+ axes=axes,
342
+ **kwargs,
343
+ )
344
+ axes.add_patch(
345
+ patches.Circle((0, 0), self.radius, facecolor=facecolor, edgecolor="black")
346
+ )
347
+ if axis_arg:
348
+ axes.axis(axis_arg)
349
+ if show:
350
+ plt.show()
351
+ return axes
352
+
353
+ def plot_side_view(
354
+ self,
355
+ bottom_boundary_nap: float | Literal["pile_tip"] = "pile_tip",
356
+ top_boundary_nap: float | Literal["pile_head"] = "pile_head",
357
+ pile_tip_level_nap: float | int = -10,
358
+ pile_head_level_nap: float | int = 0,
359
+ figsize: Tuple[float, float] = (6.0, 6.0),
360
+ facecolor: Tuple[float, float, float] | str | None = None,
361
+ axes: Axes | None = None,
362
+ axis_arg: bool | str | Tuple[float, float, float, float] | None = "scaled",
363
+ show: bool = True,
364
+ **kwargs: Any,
365
+ ) -> Axes:
366
+ """
367
+ Plot the side view of the component at a specified depth.
368
+
369
+ Parameters
370
+ ----------
371
+ bottom_boundary_nap : float or str, optional
372
+ The bottom boundary level of the plot, in m w.r.t. NAP. Default = "pile_tip".
373
+ top_boundary_nap : float or str, optional
374
+ The top boundary level of the plot, in m w.r.t. NAP. Default = "pile_head".
375
+ pile_tip_level_nap : float, optional
376
+ The pile tip level in m w.r.t. NAP. Default = -10.
377
+ pile_head_level_nap : float, optional
378
+ The pile head level in m w.r.t. NAP. Default = 0.
379
+ figsize : tuple, optional
380
+ The figure size (width, height) in inches, by default (6.0, 6.0).
381
+ facecolor : tuple or str, optional
382
+ The face color of the pile cross-section, by default None.
383
+ axes : Axes
384
+ The axes object to plot the cross-section on.
385
+ axis_arg : bool or str or tuple, optional
386
+ The axis argument to pass to the `axes.axis()` function, by default "auto".
387
+ show : bool, optional
388
+ Whether to display the plot, by default True.
389
+
390
+ Returns
391
+ -------
392
+ Axes
393
+ The axes object to plot the cross-section on.
394
+ """
395
+ axes = instantiate_axes(
396
+ figsize=figsize,
397
+ axes=axes,
398
+ **kwargs,
399
+ )
400
+
401
+ if top_boundary_nap == "pile_head":
402
+ top_boundary_nap = pile_head_level_nap
403
+
404
+ if bottom_boundary_nap == "pile_tip":
405
+ bottom_boundary_nap = pile_tip_level_nap
406
+
407
+ (
408
+ component_head_level_nap,
409
+ component_tip_level_nap,
410
+ ) = self.get_component_bounds_nap(pile_tip_level_nap, pile_head_level_nap)
411
+
412
+ if (
413
+ top_boundary_nap > component_tip_level_nap
414
+ and bottom_boundary_nap < component_head_level_nap
415
+ ):
416
+ z_offset = component_head_level_nap
417
+ height = (
418
+ max(component_tip_level_nap, bottom_boundary_nap)
419
+ - component_head_level_nap
420
+ )
421
+
422
+ axes.add_patch(
423
+ patches.Rectangle(
424
+ (self.cross_section_bounds[0], z_offset),
425
+ self.diameter,
426
+ height,
427
+ facecolor=facecolor,
428
+ )
429
+ )
430
+
431
+ if axis_arg:
432
+ axes.axis(axis_arg)
433
+ if show:
434
+ plt.show()
435
+ return axes