Skip to content

Releases: SMI-Lab-Inha/pyBModes

pyBmodes 1.18.0

Choose a tag to compare

@SMI-Lab-Inha SMI-Lab-Inha released this 13 Aug 05:27
efc187e

Highlights

Five features closing the open issue backlog, plus a correctness fix in the eigensolver that is worth reading if you model very heavy tower tops.

A silent wrong answer is now caught. scipy.linalg.eigh reduces the generalised problem through a Cholesky factor of the mass matrix, and that reduction quietly loses accuracy when a very light beam carries a very heavy lump. LAPACK does not raise there, it returns wrong frequencies: on a 100 m cantilever at a 4000:1 lump-to-beam mass ratio the reported fundamental was 0.103 Hz against a true 0.0436 Hz, and the answer wandered with mesh density. solve_modes now measures the backward error of a dense symmetric solve and redoes it on the general path when that error is large, taking the retry only when it genuinely resolves a failed mode. A RuntimeWarning names the swap and SolverDiagnostics.residual_fallback records it. The sparse path is deliberately untouched, since eigsh(sigma=0) factorises K rather than the mass matrix and does not have this failure.

Modelling reach. Discrete point masses anywhere on the beam (#35), self-weight geometric softening (#134), distributed Winkler soil springs on the monopile path (#118), and n_nodes mesh control on the deck and BMI readers (#58).

Domain-aware validation. New pre-solve checks for civil-structural and geotechnical inputs, so an implausible shell slenderness or embedment ratio is caught before the solve rather than showing up as a strange frequency.

Note on the version number

1.18.0 was prepared on 2026-08-12 but never tagged or published. The eigensolver fix then landed on top of it, and both are folded into this single release rather than leaving a 1.18.0 that appears in the changelog but cannot be installed.


Added

  • Discrete point masses at any station (#35).
    Tower.add_point_mass(height, mass) attaches a lumped mass anywhere on
    the beam, filling the gap between outfitting_factor (smeared over the
    whole tower) and tip_mass (a single lump at the top) — a flange, an
    internal platform, a transformer, a boat-landing. The lump is assembled
    through the element shape functions at its exact station, so it does
    not have to land on a mesh node and the answer does not move if the
    mesh does. Validated against the closed-form cantilever-with-lump
    frequency. Its own rotary inertia about its centre is not modelled;
    use tip_mass where that term matters.
  • Self-weight geometric softening (#134).
    Tower.run(gravity=True) (or gravity=9.80665) puts the weight of the
    tower, the RNA and any point masses into the column as an axial load,
    which lowers the bending frequencies — typically around 2 % on the 1st
    fore-aft mode of a large machine, and usually the single largest term
    when reconciling against a tool that models gravity by default. Off
    by default
    , so every existing result is unchanged and the validated
    reference cases stay pinned to the BModes-equivalent behaviour. The
    geometric stiffness is anchored to two published buckling loads with
    different load distributions, Euler tip-load and Greenhill self-weight,
    both to 0.2 %. Refused for a free-base floating model, where buoyancy
    would have to be netted against the weight, and for the pinned-free
    cable BC.
  • Distributed Winkler soil springs on the monopile path (#118).
    MudlineFoundation.distributed_springs() turns the soil the lumped
    mudline springs were built from into a spring rate along the embedded
    pile, k = D_P E_SO (z / L_P)^n with the Shadlou and Bhattacharya
    inhomogeneity exponents. Tower.attach_mudline_foundation(foundation, distributed=True) lays it into distr_k, and
    Tower.from_windio_with_monopile(..., soil_distributed=True) keeps the
    embedded pile in the beam so it deflects against the soil over its real
    length rather than being condensed onto a base spring. Cross-validated
    against the lumped tier, which is the exact static condensation of the
    same beam and bed: the two agree to 0.4 %, the residual being the
    embedded pile's own inertia the condensed form drops.
  • n_nodes on the deck and BMI readers (#58). Tower and
    RotatingBlade gain a refine_mesh(n_nodes) method and an n_nodes
    keyword on every deck constructor — Tower(...), from_bmi,
    from_elastodyn, from_elastodyn_with_subdyn,
    from_elastodyn_with_mooring, RotatingBlade(...) and its
    from_elastodyn. The geometry constructors already had it, so the
    keyword is now uniform across the whole surface. It stays opt-in
    because re-gridding a deck re-samples an already tabulated property
    table rather than recomputing exact tube properties: a UserWarning
    names any deliberate property step the new mesh misses, and a deck with
    tension-wire supports is refused outright, since those attach to node
    numbers that would silently move.
  • Domain-aware validation for the civil-structural and geotechnical
    disciplines (#102).
    check_model gains a fixed-bottom D / t band —
    the support-type-aware tightening of the deliberately wide band
    tubular_section_props applies at construction, which cannot know the
    boundary condition and has to admit a floating tower's far thinner
    shell. It also reports a monopile clamped rigidly at the mudline with
    no soil springs as non-conservative (INFO) and an implausible embedment
    ratio L / D (WARN). New check_solved_frequencies flags a
    fixed-bottom tower whose first mode lands outside 0.01-10 Hz, which is
    where a compounding scale error shows up when two inputs are wrong in
    compensating directions and each passes its own band; it runs
    automatically on Tower.run(check_model=True).
  • CheckOptions gains diameter_thickness_min / _max and
    embedment_ratio_min / _max for the new bands.
  • read_windio_monopile_tower gains clamp_at_mudline, and its result
    carries the two reduced segments so a caller can read the raw tube and
    material each was built from.

Fixed

  • The dense symmetric eigensolver could return confidently wrong low
    modes on a near-singular mass matrix, silently.
    scipy.linalg.eigh
    reduces K x = λ M x through a Cholesky factor of the mass matrix,
    and that reduction loses accuracy when a very light beam carries a very
    heavy lump. LAPACK does not raise there — it returns wrong frequencies.
    On a 100 m cantilever with a 4000:1 lump-to-beam mass ratio the
    reported fundamental was 0.103 Hz against a true 0.0436 Hz, a factor of
    2.4, and the answer wandered non-monotonically with mesh density.

    solve_modes now checks the backward error ||K x - λ M x|| / ||K x||
    of a dense symmetric solve and, when it is large, redoes it through
    the general dense path, which factorises neither matrix. The retried
    result is taken only when it resolves a mode the symmetric solve
    had failed — improving it by more than 1000× and reaching a backward
    error of 1e-3 or better. Both conditions are needed because a
    rigid-body mode's residual divides one roundoff quantity by another,
    so its value is arbitrary (0.076, 0.79 and 12.4 have all been measured
    on healthy models) while its improvement ratio stays near 10x, four
    orders short of the 1e5 to 1e10 a real rescue achieves. A RuntimeWarning names
    the swap, and attributes it to the mass matrix only when the mass
    conditioning supports that — a wide stiffness range trips the same
    guard with a perfectly conditioned mass. SolverDiagnostics gains
    residual_fallback recording it.

    The sparse path is deliberately not retried and never sets
    residual_fallback. eigsh(sigma=0, mode='normal') factorises K
    rather than the mass matrix, so a near-singular M does not degrade
    it — on the mesh sweep that motivated this work it returned correct
    frequencies on exactly the meshes large enough to select it. Retrying
    it would also mean comparing two different mode sets, since
    which="LM" selects the modes nearest zero in magnitude while the
    retry selects the algebraically smallest.

    No existing result changes. The decisive-improvement condition is
    what guarantees that: a real deck can carry a large backward error
    without being broken, and on the bundled NREL 5MW land tower (whose
    adapter leaves the mass matrix at cond ~4e10) the general path is only
    1.4× better while splitting a degenerate fore-aft / side-side pair
    the symmetric solver resolves exactly.

    Acceptance requires the candidate to be non-regressive as well as
    decisively better somewhere. Taking the retry replaces the whole
    spectrum, not the modes that prompted it, so a candidate that rescues
    one mode while pushing a previously acceptable one above the failure
    threshold is refused — it would hand back a new bad mode in place of an
    old one. The guarantee is one-sided and precise: a mode that was
    acceptable can end up above the regression floor only by not having got
    worse, never as collateral of another mode's rescue. Below that floor
    it is free to move either way, which is deliberate — a residual already
    that small is not a claim about accuracy worth defending. A mode
    already failing carries no verdict either way — above the threshold
    neither candidate is trustworthy, and a rigid-body mode, whose residual
    divides one roundoff quantity by another and has been measured at 12.4
    against 0.79 on a healthy model, lives entirely in that region.
    max_residual still reports it.

    The comparison is made per mode rather than on the two maxima, so
    that rigid-body modes cannot distort it. Their backward error is a
    ratio of two near-zero quantities and reads ~1 in both candidates
    however exact each is; on a maximum that floors the alternative and
    hides a genuinely corrupted elastic mode sitting alongside them, while
    per mode it simply registers as no improvement. The retry preserves
    zero and negative eigenvalues and verifies that nothing w...

Read more

pyBmodes 1.17.0

Choose a tag to compare

@SMI-Lab-Inha SMI-Lab-Inha released this 10 Jul 12:18
977b3d8

Highlights

A WindIO tower-ecosystem minor, driven by user feedback. Three additive, non-breaking features.

Added

  • Material / outfitting overrides on the WindIO tower paths (#133). Tower.from_windio and Tower.from_windio_with_monopile take optional E, rho, nu and outfitting_factor. Each defaults to None (use the ontology value); pass a number to override it, so a material or outfitting sensitivity sweep runs straight off a WindIO file without editing the yaml. On the combined monopile+tower path the override applies to both segments.

  • Per-mode generalised mass and stiffness on ModalResult (#134). A solve now attaches generalized_mass (kg) and generalized_stiffness (N/m) arrays, one entry per mode, normalised to unit tip lateral displacement, so a mode's modal mass and stiffness can be read off and compared against another tool's modal report. stiffness = (2*pi*f)^2 * mass by construction; a rigid-body platform mode (tip barely moves) yields NaN.

  • 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 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=...) 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). Fully distributed Winkler distr_k springs remain a separate higher-fidelity follow-up.

All additive; no public API removed or renamed.

pyBmodes 1.16.1

Choose a tag to compare

@SMI-Lab-Inha SMI-Lab-Inha released this 05 Jul 23:44
6feed7d

Highlights

Patch release correcting the WindIO auto-RNA rotor inertia (#130). The auto-RNA (read_windio_rna / lumped_rna_cal=True) previously lumped the blades as a bare point mass at the rotor apex, dropping the rotor's own diametral inertia from the blade mass spread along the span. On IEA-22 that missing term is ~2.5x the value the point-mass lump produced, so tower fore-aft / side-side frequencies came out too high against a rigid-rotor reference. The rotor is now assembled as a proper rigid body about its coned centre of mass.

Total RNA mass is unchanged; the inertia and, for coned rotors, the centre of mass change relative to 1.16.0.

Added

  • angle_units control on the WindIO auto-RNA. read_windio_rna gains angle_units ("auto" / "rad" / "deg"), forwarded from Tower.from_windio and Tower.from_windio_with_monopile as rna_angle_units. The WindIO v2 schema annotates cone_angle / uptilt as degrees while the IEA reference ontologies store radians; "auto" (default) disambiguates by magnitude, and "rad" / "deg" take the file at its word for the rare all-sub-degree case "auto" cannot resolve.

Fixed

  • WindIO auto-RNA now carries the rotor inertia from the spanwise blade mass (#130). The rotor is assembled as a rigid body, diag([I_polar, I_diam, I_diam]) about the rotor centre of mass with I_polar = N_bl · ∫ (dm/ds) · r² ds and the in-plane lever r = (hub_radius + span)·cos(cone) (the coned pitch-axis distance, matching WISDEM/ElastoDyn, with prebend and sweep folded in), using the hub diameter and cone angle when present. A coned rotor's mass sits off the hub plane, so it is placed at its true centre of mass before the parallel-axis shift, which also moves the tower-top centre of mass slightly (by the precone term) for a non-zero cone_angle. The span is measured from the blade root, and a hub tensor's off-diagonal terms are rotated with the correct WindIO hub-frame handedness. Only each blade's own sectional spin inertia is still excluded.
  • The auto-RNA rejects one- and two-bladed rotors. The axisymmetric I_polar/2 transverse split holds only for three or more evenly spaced blades; a one- or two-bladed rotor has an azimuth-dependent transverse inertia that a single rigid tower-top lump cannot represent, so read_windio_rna now raises a clear ValueError pointing at an explicit tip_mass.

pyBmodes 1.16.0

Choose a tag to compare

@SMI-Lab-Inha SMI-Lab-Inha released this 03 Jul 09:02
5687b2f

Highlights

  • WindIO auto-RNA (#82) — Tower.from_windio(..., lumped_rna_cal=True) and from_windio_with_monopile(..., lumped_rna_cal=True) derive the tower-top rotor-nacelle assembly from an IEA-22-class ontology's elastic_properties_mb blocks automatically, so you no longer hand-compute tip_mass. Mass and CM match the IEA-22 ElastoDyn deck to better than 0.1 %.
  • Monopile mudline fix (#121) — from_windio_with_monopile now takes a water_depth argument and clamps at the true seabed, dropping the embedded pile below it. Previously an embedded monopile (e.g. IEA-15, axis −75→+15 m, mudline −30 m) was modelled with the buried length as a free cantilever, dragging the frequency far too low.
  • conda-forge — conda install -c conda-forge pybmodes now works alongside pip install pybmodes.

Install with pip install pybmodes==1.16.0 or conda install -c conda-forge pybmodes (once the feedstock updates).


Added

  • Auto-derive the tower-top RNA from a WindIO ontology (#82). Tower.from_windio(..., lumped_rna_cal=True) and Tower.from_windio_with_monopile(..., lumped_rna_cal=True) assemble the hub, nacelle and blades of an IEA-22-class ontology (the elastic_properties_mb schema) into the tower-top lumped mass automatically, so the RNA no longer has to be hand-computed and passed as tip_mass. Backed by the new pybmodes.io.windio.read_windio_rna, which mirrors the ElastoDyn tower-top assembler. Mass and centre of mass reproduce the matching IEA-22 ElastoDyn deck to better than 0.1 percent; the rotary inertia is the ontology's own (WindIO-native) value, which is not expected to byte-match a separately authored deck. Ontologies without the hub and nacelle lumped blocks (IEA-15) raise a clear error, so pass tip_mass explicitly there. lumped_rna_cal is mutually exclusive with tip_mass.
  • conda-forge availability. pyBmodes is now packaged on conda-forge, so conda install -c conda-forge pybmodes works alongside pip install pybmodes. The feedstock builds the noarch package from each PyPI sdist, so the conda channel tracks PyPI releases. The README and installation guide cover the conda path and how to add the optional dependencies (matplotlib, pyyaml) that the pip extras would otherwise pull in.

Fixed

  • from_windio_with_monopile clamped the embedded pile instead of the mudline (#121). When a WindIO monopile reference_axis.z runs below the seabed (for example IEA-15, whose axis spans −75 m to +15 m with the mudline at −30 m), the combined cantilever was clamped at the pile tip and the whole embedded length was modelled as a free cantilever, so the fundamental frequency came out far too low. The constructor now takes a water_depth argument (also read from the ontology's environment.water_depth when present) and clamps at the mudline, dropping the embedded stations below it. With no water depth available the monopile base is still taken as the clamp, so ontologies whose axis already begins at the mudline are unchanged. read_windio_monopile_tower gains the same water_depth keyword.

pyBmodes 1.15.1

Choose a tag to compare

@SMI-Lab-Inha SMI-Lab-Inha released this 29 May 23:20
4b94add

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

Choose a tag to compare

@SMI-Lab-Inha SMI-Lab-Inha released this 29 May 11:15
8b1ee6f

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

Choose a tag to compare

@SMI-Lab-Inha SMI-Lab-Inha released this 23 May 09:09
4e3af9a

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

Choose a tag to compare

@SMI-Lab-Inha SMI-Lab-Inha released this 23 May 04:55
50a5838

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

Choose a tag to compare

@SMI-Lab-Inha SMI-Lab-Inha released this 23 May 03:01
7135136

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

Choose a tag to compare

@SMI-Lab-Inha SMI-Lab-Inha released this 23 May 02:08
5a0242a

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