py-pilecore 0.4.0__tar.gz → 0.4.2__tar.gz

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.

Files changed (30) hide show
  1. {py-pilecore-0.4.0/src/py_pilecore.egg-info → py_pilecore-0.4.2}/PKG-INFO +2 -1
  2. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/pyproject.toml +2 -1
  3. {py-pilecore-0.4.0 → py_pilecore-0.4.2/src/py_pilecore.egg-info}/PKG-INFO +2 -1
  4. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/py_pilecore.egg-info/requires.txt +1 -0
  5. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/_version.py +1 -1
  6. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/input/grouper_properties.py +1 -2
  7. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/input/multi_cpt.py +4 -4
  8. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/input/pile_properties.py +3 -2
  9. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/input/soil_properties.py +1 -1
  10. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/results/grouper_result.py +7 -4
  11. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/results/multi_cpt_results.py +161 -0
  12. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/results/post_processing.py +211 -9
  13. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/tests/test_input.py +23 -0
  14. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/LICENSE +0 -0
  15. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/README.md +0 -0
  16. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/setup.cfg +0 -0
  17. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/py_pilecore.egg-info/SOURCES.txt +0 -0
  18. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/py_pilecore.egg-info/dependency_links.txt +0 -0
  19. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/py_pilecore.egg-info/top_level.txt +0 -0
  20. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/__init__.py +0 -0
  21. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/api.py +0 -0
  22. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/exceptions.py +0 -0
  23. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/input/__init__.py +0 -0
  24. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/plot_utils.py +0 -0
  25. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/results/__init__.py +0 -0
  26. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/results/load_settlement.py +0 -0
  27. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/results/pile_properties.py +0 -0
  28. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/results/single_cpt_results.py +0 -0
  29. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/results/soil_properties.py +0 -0
  30. {py-pilecore-0.4.0 → py_pilecore-0.4.2}/src/pypilecore/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: py-pilecore
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Public python SDK for the CEMS PileCore web-API.
5
5
  License: MIT License
6
6
 
@@ -37,6 +37,7 @@ Requires-Dist: matplotlib<4,>=3.8
37
37
  Requires-Dist: tqdm[notebook]<5,>4
38
38
  Requires-Dist: natsort<9,>8
39
39
  Requires-Dist: shapely<3,>=2
40
+ Requires-Dist: scipy<2,>=1
40
41
  Provides-Extra: test
41
42
  Requires-Dist: coveralls; extra == "test"
42
43
  Requires-Dist: pytest; extra == "test"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "py-pilecore"
7
- version = "0.4.0"
7
+ version = "0.4.2"
8
8
  description = "Public python SDK for the CEMS PileCore web-API."
9
9
  requires-python = ">=3.9"
