Skip to content

Releases: SMI-Lab-Inha/pyBModes

pyBmodes 1.15.1

29 May 23:20
4b94add

Choose a tag to compare

Highlights

A focused ergonomic patch addressing the only piece of feedback on 1.15.0. Adds a one-call wiring from MudlineFoundation into a clamped monopile model so users can convert a from_windio_with_monopile or from_elastodyn_with_subdyn tower to the hub_conn = 3 soft monopile path without hand-building a PlatformSupport or mutating private BMI fields.

Added

  • Tower.attach_mudline_foundation(foundation) wires a pybmodes.MudlineFoundation into the tower's BMI in one call. Creates a fresh PlatformSupport carrying the foundation's 6 x 6 mooring_K block (with zero hydro, zero platform inertia, and empty distributed arrays), sets tow_support = 1, and flips hub_conn to 3. Returns self for chaining, so the canonical pattern is one expression:

    modal = (
        Tower.from_windio_with_monopile("ontology.yaml", tip_mass=991000.0)
        .attach_mudline_foundation(foundation)
        .run(n_modes=4)
    )

    Refuses to wire onto a free-base floating model (hub_conn = 2) or a pinned-free cable model (hub_conn = 4) with a clear ValueError. The mudline stiffness affects the coupled-system frequency only; ElastoDyn polynomial coefficient generation continues to use the cantilever path regardless of soil flexibility, the same architectural reason src/pybmodes/_examples/reference_decks/FLOATING_CASES.md records for floating platforms.

Documentation

  • Quickstart's soft-monopile recipe now demonstrates the canonical Tower.from_windio_with_monopile(...).attach_mudline_foundation(f) pattern as the primary path, with as_mooring_K() kept as the compose-it-yourself option for callers wiring into an existing PlatformSupport (the CS_Monopile.bmi deck pattern).

Related

  • Closes #117 (broken close-comment snippet on #97).
  • Partially addresses #118: the ergonomic half (one-call attach) is shipped here; the distributed Winkler distribution along the embedded length stays scoped for a future minor release.
  • Merged via #119.

Install

pip install --upgrade pybmodes==1.15.1

The package is published via PyPI Trusted Publishing on a green run of the Validation (external data) workflow.

pyBmodes 1.15.0

29 May 11:15
8b1ee6f

Choose a tag to compare

Highlights

Two additive features on the soft-monopile and floating coupling story. A new geotechnical building block for soil-pile interaction at a soft monopile foundation, plus a diagnostic that reconciles pyBmodes-generated ElastoDyn polynomial coefficients against the coupled-system frequency an OpenFAST linearisation reports. No behaviour change on any existing model path; the new entry points are purely additive.

Added

  • pybmodes.MudlineFoundation computes the coupled-spring soil-pile interaction stiffness (K_hh, K_hr, K_rr) at the mudline of a monopile foundation and returns a 6 x 6 matrix that drops straight into PlatformSupport.mooring_K of a hub_conn = 3 soft monopile BMI. The classmethod from_soil_properties accepts pile geometry and soil properties, applies Randolph (1981) pile classification, and dispatches to either the Shadlou and Bhattacharya (2016) formulas (Yu Table 1, covers homogeneous, parabolic, and linear soil profiles for both flexible and rigid piles) or the Psaroudakis et al. (2021) closed form (Yu Eq 25, homogeneous soil only). Reproduces the Yu and Amdahl (2023) Table 9 DTU 10 MW anchors to within 3 percent on K_hh, K_hr, K_rr for both flexible and rigid reference cases. The new module emits the 6 x 6 mooring_K contribution only and so affects coupled-system frequencies on hub_conn = 3 solves; ElastoDyn polynomial coefficient generation continues to use the cantilever path regardless of soil flexibility, for the same architectural reason src/pybmodes/_examples/reference_decks/FLOATING_CASES.md records for floating platforms.
  • pybmodes.elastodyn.report_floating_frequency_gap runs both a cantilever and a coupled solve on the same floating ElastoDyn deck and returns a FloatingFrequencyGap dataclass naming the gap between the polynomial-basis cantilever 1st FA / SS and the coupled-system 1st FA / SS that an OpenFAST linearisation reports. The two numbers differ by 20-30 percent on a typical floating platform, and the new diagnostic surfaces the gap so users reconciling pyBmodes-generated polynomial coefficients against OpenFAST linearisation output do not have to re-derive the architecture from scratch. format_report() on the result produces a short text block suitable for stdout or a log.

