Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial setup of tension module in pypilecore #103

Merged
merged 34 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
29b3454
feat: initial setup of tension module in pypilecore
RDWimmers Oct 14, 2024
83a27bf
feat: add alpha:t:clay and alpha;t;sand to PileProperties class
RDWimmers Oct 24, 2024
a078bad
feat: update figure PileGridProperties
RDWimmers Oct 24, 2024
baf761c
refactor(tension): update attributes names
RDWimmers Oct 24, 2024
5d618c6
feat: allow tension MultiCPTBearingResults as input for Viewer
RDWimmers Oct 24, 2024
6ae3a41
feat: add tension notebook
RDWimmers Oct 24, 2024
21e2079
style(python): format and lint files with black and isort
RDWimmers Oct 24, 2024
6de5f83
feat: add legend to grid figure
RDWimmers Oct 30, 2024
493f763
feat: add alpha;t;clay to PileType class
RDWimmers Oct 30, 2024
0dc493e
test(tension): update response validation to 3.2.0-beta.1
RDWimmers Oct 30, 2024
972ccbf
chore(notebook): update tension notebook; clear output
RDWimmers Oct 30, 2024
b90c772
feat(tension): update sdk to API version 3.3.0-beta.1
RDWimmers Dec 31, 2024
6793ad1
docs: add docstring to protocol class
RDWimmers Jan 16, 2025
3d47d8b
style(format): fix linting errors
RDWimmers Jan 16, 2025
6e0ba72
style(format): remove unused import statement
RDWimmers Jan 16, 2025
2988dca
feat: initial setup of tension module in pypilecore
RDWimmers Oct 14, 2024
a954f3c
feat: add alpha:t:clay and alpha;t;sand to PileProperties class
RDWimmers Oct 24, 2024
a611256
feat: update figure PileGridProperties
RDWimmers Oct 24, 2024
8341610
refactor(tension): update attributes names
RDWimmers Oct 24, 2024
4a6daf1
feat: allow tension MultiCPTBearingResults as input for Viewer
RDWimmers Oct 24, 2024
e092c41
feat: add tension notebook
RDWimmers Oct 24, 2024
e414013
style(python): format and lint files with black and isort
RDWimmers Oct 24, 2024
c89d091
feat: add legend to grid figure
RDWimmers Oct 30, 2024
a4f4114
feat: add alpha;t;clay to PileType class
RDWimmers Oct 30, 2024
c8863fc
test(tension): update response validation to 3.2.0-beta.1
RDWimmers Oct 30, 2024
108101e
chore(notebook): update tension notebook; clear output
RDWimmers Oct 30, 2024
66aaa71
feat(tension): update sdk to API version 3.3.0-beta.1
RDWimmers Dec 31, 2024
0961966
docs: add docstring to protocol class
RDWimmers Jan 16, 2025
3c40a67
style(format): fix linting errors
RDWimmers Jan 16, 2025
e213b37
style(format): remove unused import statement
RDWimmers Jan 16, 2025
6e96d41
Merge remote-tracking branch 'origin/feat-add-tension-module' into fe…
RDWimmers Feb 5, 2025
f5416ad
chore: update notebook | routing table to deselect staging url
RDWimmers Feb 14, 2025
f7dd401
test: add python version 3.12 to test matrix
RDWimmers Feb 14, 2025
ffb53f8
fix(deps): add anywidget to dependencies for installing plotly v6.0.0
RDWimmers Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,197 changes: 1,197 additions & 0 deletions notebooks/PileCore_multi_cpt_tension.ipynb

Large diffs are not rendered by default.

89 changes: 89 additions & 0 deletions src/pypilecore/api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from time import sleep
from typing import Literal

