from __future__ import annotations
from dataclasses import dataclass
from functools import cached_property
from typing import Any, Dict, List, Sequence, Tuple
import natsort
import numpy as np
import pandas as pd
import typing_extensions
from matplotlib import pyplot as plt
from matplotlib.axes import Axes
from numpy.typing import NDArray
from shapely import MultiPoint
from pypilecore.results.compression.multi_cpt_results import (
MultiCPTCompressionBearingResults,
)
from pypilecore.results.post_processing import (
MaxBearingResult,
MaxBearingResults,
MaxBearingTable,
)
from pypilecore.results.typing import GrouperBearingResultsLike
[docs]
class SingleClusterData:
"""
Data for a single CPT subgroup
*Not meant to be instantiated by the user.*
"""
def __init__(
self,
characteristic_bearing_capacity: Sequence[float],
design_bearing_capacity: Sequence[float],
design_negative_friction: Sequence[float],
group_centre_to_centre_validation: Sequence[bool],
group_centre_to_centre_validation_15: Sequence[bool],
group_centre_to_centre_validation_20: Sequence[bool],
group_centre_to_centre_validation_25: Sequence[bool],
mean_calculated_bearing_capacity: Sequence[float],
min_calculated_bearing_capacity: Sequence[float],
net_design_bearing_capacity: Sequence[float],
nominal_cpt: Sequence[str],
pile_tip_level: Sequence[float],
variation_coefficient: Sequence[float],
xi_factor: Sequence[str],
xi_values: Sequence[float],
):
"""
Parameters
----------
characteristic_bearing_capacity:
characteristic bearing capacity [kN]
design_bearing_capacity:
design bearing capacity [kN]
design_negative_friction:
design negative friction [kN]
group_centre_to_centre_validation:
group centre to centre validation
group_centre_to_centre_validation_15:
group centre to centre validation 15 meter
group_centre_to_centre_validation_20:
group centre to centre validation 20 meter
group_centre_to_centre_validation_25:
group centre to centre validation 25 meter
mean_calculated_bearing_capacity:
mean calculated bearing capacity [kN]
min_calculated_bearing_capacity:
min calculated bearing capacity [kN]
net_design_bearing_capacity:
net design bearing capacity [kN]
nominal_cpt:
nominal cpt
pile_tip_level:
pile tip level [m w.r.t NAP]
variation_coefficient:
variation coefficient [-]
xi_factor:
xi factor
xi_values:
xi values [-]
"""
self._characteristic_bearing_capacity = characteristic_bearing_capacity
self._design_bearing_capacity = design_bearing_capacity
self._design_negative_friction = design_negative_friction
self._group_centre_to_centre_validation = group_centre_to_centre_validation
self._group_centre_to_centre_validation_15 = (
group_centre_to_centre_validation_15
)
self._group_centre_to_centre_validation_20 = (
group_centre_to_centre_validation_20
)
self._group_centre_to_centre_validation_25 = (
group_centre_to_centre_validation_25
)
self._mean_calculated_bearing_capacity = mean_calculated_bearing_capacity
self._min_calculated_bearing_capacity = min_calculated_bearing_capacity
self._net_design_bearing_capacity = net_design_bearing_capacity
self._nominal_cpt = nominal_cpt
self._pile_tip_level = pile_tip_level
self._variation_coefficient = variation_coefficient
self._xi_factor = xi_factor
self._xi_values = xi_values
raw_lengths = [len(values) for values in self.__dict__.values()]
if len(list(set(raw_lengths))) > 1:
raise ValueError("All values in this dataclass must have the same length.")
@property
def characteristic_bearing_capacity(self) -> NDArray[np.float64]:
"""Characteristic bearing capacity [kN]"""
return np.array(self._characteristic_bearing_capacity).astype(np.float64)
@property
def design_bearing_capacity(self) -> NDArray[np.float64]:
"""Design bearing capacity [kN]"""
return np.array(self._design_bearing_capacity).astype(np.float64)
@property
def design_negative_friction(self) -> NDArray[np.float64]:
"""Design negative friction [kN]"""
return np.array(self._design_negative_friction).astype(np.float64)
@property
def group_centre_to_centre_validation(self) -> NDArray[np.bool_]:
"""Group centre to centre validation"""
return np.array(self._group_centre_to_centre_validation).astype(np.bool_)
@property
def group_centre_to_centre_validation_15(self) -> NDArray[np.bool_]:
"""Group centre to centre validation 15 meter"""
return np.array(self._group_centre_to_centre_validation_15).astype(np.bool_)
@property
def group_centre_to_centre_validation_20(self) -> NDArray[np.bool_]:
"""Group centre to centre validation 20 meter"""
return np.array(self._group_centre_to_centre_validation_20).astype(np.bool_)
@property
def group_centre_to_centre_validation_25(self) -> NDArray[np.bool_]:
"""Group centre to centre validation 25 meter"""
return np.array(self._group_centre_to_centre_validation_25).astype(np.bool_)
@property
def mean_calculated_bearing_capacity(self) -> NDArray[np.float64]:
"""Mean calculated bearing capacity [kN]"""
return np.array(self._mean_calculated_bearing_capacity).astype(np.float64)
@property
def min_calculated_bearing_capacity(self) -> NDArray[np.float64]:
"""Min calculated bearing capacity [kN]"""
return np.array(self._min_calculated_bearing_capacity).astype(np.float64)
@property
def net_design_bearing_capacity(self) -> NDArray[np.float64]:
"""Net design bearing capacity [kN]"""
return np.array(self._net_design_bearing_capacity).astype(np.float64)
@property
def nominal_cpt(self) -> NDArray[np.str_]:
"""Nominal cpt"""
return np.array(self._nominal_cpt).astype(str)
@property
def pile_tip_level(self) -> NDArray[np.float64]:
"""Pile tip level [m w.r.t NAP]"""
return np.array(self._pile_tip_level).astype(np.float64)
@property
def variation_coefficient(self) -> NDArray[np.float64]:
"""Variation coefficient [-]"""
return np.array(self._variation_coefficient).astype(np.float64)
@property
def xi_factor(self) -> NDArray[np.str_]:
"""Xi factor"""
return np.array(self._xi_factor).astype(str)
@property
def xi_values(self) -> NDArray[np.float64]:
"""Xi values [-]"""
return np.array(self._xi_values).astype(np.float64)
@cached_property
def to_pandas(self) -> pd.DataFrame:
return pd.DataFrame(self.__dict__)
[docs]
def plot_variation_coefficient(
self, axes: Axes | None = None, **kwargs: Any
) -> None:
"""
Plot the bearing capacity and variation coefficient in a subplot
Parameters
----------
axes:
`plt.Axes` object where the data can be plotted on.
**kwargs:
All additional keyword arguments are passed to the `pyplot.subplots()` call.
"""
if axes is None:
_, axes = plt.subplots(**kwargs)
# create variation coefficient plot
axes.plot(self.variation_coefficient, self.pile_tip_level, "o-")
axes.axvline(x=0.12, color="black", linestyle="--")
axes.grid()
axes.set_xlabel("Variation coefficient [-]")
[docs]
def plot_bearing_capacity(
self, axes: Axes | None = None, pile_load_uls: float = 0.0, **kwargs: Any
) -> None:
"""
Plot the bearing capacity and variation coefficient in a subplot
Note
------
For the `Net bearing capacity` subplot there are two colors plotted:
- orange: conservative bearing capacity
- blue: net bearing capacity
Parameters
----------
axes:
`plt.Axes` object where the data can be plotted on.
pile_load_uls:
Default is 0.0
ULS load in kN.
**kwargs:
All additional keyword arguments are passed to the `pyplot.subplots()` call.
"""
if axes is None:
_, axes = plt.subplots(**kwargs)
# add net bearing capacity to plot
axes.scatter(
self.net_design_bearing_capacity,
self.pile_tip_level,
color=list(
map(
lambda x: "tab:blue" if x <= 0.12 else "tab:orange",
self.variation_coefficient,
)
),
)
axes.axvline(x=pile_load_uls, color="black", linestyle="--")
axes.grid()
axes.set_xlabel("Net bearing capacity [kN]")
[docs]
def plot_group_centre_to_centre_validation(
self, axes: plt.Axes | None = None, **kwargs: Any
) -> None:
"""
Plot the spacing checks in a subplot
Note
------
For the `spacing` subplot there are two colors plotted:
- red: invalid spacing
- green: valid spacing
Parameters
----------
axes:
`plt.Axes` object where the data can be plotted on.
**kwargs:
All additional keyword arguments are passed to the `pyplot.subplots()` call.
"""
if axes is None:
_, axes = plt.subplots(**kwargs)
axes.scatter(
[0] * len(self.pile_tip_level),
self.pile_tip_level,
marker="o",
color=list(
map(
lambda x: "tab:green" if x else "tab:red",
self.group_centre_to_centre_validation_25,
)
),
)
axes.scatter(
[1] * len(self.pile_tip_level),
self.pile_tip_level,
marker="s",
color=list(
map(
lambda x: "tab:green" if x else "tab:red",
self.group_centre_to_centre_validation_20,
)
),
)
axes.scatter(
[2] * len(self.pile_tip_level),
self.pile_tip_level,
marker="D",
color=list(
map(
lambda x: "tab:green" if x else "tab:red",
self.group_centre_to_centre_validation_15,
)
),
)
axes.grid()
axes.set_xticks([0, 1, 2], ["25", "20", "15"])
axes.set_xlabel("CPT ctc [m]")
[docs]
def plot_xi(self, axes: plt.Axes | None = None, **kwargs: Any) -> None:
"""
Plot the xi factor in a subplot
Note
------
For the `xi factor` subplot there are two colors plotted:
- olive: xi3
- cyan: xi4
Parameters
----------
axes:
`plt.Axes` object where the data can be plotted on.
**kwargs:
All additional keyword arguments are passed to the `pyplot.subplots()` call.
"""
if axes is None:
_, axes = plt.subplots(**kwargs)
axes.scatter(
self.xi_values,
self.pile_tip_level,
color=list(
map(
lambda i: "tab:cyan" if i == "\u03be4" else "tab:olive",
self.xi_factor,
)
),
)
axes.grid()
axes.set_xlabel("xi value [-]")
[docs]
@dataclass(frozen=True)
class SingleClusterResult:
"""
*Not meant to be instantiated by the user.*
Attributes:
------------
cpt_names: List[str]
List of cpt names present in this cluster
coordinates: List[Tuple[float, float]]
List of coordinates present in this cluster
pile_load_uls
ULS load in kN. Used to determine if a grouping configuration is valid.
maximum_pile_level: float
maximum pile level [m w.r.t NAP]
minimum_pile_level: float
minimum pile level [m w.r.t NAP]
number_of_consecutive_pile_levels: int
number of consecutive pile levels
spatial_check: bool
True if cluster is spatially coherent, which means there are no other CPTs in between the members
of the subgroup.
variation_check: bool
True if a maximum variation coefficient of 12% at one or more pile-tip levels.
centre_to_centre_check: bool
True if one of the conditions stated in NEN9997-1 3.2.3 is met at one or more pile-tip levels.
data: SingleClusterData
single cluster dataclass
"""
cpt_names: List[str]
coordinates: List[Tuple[float, float]]
pile_load_uls: float
maximum_pile_level: float
minimum_pile_level: float
number_of_consecutive_pile_levels: int
spatial_check: bool
variation_check: bool
centre_to_centre_check: bool
data: SingleClusterData
@classmethod
def from_api_response(
cls, response_dict: dict, pile_load_uls: float
) -> "SingleClusterResult":
try:
table = response_dict["table"]
return cls(
cpt_names=response_dict["names"],
coordinates=response_dict["coordinates"],
pile_load_uls=pile_load_uls,
maximum_pile_level=response_dict["maximum_pile_level"],
minimum_pile_level=response_dict["minimum_pile_level"],
number_of_consecutive_pile_levels=response_dict[
"number_of_consecutive_pile_levels"
],
spatial_check=response_dict["spatial_check"],
variation_check=response_dict["variation_check"],
centre_to_centre_check=response_dict["centre_to_centre_check"],
data=SingleClusterData(
characteristic_bearing_capacity=table[
"characteristic_bearing_capacity"
],
design_bearing_capacity=table["design_bearing_capacity"],
design_negative_friction=table["design_negative_friction"],
group_centre_to_centre_validation=table[
"group_centre_to_centre_validation"
],
group_centre_to_centre_validation_15=table[
"group_centre_to_centre_validation_15"
],
group_centre_to_centre_validation_20=table[
"group_centre_to_centre_validation_20"
],
group_centre_to_centre_validation_25=table[
"group_centre_to_centre_validation_25"
],
mean_calculated_bearing_capacity=table[
"mean_calculated_bearing_capacity"
],
min_calculated_bearing_capacity=table[
"min_calculated_bearing_capacity"
],
net_design_bearing_capacity=table["net_design_bearing_capacity"],
nominal_cpt=table["nominal_cpt"],
pile_tip_level=[round(elem, 2) for elem in table["pile_tip_level"]],
variation_coefficient=table["variation_coefficient"],
xi_factor=table["xi_factor"],
xi_values=table["xi_values"],
),
)
except KeyError as e:
raise KeyError(
"Response dictionary is missing an expected key.\n" rf"Traceback: {e}"
)
except ValueError as e:
raise ValueError(
f"Could not create `SingleClusterResult` class with the following cpts: {response_dict.get('names')} \n"
rf"Traceback: {e}"
)
[docs]
def map(
self,
figsize: Tuple[int, int] | None = None,
**kwargs: Any,
) -> plt.Figure:
"""
Plot a map of the cpt locations
Parameters
----------
figsize:
Size of the activate figure, as the `plt.figure()` argument.
**kwargs:
All additional keyword arguments are passed to the `pyplot.subplots()` call.
Returns
-------
axes:
The `Axes` object where the data was plotted on.
"""
# Create axes objects if not provided
kwargs_subplot = {
"figsize": figsize,
"tight_layout": True,
}
kwargs_subplot.update(kwargs)
fig, axes = plt.subplots(
**kwargs_subplot,
)
# plot cpt
xy = list(zip(*self.coordinates))
axes = axes.scatter(x=xy[0], y=xy[1], color="grey")
# add labels (cpt names) to map
for x, y, label in zip(xy[0], xy[1], self.cpt_names):
axes.annotate(label, xy=(x, y), xytext=(3, 3), textcoords="offset points")
axes.ticklabel_format(useOffset=False, style="plain")
return fig
[docs]
def plot(
self,
figsize: Tuple[int, int] | None = None,
**kwargs: Any,
) -> plt.Figure:
"""
Plot contains the:
- bearing capacity
- variation coefficient
- xi factor
- centre to centre validation
Note
------
For the `Net bearing capacity` subplot there are two colors plotted:
- orange: conservative bearing capacity
- blue: net bearing capacity
For the `xi factor` subplot there are two colors plotted:
- olive: xi3
- cyan: xi4
For the `spacing` subplot there are two colors plotted:
- red: invalid spacing
- green: valid spacing
Parameters
----------
figsize:
Size of the activate figure, as the `plt.figure()` argument.
**kwargs:
All additional keyword arguments are passed to the `pyplot.subplots()` call.
Returns
-------
figure:
`plt.Figure` object.
"""
kwargs_subplot = {
"sharey": "row",
"sharex": "col",
"figsize": figsize,
}
kwargs_subplot.update(kwargs)
fig, axes = plt.subplots(
1,
4,
**kwargs_subplot,
)
# add plot variation coefficient
self.data.plot_variation_coefficient(axes[0])
# add plot bearing capacity
self.data.plot_bearing_capacity(axes[1], self.pile_load_uls)
# add xi table
self.data.plot_xi(axes[2])
# add centre to centre
self.data.plot_group_centre_to_centre_validation(axes[3])
return fig
[docs]
@dataclass(frozen=True)
class GrouperResults:
"""
*Not meant to be instantiated by the user.*
Use the `from_grouper_response` (or the legacy `from_api_response`) method to
instantiate the class.
Attributes:
------------
clusters: List[SingleClusterResult]
bearing_results: GrouperBearingResultsLike
The bearing results the subgroups were generated with. May be a PileCore
`MultiCPTCompressionBearingResults` or a user-built `CustomBearingResults`;
both satisfy the `GrouperBearingResultsLike` protocol.
"""
clusters: List[SingleClusterResult]
bearing_results: GrouperBearingResultsLike
def __post_init__(self) -> None:
bearing_cpt_names = self.bearing_results.cpt_names
bearing_pile_tip_levels_nap = self.bearing_results.pile_tip_levels_nap
for cluster in self.clusters:
for cpt_name in cluster.cpt_names:
# check if the cpt names in the SingleClusterResults are also present
# in the bearing results
if cpt_name not in bearing_cpt_names:
raise ValueError(
"CPT names dont match between MultiCPTBearingResults object and GrouperResults. "
"Make sure that you use the same MultiCPTBearingResults as you generated "
"the subgroups/clusters with."
)
# Check that all the pile tip levels in the SingleClusterResults are
# also present in the bearing results
for pile_tip_level in cluster.data.pile_tip_level:
if not np.isclose(
pile_tip_level,
bearing_pile_tip_levels_nap,
rtol=1e-2,
).any():
raise ValueError(
"Pile tip levels dont match between MultiCPTBearingResults object and GrouperResults. "
"Make sure that you use the same MultiCPTBearingResults as you generated "
"the subgroups/clusters with."
)
@property
@typing_extensions.deprecated(
"`multi_cpt_bearing_results` is deprecated; use `bearing_results` instead."
)
def multi_cpt_bearing_results(self) -> MultiCPTCompressionBearingResults | None:
"""
Deprecated accessor for the bearing results.
Returns the PileCore `MultiCPTCompressionBearingResults` when the results were
computed by PileCore, or ``None`` for a custom (externally-computed) source. Use
`bearing_results` instead.
"""
if isinstance(self.bearing_results, MultiCPTCompressionBearingResults):
return self.bearing_results
return None
[docs]
@classmethod
def from_grouper_response(
cls,
response: dict,
pile_load_uls: float,
bearing_results: GrouperBearingResultsLike,
) -> "GrouperResults":
"""
Stores the response of the PileCore endpoint "/grouper/group_cpts".
This is the general, source-agnostic constructor: `bearing_results` may be a
PileCore `MultiCPTCompressionBearingResults` or a user-built
`CustomBearingResults` (both satisfy `GrouperBearingResultsLike`).
Parameters
----------
response:
The resulting response of a call to `get_groups_api_result()`
pile_load_uls:
ULS load in kN. Used to determine if a grouping configuration is valid.
bearing_results:
The bearing results that the subgroups were generated with.
"""
results = [
SingleClusterResult.from_api_response(item, pile_load_uls)
for item in response["sub_groups"]
]
return cls(clusters=results, bearing_results=bearing_results)
[docs]
@classmethod
def from_api_response(
cls,
response_dict: dict,
pile_load_uls: float,
multi_cpt_bearing_results: MultiCPTCompressionBearingResults,
) -> "GrouperResults":
"""
Stores the response of the PileCore endpoint
"/grouper/group_cpts"
Kept for backwards compatibility; delegates to `from_grouper_response`.
Parameters
----------
response_dict:
The resulting response of a call to `get_groups_api_result()`
pile_load_uls:
ULS load in kN. Used to determine if a grouping configuration is valid.
multi_cpt_bearing_results:
The container that holds multiple SingleCPTBearingResults objects
"""
return cls.from_grouper_response(
response=response_dict,
pile_load_uls=pile_load_uls,
bearing_results=multi_cpt_bearing_results,
)
@cached_property
def cpt_results(self) -> "MaxBearingResults":
"""
Get the results of the maximum net design bearing capacity (R_c_d_net) for every CPT.
Source-agnostic: it folds the subgroup capacities over the per-CPT baseline that
`bearing_results.base_max_bearing_results()` provides, so the PileCore and custom
sources ride the exact same routine.
"""
max_bearing: Dict[str, Any] = {}
# Start from the per-CPT baseline. `MaxBearingTable` properties return a fresh
# array on every access, so extracting them here yields mutable arrays that the
# fold below can safely mutate without touching the baseline object.
baseline = self.bearing_results.base_max_bearing_results()
for cpt_name, base_result in baseline.cpt_results_dict.items():
max_bearing[cpt_name] = dict(
pile_head_level_nap=base_result.pile_head_level_nap,
soil_properties=base_result.soil_properties,
results_table=dict(
pile_tip_level_nap=base_result.table.pile_tip_level_nap,
R_c_d_net=base_result.table.R_c_d_net,
F_nk_d=base_result.table.F_nk_d,
origin=[f"CPT:{cpt_name}"]
* len(base_result.table.pile_tip_level_nap),
),
)
# iterate over subgroups result
for cluster_idx, cluster in enumerate(self.clusters):
# iterate over cpts in subgroup
for cpt_name in cluster.cpt_names:
# iterate over pile tip levels in the cluster results for the cpt
for cluster_ptl_idx, ptl in enumerate(cluster.data.pile_tip_level):
# find corresponding pile tip level index in the max_bearing results
max_bearing_ptl_idx = np.abs(
max_bearing[cpt_name]["results_table"]["pile_tip_level_nap"]
- ptl
).argmin()
# Replace the data if the net design bearing capacity is higher and the variation coefficient is at most 12%
if (
# New value has a higher net design bearing capacity
cluster.data.net_design_bearing_capacity[cluster_ptl_idx]
> np.nan_to_num(
max_bearing[cpt_name]["results_table"]["R_c_d_net"][
max_bearing_ptl_idx
]
)
# Maximum variation coefficient of 12%
and cluster.data.variation_coefficient[cluster_ptl_idx] <= 12.0
):
# replace data
max_bearing[cpt_name]["results_table"]["R_c_d_net"][
max_bearing_ptl_idx
] = cluster.data.net_design_bearing_capacity[cluster_ptl_idx]
max_bearing[cpt_name]["results_table"]["F_nk_d"][
max_bearing_ptl_idx
] = cluster.data.design_negative_friction[cluster_ptl_idx]
max_bearing[cpt_name]["results_table"]["origin"][
max_bearing_ptl_idx
] = f"Group:{cluster_idx}"
return MaxBearingResults(
cpt_results_dict={
cpt_name: MaxBearingResult(
pile_head_level_nap=data["pile_head_level_nap"],
soil_properties=data["soil_properties"],
table=MaxBearingTable(
pile_tip_level_nap=data["results_table"]["pile_tip_level_nap"],
R_c_d_net=data["results_table"]["R_c_d_net"],
F_nk_d=data["results_table"]["F_nk_d"],
origin=data["results_table"]["origin"],
),
)
for cpt_name, data in max_bearing.items()
}
)
[docs]
def map(
self,
distance: float = 25.0,
add_tags: bool = True,
figsize: Tuple[int, int] | None = None,
**kwargs: Any,
) -> plt.Figure:
"""
Plot a map of the valid subgroups.
Plot contains the:
- convex_hull of the subgroup with buffer distance
- All CPT's with tag
Parameters
----------
distance : float, optional
Default is 25.
The buffer around the convex_hull of the subgroup
add_tags : bool, optional
default is True
Show the CTP names as tags on the map
figsize:
Size of the activate figure, as the `plt.figure()` argument.
**kwargs:
All additional keyword arguments are passed to the `pyplot.subplots()` call.
Returns
-------
figure:
The `Figure` object where the data was plotted on.
"""
kwargs_subplot = {
"figsize": figsize,
"tight_layout": True,
}
kwargs_subplot.update(kwargs)
figure, axes = plt.subplots(
**kwargs_subplot,
)
for group_id, cluster in enumerate(self.clusters):
# add cpts to plot
xy = list(zip(*cluster.coordinates))
axes.scatter(xy[0], xy[1], color="grey")
if add_tags:
for x, y, label in zip(xy[0], xy[1], cluster.cpt_names):
axes.annotate(
label, xy=(x, y), xytext=(3, 3), textcoords="offset points"
)
# add group convex hull
polygon = (
MultiPoint(cluster.coordinates)
.convex_hull.buffer(distance=distance)
.exterior
)
axes.plot(polygon.xy[0], polygon.xy[1], label=f"Group {group_id}")
axes.legend(bbox_to_anchor=(1, 1), loc="upper left")
axes.ticklabel_format(useOffset=False, style="plain")
return figure
[docs]
def plot(
self,
figsize: Tuple[int, int] | None = None,
**kwargs: Any,
) -> plt.Figure:
"""
Plot a summary of the valid subgroups.
Note
-----
Plot contains the:
- cpts within a subgroup
- green:
There are no other CPTs in between the members of the subgroup. The
group is also compliant with the NEN9997-1 3.2.3 centre to centre
validation.
- orange:
There are no other CPTs in between the members of the subgroup. The
centre-to-centre check failed and so the group does not follow the
NEN9997-1 3.2.3 centre to centre validation.
- red:
There are other CPTs in between the members of the subgroup.
- valid depth of the subgroup
Parameters
----------
figsize:
Size of the activate figure, as the `plt.figure()` argument.
**kwargs:
All additional keyword arguments are passed to the `pyplot.subplots()` call.
Returns
-------
figure:
the `plt.Figure` object.
"""
kwargs_subplot = {
"sharex": "row",
"figsize": figsize,
"tight_layout": True,
}
kwargs_subplot.update(kwargs)
figure, axes = plt.subplots(
1,
2,
**kwargs_subplot,
)
# place holds needed to sort for plot
group_id_list_sort = []
cpt_names_list: List[str] = []
color_list = []
group_id_list = []
for group_id, cluster in enumerate(self.clusters):
group_id_list_sort.extend([group_id] * len(cluster.cpt_names))
cpt_names_list.extend(cluster.cpt_names)
color_list.extend(
[
(
"tab:green"
if cluster.spatial_check and cluster.centre_to_centre_check
else (
"tab:orange"
if cluster.spatial_check and ~cluster.centre_to_centre_check
else "tab:red"
)
)
]
* len(cluster.cpt_names)
)
valid_pile_level = np.array(cluster.data.pile_tip_level)[
np.array(cluster.data.net_design_bearing_capacity)
>= cluster.pile_load_uls
]
axes[1].scatter([group_id] * len(valid_pile_level), valid_pile_level)
group_id_list.append(group_id)
data = pd.DataFrame(
{
"group_id": group_id_list_sort,
"cpt_names": cpt_names_list,
"colors": color_list,
}
).sort_values("cpt_names", ascending=False, key=natsort.natsort_keygen())
axes[0].scatter(x="group_id", y="cpt_names", color="colors", data=data)
axes[0].set_xlabel("Group ID")
axes[0].set_xticks(group_id_list)
axes[0].set_ylabel("CPT name")
axes[0].grid(which="major", axis="both", alpha=0.1)
axes[1].set_xlabel("Group ID")
axes[1].set_ylabel("Depth [m NAP]")
axes[1].grid(which="major", axis="both", alpha=0.1)
return figure