Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added

- **Integrated soil-pile interaction on the WindIO monopile path (#118).**
`Tower.from_windio_with_monopile` gains a `soil` keyword (a pre-built
`MudlineFoundation`) and a `soil_E` / `soil_nu` / `soil_profile` /
`pile_behaviour` / `soil_formula` auto-build path. When given, the rigid
mudline clamp is replaced by the coupled-spring soil foundation and the
model switches to a soft monopile (`hub_conn=3`), lowering the coupled
frequency relative to the rigid clamp. A new
`MudlineFoundation.from_windio(yaml, soil_E=...)` classmethod extracts the
pile diameter, embedded length and EI at the mudline from the ontology, so
only the soil is specified. Reuses the validated `MudlineFoundation`
(#97) and `attach_mudline_foundation` wiring, removing the manual
BMI-poking the feature request showed. Fully distributed Winkler `distr_k`
springs remain a separate higher-fidelity follow-up.

- **Material / outfitting overrides on the WindIO tower paths (#133).**
`Tower.from_windio` and `Tower.from_windio_with_monopile` accept optional
`E`, `rho`, `nu` and `outfitting_factor` keywords. Each defaults to
Expand Down
88 changes: 88 additions & 0 deletions src/pybmodes/foundation.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
from __future__ import annotations

import math
import pathlib
import warnings
from dataclasses import dataclass
from typing import Literal
Expand Down Expand Up @@ -411,3 +412,90 @@ def from_soil_properties(
soil_profile=soil_profile,
formula=formula,
)

@classmethod
def from_windio(
cls,
yaml_path: str | pathlib.Path,
*,
soil_E: float,
soil_nu: float = 0.3,
soil_profile: SoilProfile = "homogeneous",
pile_behaviour: str = "auto",
formula: FormulaFamily = "shadlou",
component_monopile: str = "monopile",
water_depth: float | None = None,
E: float | None = None,
thickness_interp: str = "linear",
) -> MudlineFoundation:
"""Build the mudline foundation from a WindIO monopile ontology plus
soil properties, auto-extracting the pile geometry (issue #118).

Only the soil is specified; the pile terms the coupled-spring model
needs are read from the ontology's ``monopile`` component:

- **pile diameter** — the monopile outer diameter at the mudline.
- **embedded length** — the mudline down to the pile base
(``-water_depth`` minus the monopile ``reference_axis.z`` base).
- **pile EI** — ``E * pi/64 (D^4 - (D - 2 t)^4)`` at the mudline, with
``E`` the monopile material modulus from the ontology.

then defers to :meth:`from_soil_properties`. Pair the result with
:meth:`pybmodes.models.Tower.attach_mudline_foundation`, or let
:meth:`~pybmodes.models.Tower.from_windio_with_monopile` build and
attach it for you via its ``soil_E`` keyword.

``water_depth`` (m, positive) locates the mudline and defaults to the
ontology's ``environment.water_depth``. ``E`` and ``thickness_interp``
override the monopile modulus and wall-thickness interpolation used
for ``pile_EI`` (``E=None`` uses the ontology value); pass the same
values here as the beam so the mudline springs stay consistent with
it under a material or wall-schedule sensitivity run. Raises
``ValueError`` when the mudline is unknown or the monopile does not
extend below it (no embedded length to model soil reaction over).
Requires the optional ``[windio]`` extra (PyYAML).
"""
from pybmodes.io.windio import _read_water_depth, read_windio_tubular

mp = read_windio_tubular(
yaml_path, component=component_monopile,
thickness_interp=thickness_interp,
)
wd = _read_water_depth(yaml_path, water_depth)
if wd is None:
raise ValueError(
"water_depth is required to place the mudline for the soil "
"springs; pass water_depth=... or set environment.water_depth "
"in the ontology."
)
mudline = -wd
if not (mp.z_base < mudline < mp.z_top):
raise ValueError(
f"the monopile (reference_axis.z {mp.z_base:g}..{mp.z_top:g} m) "
f"does not extend below the mudline (z = {mudline:g} m), so "
f"there is no embedded length to model soil reaction over. "
f"Check water_depth and the monopile reference_axis.z."
)
z_phys = mp.z_base + mp.station_grid * (mp.z_top - mp.z_base)
pile_diameter = float(np.interp(mudline, z_phys, mp.outer_diameter))
if thickness_interp == "piecewise_constant":
# Match the beam reduction: the wall is constant within a segment,
# taking the lower-station step value rather than a linear blend.
idx = int(np.searchsorted(z_phys, mudline, side="right")) - 1
idx = max(0, min(idx, mp.wall_thickness.size - 1))
wall = float(mp.wall_thickness[idx])
else:
wall = float(np.interp(mudline, z_phys, mp.wall_thickness))
inner = pile_diameter - 2.0 * wall
pile_E = mp.E if E is None else E
pile_EI = pile_E * math.pi / 64.0 * (pile_diameter**4 - inner**4)
return cls.from_soil_properties(
pile_diameter=pile_diameter,
pile_length_embedded=mudline - mp.z_base,
pile_EI=pile_EI,
soil_E=soil_E,
soil_nu=soil_nu,
soil_profile=soil_profile,
pile_behaviour=pile_behaviour,
formula=formula,
)
93 changes: 89 additions & 4 deletions src/pybmodes/models/tower.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,12 @@ def from_windio_with_monopile(
rho: float | None = None,
nu: float | None = None,
outfitting_factor: float | None = None,
soil: MudlineFoundation | None = None,
soil_E: float | None = None,
soil_nu: float = 0.3,
soil_profile: str = "homogeneous",
pile_behaviour: str = "auto",
soil_formula: str = "shadlou",
) -> Tower:
"""Build a combined **monopile + tower** fixed-bottom cantilever
from a WindIO ontology ``.yaml`` (issue #92).
Expand Down Expand Up @@ -519,22 +525,54 @@ def from_windio_with_monopile(
the monopile and the tower segment, so a single value drives a
whole-structure sensitivity sweep off a WindIO file. For
per-segment materials, edit the ontology instead.
soil : optional pre-built :class:`pybmodes.MudlineFoundation`
(issue #118). When given, the rigid mudline clamp is replaced by
the foundation's coupled springs and the model is switched to a
soft monopile (``hub_conn = 3``) via
:meth:`attach_mudline_foundation`. Mutually exclusive with
``soil_E``.
soil_E, soil_nu, soil_profile, pile_behaviour, soil_formula :
soil properties to **auto-build** the foundation (issue #118).
Pass ``soil_E`` (soil Young's modulus, Pa) and optionally the
others to have the pile geometry (diameter, embedded length and
EI at the mudline) read from the ontology and a
:class:`pybmodes.MudlineFoundation` built via
:meth:`MudlineFoundation.from_windio`, then attached. Needs a
``water_depth`` (or ``environment.water_depth``) and a monopile
that extends below the mudline. Defaults reproduce
``from_soil_properties``' defaults (``soil_nu=0.3``,
``soil_profile="homogeneous"``, ``pile_behaviour="auto"``,
``soil_formula="shadlou"``).

Notes
-----
Requires the optional ``[windio]`` extra (PyYAML). This is the
Requires the optional ``[windio]`` extra (PyYAML). Defaults to the
**rigid fixed-base** monopile path: the pile is clamped at the
mudline with no soil flexibility, matching
:meth:`from_elastodyn_with_subdyn` and the bundled monopile
samples. Distributed soil springs (a Winkler ``distr_k`` /
``hub_conn = 3`` foundation) and Morison hydrodynamics are out of
scope here and tracked separately. Raises ``ValueError`` if the
samples. Pass ``soil`` or ``soil_E`` for the soft-monopile tier
(lumped mudline coupled springs, ``hub_conn = 3``); this lowers the
coupled frequency relative to the rigid clamp. Fully distributed
Winkler ``distr_k`` springs and Morison hydrodynamics remain a
separate higher-fidelity follow-up. Raises ``ValueError`` if the
monopile top and tower base do not meet at a common
transition-piece elevation.
"""
from pybmodes.io._elastodyn.adapter import _build_bmi_skeleton
from pybmodes.io.windio import read_windio_monopile_tower

if lumped_rna_cal and (soil is not None or soil_E is not None):
raise ValueError(
"lumped_rna_cal is not supported together with a soil "
"foundation (soil / soil_E). The auto-RNA tip mass is "
"expressed in the clamped-base (hub_conn = 1) convention, "
"which a soft monopile (hub_conn = 3) interprets differently "
"and would misplace the rotary inertia. Pass an explicit "
"tip_mass for the soil-flexible base, or drop the soil for the "
"rigid clamped model (the basis ElastoDyn uses for tower mode "
"shapes regardless of soil)."
)

if lumped_rna_cal:
if tip_mass is not None:
raise ValueError(
Expand Down Expand Up @@ -576,6 +614,53 @@ def from_windio_with_monopile(
obj._bmi = bmi
obj._sp = mt.section_props
obj.coeff_validation = None

# Optional soil-pile interaction (issue #118): replace the rigid
# mudline clamp with a coupled-spring foundation (hub_conn = 3). Pass
# a pre-built ``soil`` MudlineFoundation, or ``soil_E`` to auto-build
# it from the ontology's pile geometry.
if soil is not None and soil_E is not None:
raise ValueError(
"pass either soil (a MudlineFoundation) or soil_E (auto-build "
"from the ontology), not both."
)
foundation = soil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require mudline depth when attaching explicit soil

When a caller passes a pre-built soil for a WindIO file that includes an embedded monopile but lacks or omits water_depth, read_windio_monopile_tower builds mt with the monopile toe as the beam base because it only truncates when a depth is available. This branch then attaches the foundation to that base, so the coupled springs act at the pile tip rather than the mudline and the embedded pile is also modeled as a free beam; the soil_E path avoids this by requiring a resolved water depth. Please require/resolve the mudline depth for the explicit-soil path too, or otherwise ensure the beam was truncated to the mudline before attaching.

Useful? React with 👍 / 👎.

if soil_E is not None:
from pybmodes.foundation import MudlineFoundation

foundation = MudlineFoundation.from_windio(
yaml_path,
soil_E=soil_E,
soil_nu=soil_nu,
soil_profile=soil_profile, # type: ignore[arg-type]
Comment on lines +631 to +635

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor geometry overrides when auto-building soil springs

When callers combine the new soil_E auto-build path with existing section overrides such as E=... or thickness_interp="piecewise_constant", the beam is built above with those overrides, but this call re-reads the monopile through MudlineFoundation.from_windio using the raw YAML/default linear thickness. The resulting pile_EI in the mudline springs can therefore differ from the actual monopile stiffness in the same model, corrupting the coupled frequencies for material or wall-schedule sensitivity runs.

Useful? React with 👍 / 👎.

pile_behaviour=pile_behaviour,
formula=soil_formula, # type: ignore[arg-type]
component_monopile=component_monopile,
water_depth=water_depth,
# keep the mudline springs consistent with the beam's monopile
# under a material / wall-schedule override (Codex review #118)
E=E,
thickness_interp=thickness_interp,
)
if foundation is not None:
# The springs act at the mudline, so the beam must be truncated
# there. read_windio_monopile_tower only truncates when a water
# depth resolves; without one it clamps at the pile toe, which
# would leave the embedded pile as a free beam and place the
# springs at the toe. The soil_E path already requires the depth
# (via MudlineFoundation.from_windio); enforce it for the explicit
# ``soil`` path too (Codex review #118).
from pybmodes.io.windio import _read_water_depth

if _read_water_depth(yaml_path, water_depth) is None:
raise ValueError(
"a soil foundation needs a resolved water depth so the "
"beam is truncated to the mudline (and the springs act "
"there, not at the monopile toe with the embedded pile "
"left as a free beam). Pass water_depth=... or set "
"environment.water_depth in the ontology."
)
obj.attach_mudline_foundation(foundation)
Comment on lines +645 to +663

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject auto-RNA with soil-flexible bases

When callers combine lumped_rna_cal=True with soil or soil_E, this branch switches the model to hub_conn=3 after read_windio_rna has produced a clamped-base tip-mass record. The single-tower path explicitly rejects auto-RNA for hub_conn=3, and read_windio_rna documents that the returned inertia is consumed only by the clamped-base path because nondim_tip_mass interprets cm_offset/cm_axial differently for hub_conn 3. This lets WindIO soil models silently solve with misplaced/double-counted RNA rotary inertia unless the combination is rejected or an explicit tip_mass is required.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject auto-RNA when enabling soil springs

When callers pass lumped_rna_cal=True together with soil or soil_E, this new branch still attaches the foundation and flips the model to hub_conn=3 after read_windio_rna has populated tip_mass. The WindIO RNA lump is explicitly clamped-base-only: nondim_tip_mass treats cm_axial differently for hub_conn in (2, 3), so the already tower-top-shifted auto-RNA inertia is reinterpreted and the soft-monopile frequencies are wrong. Please reject this combination, or require an explicit tip_mass, before enabling the soil foundation.

Useful? React with 👍 / 👎.

return obj

@classmethod
Expand Down
Loading
Loading