Skip to content

Commit efb8cfd

Browse files
committed
release: 1.10.1
Promote the [Unreleased] CHANGELOG block to [1.10.1] (2026-05-23) and bump the version in pyproject.toml and the __init__ dev fallback. Patch release: a labelling-only fix for floating-platform rigid-body mode classification (#93); frequencies and mode shapes are unchanged on every deck.
1 parent 382cf59 commit efb8cfd

3 files changed

Lines changed: 52 additions & 2 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,56 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
88

99
## [Unreleased]
1010

11+
## [1.10.1] — 2026-05-23
12+
13+
A labelling-only bug-fix release: the floating-platform rigid-body mode
14+
classifier (`ModalResult.mode_labels`) mislabelled asymmetric platforms.
15+
Frequencies and mode shapes are unchanged on every deck.
16+
17+
### Fixed
18+
19+
- **Asymmetric-platform rigid-body mode labels (`ModalResult.mode_labels`).**
20+
The floating-platform rigid-mode classifier
21+
(`pybmodes.fem.platform_modes.classify_platform_modes`) named each of the
22+
six lowest modes by a *greedy* per-mode argmax over mass-weighted
23+
base-node modal kinetic energy, dropping any DOF name already taken by an
24+
earlier mode. On an asymmetric platform (horizontal CM offset or
25+
off-diagonal mooring/hydro coupling) a genuine surge mode carrying a small
26+
parasitic high-inertia rotation reads, mass-weighted, as yaw-dominated —
27+
so it stole `yaw`, the true yaw mode then hit the "already used" branch
28+
and was left `None`, and the mislabel cascaded onto the neighbouring modes
29+
(reported as "surge classified as yaw, sway as surge, third mode
30+
unclassified"). The labels are now assigned by a **global Hungarian
31+
optimal matching** (`scipy.optimize.linear_sum_assignment`, already used
32+
in `pybmodes.mac`) over the 6 rigid candidates × 6 platform DOFs, so the
33+
mode that best expresses each DOF wins that label and no DOF is ever named
34+
twice. The conservative dominance threshold (0.6) still gates each
35+
assignment, so a genuinely coupled / rotated pair whose energy splits
36+
across DOFs stays `None` rather than mislabelled. (#93)
37+
38+
- **Deterministic rigid-body labels for symmetric platforms (degenerate
39+
pairs).** On a (bi)symmetric platform surge≈sway and roll≈pitch share an
40+
eigenvalue, so the eigensolver may return any rotation of that 2-D
41+
eigenspace — a basis that depends on BLAS thread ordering (the same
42+
non-determinism hazard fixed for the FA/SS *tower* pair in 1.10.0). A
43+
45°-mixed degenerate pair reads 50 / 50 across two DOFs and would fall
44+
below the dominance threshold, so the modes could be silently left
45+
`None`. `classify_platform_modes` now takes the modal `frequencies` and
46+
rotates each frequency-degenerate rigid pair back onto its platform axes
47+
before labelling (the rigid-body analog of `_rotate_degenerate_pairs` in
48+
`pybmodes.elastodyn.params`), accepting the rotation only when it cleanly
49+
separates the pair into two distinct dominant DOFs. The result no longer
50+
depends on the arbitrary eigensolver basis. Asymmetric platforms break the
51+
degeneracy, so this step is a no-op there.
52+
53+
Both changes are confined to mode **labelling**; frequencies and mode
54+
shapes are byte-for-byte unchanged, and symmetric decks (OC3 Hywind, IEA-15
55+
UMaineSemi, …) — whose rigid modes are ~98 % single-DOF — keep their
56+
existing labels. The new `frequencies` argument on
57+
`classify_platform_modes` is optional and keyword-only-friendly (defaults to
58+
`None`, which skips the degeneracy resolution), so the change is
59+
backward-compatible.
60+
1161
## [1.10.0] — 2026-05-22
1262

1363
Static-review follow-up on top of 1.9.0: a deterministic fix for the

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "pybmodes"
7-
version = "1.10.0"
7+
version = "1.10.1"
88
description = "Python finite-element library for wind turbine blade and tower modal analysis (OpenFAST/ElastoDyn)"
99
readme = "README.md"
1010
license = { file = "LICENSE" }

‎src/pybmodes/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@
178178
try:
179179
__version__ = version("pybmodes")
180180
except PackageNotFoundError:
181-
__version__ = "1.10.0"
181+
__version__ = "1.10.1"
182182

183183
__all__ = [
184184
"CheckOptions",

0 commit comments

Comments
 (0)