from nuclei.client import NucleiClient
from requests import Response
Expand Down Expand Up @@ -235,3 +236,91 @@ def get_groups_api_report(
return client.call_endpoint(
"PileCore", "/get-task-result", version="v3", schema=ticket.json()
)


def get_multi_cpt_api_result_tension(
client: NucleiClient,
payload: dict,
verbose: bool = False,
standard: Literal["NEN9997-1", "CUR236"] = "NEN9997-1",
) -> dict:
"""
Wrapper around the PileCore endpoint "/tension/[nen or cur]/multiple-cpts/results".

Parameters
----------
client: NucleiClient
client object created by [nuclei](https://github.com/cemsbv/nuclei)
payload: dict
the payload of the request, can be created by calling `create_grouper_payload()`
verbose: bool
if True, print additional information to the console
standard: str
Norm used to calculate bearing capacities
"""
logging.info(
"Calculating bearing capacities... \n"
"Depending on the amount of pile tip levels and CPT's this can take a while."
)
if standard == "NEN9997-1":
endpoint = "/tension/nen/multiple-cpt/results"
else:
endpoint = "/tension/cur/multiple-cpt/results"

ticket = client.call_endpoint(
"PileCore",
endpoint=endpoint,
version="v3",
schema=payload,
return_response=True,
)

wait_until_ticket_is_ready(client=client, ticket=ticket, verbose=verbose)

return client.call_endpoint(
"PileCore", "/get-task-result", version="v3", schema=ticket.json()
)


def get_multi_cpt_api_report_tension(
client: NucleiClient,
payload: dict,
verbose: bool = False,
standard: Literal["NEN9997-1", "CUR236"] = "NEN9997-1",
) -> bytes:
"""
Wrapper around the PileCore endpoint "/tension/[nen or cur]/multiple-cpts/report".

Parameters
----------
client: NucleiClient
client object created by [nuclei](https://github.com/cemsbv/nuclei)
payload: dict
the payload of the request, can be created by calling `create_grouper_payload()`
verbose: bool
if True, print additional information to the console
standard: str
Norm used to calculate bearing capacities
"""
logging.info(
"Generate report... \n"
"Depending on the amount of pile tip levels and CPT's this can take a while."
)

if standard == "NEN9997-1":
endpoint = "/tension/nen/multiple-cpt/report"
else:
endpoint = "/tension/cur/multiple-cpt/report"

ticket = client.call_endpoint(
"PileCore",
endpoint=endpoint,
version="v3",
schema=payload,
return_response=True,
)
wait_until_ticket_is_ready(client=client, ticket=ticket, verbose=verbose)

return client.call_endpoint(
"PileCore", "/get-task-result", version="v3", schema=ticket.json()
)
2 changes: 2 additions & 0 deletions src/pypilecore/common/piles/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from .grid import PileGridProperties
from .main import PileProperties, create_basic_pile

__all__ = [
"PileProperties",
"PileGridProperties",
"create_basic_pile",
]
15 changes: 15 additions & 0 deletions src/pypilecore/common/piles/geometry/materials.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
"name": "grout",
"elastic_modulus": 15000,
"yield_stress": 1.5,
"tensile_strength": 1500,
"color": "#8A8A8A",
},
"grout_extorted": {
"name": "grout_extorted",
"elastic_modulus": 20000,
"yield_stress": 2,
"tensile_strength": 2000,
"color": "#8A8A8A",
},
}
Expand Down Expand Up @@ -82,6 +84,7 @@ def __init__(
name: str,
elastic_modulus: float,
yield_stress: float | None = None,
tensile_strength: float | None = None,
color: Color | str | Dict[str, int] | None = None,
):
"""
Expand All @@ -95,12 +98,15 @@ def __init__(
The elastic modulus [MPa] of the material.
yield_stress : float, optional
The yield stress [MPa] of the material, by default None.
tensile_strength : float, optional
The tensile strengths [MPa] of the material, by default None.
color : Color or str or dict, optional
The color of the material, by default None.
"""
self._name = name
self._elastic_modulus = elastic_modulus
self._yield_stress = yield_stress
self._tensile_strength = tensile_strength
if isinstance(color, str):
color = Color.from_hex(hex=color)
elif isinstance(color, dict):
Expand Down Expand Up @@ -132,6 +138,7 @@ def from_api_response(cls, material: dict) -> PileMaterial:
name=material["name"],
elastic_modulus=material["elastic_modulus"],
yield_stress=material.get("yield_stress"),
tensile_strength=material.get("tensile_strength"),
color=color,
)

Expand All @@ -150,6 +157,11 @@ def yield_stress(self) -> float | None:
"""The yield stress [MPa] of the material"""
return self._yield_stress

@property
def tensile_strength(self) -> float | None:
"""The tensile strength [MPa] of the material"""
return self._tensile_strength

@property
def color(self) -> Color | Color | None:
"""The color of the material"""
Expand All @@ -170,4 +182,7 @@ def serialize_payload(self) -> Dict[str, str | float | Dict[str, int]]:
if self.color is not None:
payload["color"] = self.color.serialize_payload()

if self.tensile_strength is not None:
payload["tensile_strength"] = self.tensile_strength

return payload
222 changes: 222 additions & 0 deletions src/pypilecore/common/piles/grid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
from __future__ import annotations

from typing import Any, List, Tuple

import matplotlib.patches as patches
import matplotlib.ticker as ticker
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.axes import Axes
from matplotlib.lines import Line2D
from shapely.geometry import Polygon, shape
from shapely.plotting import plot_polygon


class PileGridProperties:
def __init__(
self,
points: List[Tuple[float, float]],
index_location: int,
geometry: Polygon | None = None,
_validate: bool = True,
):
"""
Class that holds the information of the pile placement.
The structure of the grid effects the effective area of the
pile.

Parameters
----------
points: List of tuple
List of tuple with xy coordinates.
index_location: int
selected index of the points list as pile location
geometry: Polygon, optional
A;eff as polygon
_validate: bool, optional
flag that overrules validation of the parameters

"""
if len(points) < 2 and _validate:
raise ValueError("Provide at least two points to the pile grid.")