Documentation

  • src/pybmodes/_examples/reference_decks/FLOATING_CASES.md now carries an FAQ section explaining the cantilever-vs-coupled frequency gap and recording the audit trail for the rejected projection-method polynomial-generation proposal (which would have introduced Rayleigh-quotient bias on FreqTFA and double-counted platform restoring against the runtime Sg/Sw/Hv/R/P/Y DOFs in ElastoDyn.f90:7485-7544). The next person to raise the proposal finds the answer in-tree.
  • The docstring on Tower.from_elastodyn_with_mooring now points at report_floating_frequency_gap so the diagnostic is discoverable from the constructor users actually call.

Related

  • Closes #97 (geotechnical building block for soil-pile interaction; MudlineFoundation delivers the coupled-spring foundation surface).
  • Merged via #114 (feature implementation) and #115 (version bump).

Install

pip install --upgrade pybmodes==1.15.0

The package is published via PyPI Trusted Publishing on a green run of the Validation (external data) workflow.

pyBmodes 1.14.1

23 May 09:09
4e3af9a

Choose a tag to compare

Fixed

  • pybmodes --help crashed on a non-UTF-8 Windows console. The windio subcommand help carried a rightwards-arrow glyph, so argparse raised UnicodeEncodeError when writing the formatted help to a legacy Windows console (cp1252 / cp437) where that character has no mapping. Linux and macOS use UTF-8, so it only surfaced on Windows. The printed CLI help and description strings are now plain ASCII, and a new test asserts every parser's formatted help is ASCII-encodable (and encodes on cp1252 / cp437) so it prints on any code page.

A patch release with no API or numerical change. Caught by the conda-forge Windows build during the conda-forge submission.

pyBmodes 1.14.0

23 May 04:55
50a5838

Choose a tag to compare

Highlights

An engineering-hardening pass that makes the library fail closed on non-physical input, surface what it could not model, and report the numerical health of every solve. One behaviour change (ERROR-severity pre-solve checks now raise by default), everything else additive.

Changed

  • Pre-solve ERROR findings now fail closed. Tower.run / RotatingBlade.run raise pybmodes.checks.ModelValidationError (a ValueError subclass) on any ERROR-severity finding instead of warning and feeding the eigensolver non-physical input. New on_error="raise"|"warn" keyword (default raise). Pass on_error="warn" for the pre-1.14.0 behaviour, or check_model=False to skip. WARN findings still warn. This only affects models that were already producing meaningless output.

Added

  • ModalResult.diagnostics (SolverDiagnostics). Path taken, sparse-to-dense fallback and reason, mode-count guarantee, per-mode backward-error residuals, and a mass-matrix conditioning estimate. The general path warns when it recovers fewer valid modes than requested. Transient telemetry, excluded from equality and not serialised.
  • ModalResult.ignored_physics. Names parsed-but-not-modelled physics (distributed added mass distr_m today) so a result is honest about its fidelity. Persisted when non-empty and shown in the bundled report.
  • Report completeness stamp. generate_report gains a status argument; run_windio sets complete / partial / screening and carries it on WindioResult.report_status.

Fixed

  • WindIO discovery is a structured parse, not a text scan. Candidates are parsed as YAML and checked structurally, with the missing-PyYAML install hint preserved, non-UTF-8 sidecars skipped, and deck discovery scoped by the enclosing project (.git) boundary so a deeply-nested ontology resolves its decks without climbing into a broad workspace.
  • BMI parser errors are a first-class diagnostic. The .bmi reader raises pybmodes.io.errors.BMIParseError carrying the file, 1-based line, offending text, and section, instead of a bare ValueError / IndexError / EOFError. Still a ValueError subclass.