10
10
  dependencies = [
@@ -16,6 +16,7 @@ dependencies = [
16
16
  "tqdm[notebook]>4,<5",
17
17
  "natsort>8,<9",
18
18
  "shapely>=2,<3",
19
+ "scipy>=1, <2",
19
20
  ]
20
21
  license = { file = "LICENSE" }
21
22
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: py-pilecore
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Public python SDK for the CEMS PileCore web-API.
5
5
  License: MIT License
6
6
 
@@ -37,6 +37,7 @@ Requires-Dist: matplotlib<4,>=3.8
37
37
  Requires-Dist: tqdm[notebook]<5,>4
38
38
  Requires-Dist: natsort<9,>8
39
39
  Requires-Dist: shapely<3,>=2
40
+ Requires-Dist: scipy<2,>=1
40
41
  Provides-Extra: test
41
42
  Requires-Dist: coveralls; extra == "test"
42
43
  Requires-Dist: pytest; extra == "test"
@@ -6,6 +6,7 @@ matplotlib<4,>=3.8
6
6
  tqdm[notebook]<5,>4
7
7
  natsort<9,>8
8
8
  shapely<3,>=2
9
+ scipy<2,>=1
9
10
 
10
11
  [docs]
11
12
  Sphinx==6.1.3
@@ -4,4 +4,4 @@ try:
4
4
  __version__ = version("py-pilecore")
5
5
  # during CI
6
6
  except PackageNotFoundError:
7
- __version__ = "0.4.0"
7
+ __version__ = "0.4.2"
@@ -43,7 +43,7 @@ def create_grouper_payload(
43
43
  "/grouper/group_cpts"
44
44
 
45
45
 
46
- Notes
46
+ Note
47
47
  ------
48
48
  The grouper uses pile bearing capacity results calculated by PileCore or other software to
49
49
  form subgroups of the total group of CPT’s belonging to this project.
@@ -56,7 +56,6 @@ def create_grouper_payload(
56
56
  - is spatially coherent, which means there are no other CPTs in between the members
57
57
  of the subgroup. (Spatial check)
58
58
 
59
-
60
59
  Additionally, centre to centre validation (include_centre_to_centre_check; NEN9997-1 3.2.3) can be
61
60
  added to the cluster method. This check adds restrictions to the maximum allowable R;c;cal outliers
62
61
  and makes sure that the suitable data density requirements for the subgroup are met, by checking the
@@ -313,9 +313,9 @@ def create_multi_cpt_payload(
313
313
  individual_ocr=individual_ocr,
314
314
  )
315
315
  pile_properties = create_pile_properties_payload(
316
- pile_type=pile_type,
317
- specification=specification,
318
- installation=installation,
316
+ pile_type=str(pile_type),
317
+ specification=str(specification),
318
+ installation=str(installation),
319
319
  pile_shape=pile_shape,
320
320
  diameter_base=diameter_base,
321
321
  diameter_shaft=diameter_shaft,
@@ -344,7 +344,7 @@ def create_multi_cpt_payload(
344
344
  pile_head_level_nap=pile_head_level_nap,
345
345
  stiff_construction=stiff_construction,
346
346
  rel_pile_load=relative_pile_load,
347
- soil_load=soil_load_sls,
347
+ soil_load=soil_load_sls if soil_load_sls is not None else 0.0,
348
348
  excavation_param_t=excavation_param_t,
349
349
  use_almere_rules=use_almere_rules,
350
350
  gamma_f_nk=gamma_f_nk,
@@ -29,8 +29,9 @@ def create_pile_properties_payload(
29
29
  Creates a dictionary with the `pile_properties` payload content for the PileCore
30
30
  endpoints.
31
31
 
32
- Note that
33
- the dictionary should be converted to a jsonifyable message before it can be passed
32
+ Note
33
+ ----
34
+ The dictionary should be converted to a jsonifyable message before it can be passed
34
35
  to a `requests` call directly, for instance with
35
36
  `nuclei.client.utils.python_types_to_message()`.
36
37
 
@@ -35,7 +35,7 @@ def create_soil_properties_payload(
35
35
  Creates a dictionary with the `soil_properties` payload content for the PileCore
36
36
  endpoints.
37
37
 
38
- Notes
38
+ Note
39
39
  ------
40
40
  the dictionary should be converted to a jsonifyable message before it can be passed
41
41
  to a `requests` call directly, for instance with
@@ -211,7 +211,7 @@ class SingleClusterData:
211
211
  """
212
212
  Plot the bearing capacity and variation coefficient in a subplot
213
213
 
214
- Notes
214
+ Note
215
215
  ------
216
216
  For the `Net bearing capacity` subplot there are two colors plotted:
217
217
  - orange: conservative bearing capacity
@@ -251,7 +251,7 @@ class SingleClusterData:
251
251
  """
252
252
  Plot the spacing checks in a subplot
253
253
 
254
- Notes
254
+ Note
255
255
  ------
256
256
  For the `spacing` subplot there are two colors plotted:
257
257
  - red: invalid spacing
@@ -308,7 +308,7 @@ class SingleClusterData:
308
308
  """
309
309
  Plot the xi factor in a subplot
310
310
 
311
- Notes
311
+ Note
312
312
  ------
313
313
  For the `xi factor` subplot there are two colors plotted:
314
314
  - olive: xi3
@@ -499,7 +499,7 @@ class SingleClusterResult:
499
499
  - xi factor
500
500
  - centre to centre validation
501
501
 
502
- Notes
502
+ Note
503
503
  ------
504
504
  For the `Net bearing capacity` subplot there are two colors plotted:
505
505
  - orange: conservative bearing capacity
@@ -775,6 +775,9 @@ class GrouperResults:
775
775
  ) -> plt.Figure:
776
776
  """
777
777
  Plot a summary of the valid subgroups.
778
+
779
+ Note
780
+ -----
778
781
  Plot contains the:
779
782
 
780
783
  - cpts within a subgroup
@@ -7,6 +7,7 @@ import matplotlib.pyplot as plt
7
7
  import numpy as np
8
8
  import pandas as pd
9
9
  from matplotlib.axes import Axes
10
+ from matplotlib.patches import Patch
10
11
 
11
12
  from pypilecore.exceptions import UserError
12
13
  from pypilecore.results.load_settlement import get_load_settlement_plot
@@ -548,6 +549,166 @@ class MultiCPTBearingResults:
548
549
  """The CPTGroupResultsTable dataclass, containing the group results."""
549
550
  return self._group_results_table
550
551
 
552
+ def boxplot(
553
+ self,
554
+ attribute: str,
555
+ axes: Axes | None = None,
556
+ figsize: Tuple[float, float] = (6.0, 6.0),
557
+ show_sqrt: bool = False,
558
+ **kwargs: Any,
559
+ ) -> Axes:
560
+ """
561
+ Plot a box and whisker plot for a given attribute.
562
+
563
+
564
+ .. code-block:: none
565
+
566
+ MIN Q1 median Q3 MAX
567
+ |-----:-----|
568
+ |--------| : |--------|
569
+ |-----:-----|
570
+
571
+ Parameters
572
+ ----------
573
+ attribute:
574
+ result attribute to create boxplot. Please note that the attribute name must be present in
575
+ the `CPTResultsTable` and `CPTGroupResultsTable` class.
576
+ axes:
577
+ Optional `Axes` object where the boxplot data can be plotted on.
578
+ If not provided, a new `plt.Figure` will be activated and the `Axes`
579
+ object will be created and returned.
580
+ figsize:
581
+ Size of the activate figure, as the `plt.figure()` argument.
582
+ show_sqrt:
583
+ Add sqrt(2) bandwidth to figure
584
+ **kwargs:
585
+ All additional keyword arguments are passed to the `pyplot.subplots()` call.
586
+
587
+ Returns
588
+ -------
589
+ axes:
590
+ The `Axes` object where the settlement curves were plotted on
591
+ """
592
+
593
+ # validate attribute
594
+ if (
595
+ attribute not in self.cpt_results.results[0].table.__dict__.keys()
596
+ or attribute not in self.group_results_table.__dict__.keys()
597
+ ):
598
+ raise ValueError(
599
+ f"""
600
+ {attribute} is not present in CPTResultsTable or CPTGroupResultsTable class.
601
+ Please select on of the following attributes:
602
+ {
603
+ set(self.cpt_results.results[0].table.__dict__.keys())
604
+ & set(self.group_results_table.__dict__.keys())
605
+ }
606
+ """
607
+ )
608
+
609
+ # Create axes objects if not provided
610
+ if axes is not None:
611
+ if not isinstance(axes, Axes):
612
+ raise ValueError(
613
+ "'axes' argument to boxplot() must be a `pyplot.axes.Axes` object or None."
614
+ )
615
+ else:
616
+ kwargs_subplot = {
617
+ "figsize": figsize,
618
+ "tight_layout": True,
619
+ }
620
+
621
+ kwargs_subplot.update(kwargs)
622
+
623
+ _, axes = plt.subplots(
624
+ 1,
625
+ 1,
626
+ **kwargs_subplot,
627
+ )
628
+
629
+ if not isinstance(axes, Axes):
630
+ raise ValueError(
631
+ "Could not create Axes objects. This is probably due to invalid matplotlib keyword arguments. "
632
+ )
633
+
634
+ # Collect data from single calculation
635
+ data = np.array(
636
+ [
637
+ item.table.__getattribute__(attribute)
638
+ for item in self.cpt_results.results
639
+ ]
640
+ )
641
+
642
+ # Draw a box and whisker plot
643
+ axes.boxplot(
644
+ np.flip(data, axis=0),
645
+ labels=np.flip(self.group_results_table.pile_tip_level_nap),
646
+ whis=(0, 100),
647
+ autorange=True,
648
+ vert=False,
649
+ patch_artist=True,
650
+ showmeans=True,
651
+ zorder=0,
652
+ )
653
+
654
+ # ad additional bandwidth of sqrt(2) of the mean value
655
+ if show_sqrt:
656
+ axes.scatter(
657
+ np.flip(data.mean(axis=0)) * np.sqrt(2),
658
+ np.flip(
659
+ np.arange(len(self.group_results_table.pile_tip_level_nap)) + 1
660
+ ),
661
+ marker="^",
662
+ color="tab:purple",
663
+ zorder=1,
664
+ )
665
+ axes.scatter(
666
+ np.flip(data.mean(axis=0)) / np.sqrt(2),
667
+ np.flip(
668
+ np.arange(len(self.group_results_table.pile_tip_level_nap)) + 1
669
+ ),
670
+ marker="^",
671
+ color="tab:purple",
672
+ zorder=1,
673
+ )
674
+
675
+ # Draw group result over single result
676
+ axes.scatter(
677
+ np.flip(self.group_results_table.__getattribute__(attribute)),
678
+ np.flip(np.arange(len(self.group_results_table.pile_tip_level_nap)) + 1),
679
+ marker="o",
680
+ color="tab:red",
681
+ zorder=1,
682
+ )
683
+
684
+ # Draw group result over single result
685
+ for i, x in enumerate(data.mean(axis=0)):
686
+ axes.annotate(f"{x.round(2)}", xy=(x, i + 1))
687
+
688
+ # add legend to figure
689
+ axes.legend(
690
+ handles=[
691
+ Patch(color=clr, label=key)
692
+ for (key, clr) in {
693
+ "Single;min:max": "black",
694
+ "Single;Q25:Q75": "tab:blue",
695
+ "Single;Q50": "tab:orange",
696
+ "Single;mean": "tab:green",
697
+ "Single;mean;sqrt": "tab:purple",
698
+ "Group;normative": "tab:red",
699
+ }.items()
700
+ ],
701
+ loc="upper left",
702
+ bbox_to_anchor=(1, 1),
703
+ title=f"Bandwidth {attribute}",
704
+ )
705
+
706
+ # set label
707
+ axes.set_ylabel("Depth [m NAP]")
708
+ axes.set_xlabel(f"{attribute}")
709
+
710
+ return axes
711
+
551
712
  def plot_load_settlement(
552
713
  self,
553
714
  pile_tip_level_nap: float,
@@ -9,8 +9,10 @@ import numpy as np
9
9
  import pandas as pd
10
10
  from matplotlib import pyplot as plt
11
11
  from matplotlib.axes import Axes
12
+ from matplotlib.collections import PatchCollection
12
13
  from matplotlib.figure import Figure
13
14
  from numpy.typing import NDArray
15
+ from scipy.spatial import Delaunay, Voronoi, voronoi_plot_2d
14
16
 
15
17
  from pypilecore.results.soil_properties import SoilProperties, get_soil_layer_handles
16
18
 
@@ -94,7 +96,7 @@ class MaxBearingResult:
94
96
  The object with soil properties
95
97
  pile_head_level_nap
96
98
  The elevation of the pile-head, in [m] w.r.t. NAP.
97
- results_table
99
+ table
98
100
  The object with CPT results.
99
101
  """
100
102
 
@@ -371,6 +373,59 @@ class MaxBearingResults:
371
373
 
372
374
  return cpt_results_df
373
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
+
374
429
  def plot(
375
430
  self,
376
431
  projection: Optional[Literal["3d"]] = "3d",
@@ -413,20 +468,20 @@ class MaxBearingResults:
413
468
  kwargs_subplot.update(kwargs)
414
469
  fig = plt.figure(**kwargs_subplot)
415
470
  axes = fig.add_subplot(projection=projection)
471
+ df = self.to_pandas().dropna()
416
472
  # create color list based on hue option
417
473
  if hue == "category":
418
474
  colors = [
419
- "red" if var < pile_load_uls else "green"
420
- for var in self.to_pandas()["R_c_d_net"]
475
+ "red" if var < pile_load_uls else "green" for var in df["R_c_d_net"]
421
476
  ]
422
477
  else:
423
- colors = self.to_pandas()["R_c_d_net"].tolist()
478
+ colors = df["R_c_d_net"].tolist()
424
479
  # create scatter plot
425
480
  if projection == "3d":
426
481
  cmap = axes.scatter(
427
- self.to_pandas()["x"],
428
- self.to_pandas()["y"],
429
- self.to_pandas()["pile_tip_level_nap"],
482
+ df["x"],
483
+ df["y"],
484
+ df["pile_tip_level_nap"],
430
485
  c=colors,
431
486
  )
432
487
  axes.set_xlabel("X")
@@ -444,12 +499,13 @@ class MaxBearingResults:
444
499
  )
445
500
  else:
446
501
  cmap = axes.scatter(
447
- self.to_pandas()["test_id"],
448
- self.to_pandas()["pile_tip_level_nap"],
502
+ df["test_id"],
503
+ df["pile_tip_level_nap"],
449
504
  c=colors,
450
505
  )
451
506
  axes.set_ylabel("Z [m w.r.t NAP]")
452
507
  axes.tick_params(axis="x", labelrotation=90)
508
+ axes.grid()
453
509
 
454
510
  if hue == "category":
455
511
  fig.legend(
@@ -475,3 +531,149 @@ class MaxBearingResults:
475
531
  fig.colorbar(cmap, orientation="vertical", label="$R_{c;d;net}$ [kN]")
476
532
 
477
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
@@ -738,3 +738,26 @@ def test_create_multi_cpt_payload_errors(
738
738
  installation="1",
739
739
  pile_shape="rect",
740
740
  )
741
+
742
+ def test_soil_load_is_always_float(
743
+ cpt: CPTData, mock_classify_response: dict
744
+ ) -> None:
745
+ """
746
+ Check that a soil-load value `float(0)` is passed even when the soil_load_sls
747
+ argument is None.
748
+ """
749
+
750
+ payload, _ = create_multi_cpt_payload(
751
+ pile_tip_levels_nap=[0, 1],
752
+ cptdata_objects=[cpt],
753
+ classify_tables={cpt.alias: mock_classify_response},
754
+ groundwater_level_nap=-2.5,
755
+ pile_type="concrete",
756
+ specification="1",
757
+ installation="A",
758
+ pile_shape="round",
759
+ diameter_base=0.3,
760
+ soil_load_sls=None,
761
+ )
762
+
763
+ assert np.isclose(payload.pop("soil_load"), 0.0)
File without changes
File without changes
File without changes