if len(points) != len(set(points)):
raise ValueError("All points of the pile grid must be unique.")
if len(points) - 1 < index_location > 0:
raise IndexError(
f"Index out of range, must be between 0 and {len(points) - 1} got {index_location}."
)

self._points = points
self._index_location = index_location
self._geometry = geometry

@classmethod
def regular(cls, ctc: float, index_location: int) -> "PileGridProperties":
"""
Create a regular grid with a centre to centre distance provided by the user.
The regular grid contains 9 points. Numbering start in the lower left corner.
So for the
- center pile `index_location= 4`
- middle pile `index_location= 1 or 3 or 4 or 5 or 7`
- corner pile `index_location= 0 or 2 or 6 or 8`

x --- x --- x
| | |
x --- x --- x
| | |
x --- x --- x with --- == | == ctc

Parameters
----------
ctc: float
Centre to centre distance of regular grid [m]
index_location: int
Location of the pile to calculate
Returns
-------
PileGrid
"""
xx, yy = np.meshgrid(np.arange(0, 3) * ctc, np.arange(0, 3) * ctc)

return cls(
points=list(zip(xx.flatten(), yy.flatten())), index_location=index_location
)

@classmethod
def from_api_response(cls, payload: dict) -> PileGridProperties:
"""
Instantiates a PileGeometry from a geometry object in the API response payload.

Parameters:
-----------
geometry: dict
A dictionary that is retrieved from the API response payload at "/pile_properties/geometry".

Returns:
--------
PileGeometry
A pile geometry.
"""
# make list type hashable
points = [(point[0], point[1]) for point in payload["points"]]
return cls(
points=points,
index_location=payload["index_location"],
geometry=shape(payload["geometry"]),
_validate=False,
)

def serialize_payload(self) -> dict:
return {
"locations": self._points,
"pile_index": self._index_location,
}

def plot_overview(
self,
figsize: Tuple[float, float] = (6.0, 6.0),
axes: Axes | None = None,
add_ticks: bool = False,
add_legend: bool = True,
**kwargs: Any,
) -> Axes:
"""
Plot the Bird's-eye view of the pile grid. When provided the effect area of
the pile influence zone is add to the figure.

Parameters
----------
figsize : tuple, optional
The figure size (width, height) in inches, by default (6.0, 6.0).
axes : Axes
The axes object to plot the cross-section on.
add_ticks : bool
Add ticks to figure, by default False
add_legend : bool
Add legend to figure, by default True
**kwargs
Additional keyword arguments to pass to the `plt
"""

# Create axes objects if not provided
if axes is not None:
if not isinstance(axes, Axes):
raise ValueError(
"'axes' argument to plot_overview() must be a `pyplot.axes.Axes` object or None."
)
else:
kwargs_subplot = {
"figsize": figsize,
"tight_layout": True,
}

kwargs_subplot.update(kwargs)

_, axes = plt.subplots(1, 1, **kwargs_subplot)

if not isinstance(axes, Axes):
raise ValueError(
"Could not create Axes objects. This is probably due to invalid matplotlib keyword arguments. "
)
handles = [
Line2D(
[0],
[0],
label="Selected pile",
marker=".",
color="None",
markerfacecolor="tab:orange",
markeredgecolor="None",
ls="",
),
]

if len(self._points) >= 2:
handles.append(
Line2D(
[0],
[0],
color="None",
label="Other piles",
marker=".",
markerfacecolor="gray",
markeredgecolor="None",
ls="",
)
)
# add the effective area of the pile with the pile grid
if self._geometry is not None:
plot_polygon(self._geometry, ax=axes, add_points=False, color="tab:blue")
handles.append(
patches.Patch(
facecolor="tab:blue",
alpha=0.3,
label=r"$A_{eff}$" + rf" {self._geometry.area:.1f} $m^2$",
edgecolor="tab:blue",
)
)

# plot points
colors = ["gray"] * len(self._points)
colors[self._index_location] = "tab:orange"
axes.scatter(*zip(*self._points), color=colors, marker=".")

# add labels to points
for x, y, label in zip(*zip(*self._points), range(len(self._points))):
axes.annotate(label, xy=(x, y), xytext=(3, 3), textcoords="offset points")

axes.ticklabel_format(useOffset=False, style="plain")
axes.set_aspect("equal", adjustable="box")
if add_ticks:
axes.xaxis.set_major_locator(ticker.NullLocator())
axes.yaxis.set_major_locator(ticker.NullLocator())

if add_legend:
axes.legend(
title="Pile grid configuration",
bbox_to_anchor=(1.04, 1),
loc="upper left",
handles=handles,
)
return axes
Loading
Loading