Internal

  • Strict mypy ratchet gains checks, coords, io.errors, workflows._base.
  • Enforced coverage floor (fail_under = 85) replaces the informational-only report.
  • pybmodes.campbell no longer re-exports its private helpers at the package root.
  • README documentation links now point at the rendered Read the Docs pages.

Full detail in the changelog.

pyBmodes 1.13.1

23 May 03:01
7135136

Choose a tag to compare

Highlights

Fixes an asymmetric-FOWT labelling bug where the Campbell diagram could name a floating platform's surge/sway rigid-body modes as "1st tower fore-aft/side-to-side", while the mode-shape plots and reports named them correctly (issue #57). No numerical change to any model. This is figure and label text only.

Fixed

  • Campbell diagram mislabelled floating-platform rigid-body modes as flexible tower bending modes. On a floating turbine the Campbell sweep could name a low-frequency rigid-body mode (surge or sway near 0.01 Hz) "1st tower FA" or "1st tower SS", the same name a flexible bending mode near 0.5 Hz carries. So a user reading "1st tower fore-aft" off the diagram (for example to feed plot_environmental_spectra) got the platform frequency instead of the bending frequency. The mode-shape plots and reports were unaffected. There were three root causes, all fixed.
    • Near-degenerate rigid-body pairs were not recognised as degenerate. A real floater's surge/sway (and roll/pitch) pair is rarely exactly degenerate, because a slightly asymmetric mooring or hull splits it by a fraction of a percent. The eigensolver still returns it in an arbitrary mixed basis that varies run to run. The classifier's degeneracy window was a strict 1e-4, so a sub-percent split fell through un-aligned and was left unnamed in one solve while a sister solve named it. The window is widened to 2e-2, so a near-degenerate pair is rotated onto its platform axes and named deterministically, and the report, mode-shape plot and Campbell now agree. The accept-gate that requires a clean two-DOF separation keeps genuinely distinct close modes untouched.
    • The Campbell tower path solved only n_tower_modes modes, so when fewer than six were requested (the default is four) the rigid-block assignment was truncated. The floating tower is now always classified over the full six-mode rigid block, then sliced back, so the Campbell labels match a direct Tower(...).run().mode_labels.
    • The Campbell fallback that names modes the classifier still leaves None had no rigid-versus-flexible distinction. A None mode inside the rigid-body block is now drawn in the red Platform family, never as flexible "Nth tower FA/SS". Verified across all eleven bundled reference turbines.

Changed

  • Bending-mode names are spelled out in full on the Campbell and environmental diagrams ("flapwise bending", "edgewise bending", "fore-aft bending", "side-to-side bending"). Figure text only. CampbellResult.labels keeps the terse "1st flap" and "1st tower FA" tokens for CSV and API stability.

Docs

  • Installation guide gained an "Updating to a new release" section, including how to upgrade a conda-environment install (it is a pip operation inside the env, and conda update does not apply).

pyBmodes 1.13.0

23 May 02:08
5a0242a

Choose a tag to compare

Off-axis floating support (#100), a clearer Campbell diagram (#57), and docs-build/README fixes. Additive and backward-compatible — no numerical change to any existing model.

Added (#100)

  • PlatformSupport.ref_x / ref_y — horizontal position of the hydro/mooring reference (PtfmRefxt/PtfmRefyt) from the tower axis. The rigid-arm transform now carries hydro_M/hydro_K/mooring_K horizontally to the tower base (previously only the structural inertia), so an off-axis floater (tower on an off-centre column) can be modelled consistently. Settable on a hand-built PlatformSupport and round-tripped through the .bmi format (ref_msl_xyz line). Defaults 0.0 → standard on-axis decks byte-identical.
  • PlatformSupport.tower_base_z — positive-up alias for draft (tower_base_z == -draft).

Changed (#57)

  • Campbell diagram — mode-frequency labels in a clean, de-overlapped right-margin column (kept inside the axes for caller-supplied subplots); per-line dashing within each colour band.

Fixed

  • Docs build — switched the Sphinx theme from furo (failing to provision) to sphinx_rtd_theme; added a Read the Docs status badge.
  • README — repointed documentation links from the 404ing Read the Docs URLs to the GitHub docs/ source; added the conventions guide; added an Updating to a new release section (pip upgrade, version pinning, source-checkout and conda-environment refresh).

Full changelog: https://github.com/SMI-Lab-Inha/pyBModes/blob/master/CHANGELOG.md

pyBmodes 1.12.1

23 May 01:19
d297022

Choose a tag to compare

Documentation release — clarifies the reference-frame conventions that were tripping users up. No code-behaviour change (frequencies, shapes and labels are identical to 1.12.0).

Added

  • Coordinate-systems / conventions guide (docs/conventions.rst): a per-case reference (land / monopile / floating / blade) for the single origin (tower base), axis directions, 6-DOF order, boundary conditions (hub_conn), the tower-top/RNA frame, and — for floaters — the MSL vertical datum with the exact signs of draft / cm_pform / ref_msl, the inertia-only horizontal cm_pform_x/cm_pform_y arm, and the static-equilibrium assumption. Includes a worked OC3 Hywind example and a common-pitfalls list.
  • Field-level docstrings on PlatformSupport, TipMassProps and BMIFile carrying the same conventions. Notably documents that draft is the signed tower-base elevation relative to MSL (negative = above) — a name inherited from the BModes .bmi format, not the naval-architecture draft.

Full changelog: https://github.com/SMI-Lab-Inha/pyBModes/blob/master/CHANGELOG.md

pyBmodes 1.12.0

23 May 00:24
358db08

Choose a tag to compare

Domain-aware input validation (#102): construction-layer guards that catch the mechanical / civil-structural mistakes a non-specialist makes, plus two fixes for review findings on the 1.10.1 / 1.11.0 floating guards. Additive and backward-compatible — no numerical change to any existing model.

Added — construction-layer plausibility guards (#102)

tubular_section_props (the chokepoint for from_geometry / from_windio / from_windio_with_monopile) now emits a UserWarning for physically implausible raw inputs, before a meaningless solve:

  • Material modulus E outside [1e9, 1e12] Pa (catches "E in GPa/MPa instead of Pa").
  • Material density ρ outside [100, 25000] kg/m³ (catches t/m³ or g/cm³).
  • Diameter-to-thickness D/t outside a broad [5, 10000] band — a gross unit/geometry sanity (a ×1000 D-vs-t unit mismatch, or a near-solid / sub-mm wall). Deliberately loose: real towers span D/t ≈ 56–1096 (the high end is the IEA-15 VolturnUS-S floating tower's legitimately thin upper wall), so it is not a fixed-tower shell-buckling check.
  • Taper direction — outer diameter growing base→top (reversed station ordering).

Material checks run on the raw E/ρ — the only safe place, since derived SectionProperties carry convention placeholders (ElastoDyn towers are axially rigid → axial_stff ≠ physical E·A). All bands verified silent on every WindIO reference turbine, fixed and floating.

Fixed

  • Floating-readiness check_model gates no longer fire on fixed-bottom decks. The 1.11.0 gates keyed only on the presence of a PlatformSupport, so they emitted spurious WARN/ERROR on fixed-bottom monopile decks carrying an all-zero PlatformSupport block by layout convention (the bundled 02/04/05/06 samples). They now gate on hub_conn == 2, the genuine free-free floating path.
  • classify_platform_modes no longer raises IndexError on a truncated frequencies array — the degeneracy alignment is skipped (global assignment still labels) instead of indexing past the end.

Full changelog: https://github.com/SMI-Lab-Inha/pyBModes/blob/master/CHANGELOG.md

pyBmodes 1.11.0

22 May 23:39
818aee9

Choose a tag to compare

A minor feature release focused on guarding non-specialist inputs on the floating path, plus a new WindIO monopile constructor. Additive and backward-compatible — no numerical change to any existing model.

Highlights

  • Tower.from_windio_with_monopile(...) (#92) — splice a WindIO monopile + tower into one fixed-bottom cantilever clamped at the mudline (the WindIO analog of from_elastodyn_with_subdyn).
  • Floating-model readiness guards in check_model (#95) — a WindIO .yaml is enough for a land tower but not a floating system, whose rigid-body behaviour comes from hydrodynamics + mooring (companion decks), not the yaml. New gates catch the seakeeping omissions a non-specialist makes.

Added

  • Tower.from_windio_with_monopile(yaml, *, component_tower, component_monopile, thickness_interp, tip_mass, n_nodes) (#92). Reduces the monopile and tower components separately (each keeps its own wall schedule + steel grade) and splices them at the transition piece (from each component's reference_axis.z) into a single cantilever clamped at the mudline (hub_conn=1), RNA via tip_mass. Rigid fixed-base (no soil flexibility — that's tracked in #97); raises ValueError if the segments aren't contiguous. Backed by read_windio_monopile_tower / WindIOMonopileTower; validated end-to-end on IEA-15.

  • Floating-model readiness guards in check_model (#95). For any hub_conn=2 PlatformSupport model:

    • CM offset larger than the platform's yaw radius of gyration √(I_yaw/m) → WARN (catches a coordinate-origin offset leaking into cm_pform_x/cm_pform_y, which mislabels the rigid-body modes). Tunable via CheckOptions.platform_cm_offset_gyradius_factor.
    • No added mass (hydro_M/A_inf all zero) → WARN (biases all rigid-body frequencies high).
    • No restoring (hydro_K and mooring_K both zero) → WARN (modes collapse to ~0 Hz).
    • Non-physical platform inertia (non-positive mass or i_matrix diagonal) → ERROR.
      Each message names the fix and points at the validated deck path. PlatformSupport.cm_pform_x/cm_pform_y docs now state they are tower-axis-relative, not a global coordinate.
  • pybmodes.io.windio.WindIOTubular now exposes z_base / z_top (absolute elevations from reference_axis.z); additive defaulted fields.

Full changelog: https://github.com/SMI-Lab-Inha/pyBModes/blob/master/CHANGELOG.md

pyBmodes 1.10.1

22 May 15:48
b8c837a

Choose a tag to compare

A labelling-only bug-fix release: the floating-platform rigid-body mode classifier (ModalResult.mode_labels) mislabelled asymmetric platforms. Frequencies and mode shapes are unchanged on every deck.

Fixed

  • Asymmetric-platform rigid-body mode labels (#93). The classifier named the six lowest modes by a greedy per-mode argmax over mass-weighted base-node modal kinetic energy, dropping any DOF name already taken by an earlier mode. On an asymmetric platform a genuine surge mode carrying a small parasitic high-inertia rotation reads, mass-weighted, as yaw-dominated — so it stole yaw, the true yaw mode was then forced to None, and the mislabel cascaded (reported as "surge classified as yaw, sway as surge, third mode unclassified"). Labels are now assigned by a global Hungarian optimal matching (scipy.optimize.linear_sum_assignment) over the 6 rigid candidates × 6 platform DOFs, so the mode that best expresses each DOF wins that label and no DOF is named twice. The 0.6 dominance threshold still gates each assignment, so a genuinely coupled pair stays None rather than mislabelled.

  • Deterministic rigid-body labels for symmetric platforms (degenerate pairs). On a (bi)symmetric platform surge≈sway and roll≈pitch share an eigenvalue, so the eigensolver may return any rotation of that 2-D eigenspace (BLAS-thread-order dependent — the hazard fixed for the FA/SS tower pair in 1.10.0). A 45°-mixed pair reads 50/50 and could be silently left None. classify_platform_modes now takes the modal frequencies and rotates each frequency-degenerate rigid pair back onto its platform axes before labelling (the rigid-body analog of _rotate_degenerate_pairs in pybmodes.elastodyn.params), so the result no longer depends on the arbitrary eigensolver basis. Asymmetric platforms break the degeneracy, so this step is a no-op there.

Both changes are confined to mode labelling; the new frequencies argument on classify_platform_modes is optional (defaults to None), so the change is backward-compatible. Symmetric decks (OC3 Hywind, IEA-15 UMaineSemi) keep their existing labels.

Full changelog: https://github.com/SMI-Lab-Inha/pyBModes/blob/master/CHANGELOG.md