pybmodes 1.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. pybmodes/__init__.py +188 -0
  2. pybmodes/_examples/__init__.py +45 -0
  3. pybmodes/_examples/reference_decks/FLOATING_CASES.md +144 -0
  4. pybmodes/_examples/reference_decks/README.md +150 -0
  5. pybmodes/_examples/reference_decks/VALIDATION_SUMMARY.md +63 -0
  6. pybmodes/_examples/reference_decks/iea15mw_umainesemi/IEA-15-240-RWT-UMaineSemi_ElastoDyn.dat +261 -0
  7. pybmodes/_examples/reference_decks/iea15mw_umainesemi/IEA-15-240-RWT-UMaineSemi_Tower.dat +51 -0
  8. pybmodes/_examples/reference_decks/iea15mw_umainesemi/IEA-15-240-RWT_Blade.dat +82 -0
  9. pybmodes/_examples/reference_decks/iea15mw_umainesemi/before_patch.txt +17 -0
  10. pybmodes/_examples/reference_decks/iea15mw_umainesemi/validation_report.txt +42 -0
  11. pybmodes/_examples/reference_decks/iea34_land/IEA-3.4-130-RWT_Blade.dat +62 -0
  12. pybmodes/_examples/reference_decks/iea34_land/IEA-3.4-130-RWT_ElastoDyn.dat +162 -0
  13. pybmodes/_examples/reference_decks/iea34_land/IEA-3.4-130-RWT_Tower.dat +51 -0
  14. pybmodes/_examples/reference_decks/iea34_land/before_patch.txt +17 -0
  15. pybmodes/_examples/reference_decks/iea34_land/validation_report.txt +16 -0
  16. pybmodes/_examples/reference_decks/nrel5mw_land/NRELOffshrBsline5MW_Blade.dat +83 -0
  17. pybmodes/_examples/reference_decks/nrel5mw_land/NRELOffshrBsline5MW_Onshore_ElastoDyn.dat +185 -0
  18. pybmodes/_examples/reference_decks/nrel5mw_land/NRELOffshrBsline5MW_Tower.dat +54 -0
  19. pybmodes/_examples/reference_decks/nrel5mw_land/before_patch.txt +17 -0
  20. pybmodes/_examples/reference_decks/nrel5mw_land/validation_report.txt +16 -0
  21. pybmodes/_examples/reference_decks/nrel5mw_oc3monopile/NRELOffshrBsline5MW_Blade.dat +83 -0
  22. pybmodes/_examples/reference_decks/nrel5mw_oc3monopile/NRELOffshrBsline5MW_OC3Monopile_ElastoDyn.dat +165 -0
  23. pybmodes/_examples/reference_decks/nrel5mw_oc3monopile/NRELOffshrBsline5MW_OC3Monopile_SubDyn.dat +109 -0
  24. pybmodes/_examples/reference_decks/nrel5mw_oc3monopile/NRELOffshrBsline5MW_OC3Monopile_Tower.dat +54 -0
  25. pybmodes/_examples/reference_decks/nrel5mw_oc3monopile/before_patch.txt +17 -0
  26. pybmodes/_examples/reference_decks/nrel5mw_oc3monopile/validation_report.txt +16 -0
  27. pybmodes/_examples/reference_decks/nrel5mw_oc3spar/NRELOffshrBsline5MW_Blade.dat +83 -0
  28. pybmodes/_examples/reference_decks/nrel5mw_oc3spar/NRELOffshrBsline5MW_OC3Hywind_ElastoDyn.dat +234 -0
  29. pybmodes/_examples/reference_decks/nrel5mw_oc3spar/NRELOffshrBsline5MW_OC3Hywind_Tower.dat +54 -0
  30. pybmodes/_examples/reference_decks/nrel5mw_oc3spar/before_patch.txt +17 -0
  31. pybmodes/_examples/reference_decks/nrel5mw_oc3spar/validation_report.txt +16 -0
  32. pybmodes/_examples/reference_decks/nrel5mw_oc4semi/NRELOffshrBsline5MW_Blade.dat +83 -0
  33. pybmodes/_examples/reference_decks/nrel5mw_oc4semi/NRELOffshrBsline5MW_OC4DeepCwindSemi_ElastoDyn.dat +189 -0
  34. pybmodes/_examples/reference_decks/nrel5mw_oc4semi/NRELOffshrBsline5MW_OC4DeepCwindSemi_Tower.dat +54 -0
  35. pybmodes/_examples/reference_decks/nrel5mw_oc4semi/before_patch.txt +17 -0
  36. pybmodes/_examples/reference_decks/nrel5mw_oc4semi/validation_report.txt +16 -0
  37. pybmodes/_examples/sample_inputs/01_uniform_blade/README.md +75 -0
  38. pybmodes/_examples/sample_inputs/01_uniform_blade/uniform_blade.bmi +51 -0
  39. pybmodes/_examples/sample_inputs/01_uniform_blade/uniform_blade_sec_props.dat +7 -0
  40. pybmodes/_examples/sample_inputs/02_tower_topmass/README.md +82 -0
  41. pybmodes/_examples/sample_inputs/02_tower_topmass/tower_topmass.bmi +54 -0
  42. pybmodes/_examples/sample_inputs/02_tower_topmass/tower_topmass_sec_props.dat +7 -0
  43. pybmodes/_examples/sample_inputs/03_rotating_uniform_blade/README.md +103 -0
  44. pybmodes/_examples/sample_inputs/03_rotating_uniform_blade/rotating_blade.bmi +51 -0
  45. pybmodes/_examples/sample_inputs/03_rotating_uniform_blade/rotating_blade_sec_props.dat +7 -0
  46. pybmodes/_examples/sample_inputs/04_pinned_free_cable/README.md +94 -0
  47. pybmodes/_examples/sample_inputs/04_pinned_free_cable/cable.bmi +51 -0
  48. pybmodes/_examples/sample_inputs/04_pinned_free_cable/cable_sec_props.dat +7 -0
  49. pybmodes/_examples/sample_inputs/README.md +115 -0
  50. pybmodes/_examples/sample_inputs/reference_turbines/01_nrel5mw_land/01_nrel5mw_land_blade.bmi +51 -0
  51. pybmodes/_examples/sample_inputs/reference_turbines/01_nrel5mw_land/01_nrel5mw_land_blade_sec_props.dat +54 -0
  52. pybmodes/_examples/sample_inputs/reference_turbines/01_nrel5mw_land/01_nrel5mw_land_tower.bmi +60 -0
  53. pybmodes/_examples/sample_inputs/reference_turbines/01_nrel5mw_land/01_nrel5mw_land_tower_sec_props.dat +16 -0
  54. pybmodes/_examples/sample_inputs/reference_turbines/01_nrel5mw_land/README.md +54 -0
  55. pybmodes/_examples/sample_inputs/reference_turbines/02_nrel5mw_oc3monopile/02_nrel5mw_oc3monopile_blade.bmi +51 -0
  56. pybmodes/_examples/sample_inputs/reference_turbines/02_nrel5mw_oc3monopile/02_nrel5mw_oc3monopile_blade_sec_props.dat +54 -0
  57. pybmodes/_examples/sample_inputs/reference_turbines/02_nrel5mw_oc3monopile/02_nrel5mw_oc3monopile_tower.bmi +100 -0
  58. pybmodes/_examples/sample_inputs/reference_turbines/02_nrel5mw_oc3monopile/02_nrel5mw_oc3monopile_tower_sec_props.dat +20 -0
  59. pybmodes/_examples/sample_inputs/reference_turbines/02_nrel5mw_oc3monopile/README.md +54 -0
  60. pybmodes/_examples/sample_inputs/reference_turbines/03_iea34_land/03_iea34_land_blade.bmi +51 -0
  61. pybmodes/_examples/sample_inputs/reference_turbines/03_iea34_land/03_iea34_land_blade_sec_props.dat +35 -0
  62. pybmodes/_examples/sample_inputs/reference_turbines/03_iea34_land/03_iea34_land_tower.bmi +60 -0
  63. pybmodes/_examples/sample_inputs/reference_turbines/03_iea34_land/03_iea34_land_tower_sec_props.dat +15 -0
  64. pybmodes/_examples/sample_inputs/reference_turbines/03_iea34_land/README.md +54 -0
  65. pybmodes/_examples/sample_inputs/reference_turbines/04_iea10_monopile/04_iea10_monopile_blade.bmi +51 -0
  66. pybmodes/_examples/sample_inputs/reference_turbines/04_iea10_monopile/04_iea10_monopile_blade_sec_props.dat +35 -0
  67. pybmodes/_examples/sample_inputs/reference_turbines/04_iea10_monopile/04_iea10_monopile_tower.bmi +100 -0
  68. pybmodes/_examples/sample_inputs/reference_turbines/04_iea10_monopile/04_iea10_monopile_tower_sec_props.dat +35 -0
  69. pybmodes/_examples/sample_inputs/reference_turbines/04_iea10_monopile/README.md +54 -0
  70. pybmodes/_examples/sample_inputs/reference_turbines/05_iea15_monopile/05_iea15_monopile_blade.bmi +51 -0
  71. pybmodes/_examples/sample_inputs/reference_turbines/05_iea15_monopile/05_iea15_monopile_blade_sec_props.dat +55 -0
  72. pybmodes/_examples/sample_inputs/reference_turbines/05_iea15_monopile/05_iea15_monopile_tower.bmi +100 -0
  73. pybmodes/_examples/sample_inputs/reference_turbines/05_iea15_monopile/05_iea15_monopile_tower_sec_props.dat +44 -0
  74. pybmodes/_examples/sample_inputs/reference_turbines/05_iea15_monopile/README.md +54 -0
  75. pybmodes/_examples/sample_inputs/reference_turbines/06_iea22_monopile/06_iea22_monopile_blade.bmi +51 -0
  76. pybmodes/_examples/sample_inputs/reference_turbines/06_iea22_monopile/06_iea22_monopile_blade_sec_props.dat +64 -0
  77. pybmodes/_examples/sample_inputs/reference_turbines/06_iea22_monopile/06_iea22_monopile_tower.bmi +100 -0
  78. pybmodes/_examples/sample_inputs/reference_turbines/06_iea22_monopile/06_iea22_monopile_tower_sec_props.dat +45 -0
  79. pybmodes/_examples/sample_inputs/reference_turbines/06_iea22_monopile/README.md +54 -0
  80. pybmodes/_examples/sample_inputs/reference_turbines/07_nrel5mw_oc3hywind_spar/07_nrel5mw_oc3hywind_spar_blade.bmi +51 -0
  81. pybmodes/_examples/sample_inputs/reference_turbines/07_nrel5mw_oc3hywind_spar/07_nrel5mw_oc3hywind_spar_blade_sec_props.dat +54 -0
  82. pybmodes/_examples/sample_inputs/reference_turbines/07_nrel5mw_oc3hywind_spar/07_nrel5mw_oc3hywind_spar_tower.bmi +100 -0
  83. pybmodes/_examples/sample_inputs/reference_turbines/07_nrel5mw_oc3hywind_spar/07_nrel5mw_oc3hywind_spar_tower_sec_props.dat +16 -0
  84. pybmodes/_examples/sample_inputs/reference_turbines/07_nrel5mw_oc3hywind_spar/README.md +54 -0
  85. pybmodes/_examples/sample_inputs/reference_turbines/08_nrel5mw_oc4semi/08_nrel5mw_oc4semi_blade.bmi +51 -0
  86. pybmodes/_examples/sample_inputs/reference_turbines/08_nrel5mw_oc4semi/08_nrel5mw_oc4semi_blade_sec_props.dat +54 -0
  87. pybmodes/_examples/sample_inputs/reference_turbines/08_nrel5mw_oc4semi/08_nrel5mw_oc4semi_tower.bmi +100 -0
  88. pybmodes/_examples/sample_inputs/reference_turbines/08_nrel5mw_oc4semi/08_nrel5mw_oc4semi_tower_sec_props.dat +16 -0
  89. pybmodes/_examples/sample_inputs/reference_turbines/08_nrel5mw_oc4semi/README.md +54 -0
  90. pybmodes/_examples/sample_inputs/reference_turbines/09_iea15_umainesemi/09_iea15_umainesemi_blade.bmi +51 -0
  91. pybmodes/_examples/sample_inputs/reference_turbines/09_iea15_umainesemi/09_iea15_umainesemi_blade_sec_props.dat +55 -0
  92. pybmodes/_examples/sample_inputs/reference_turbines/09_iea15_umainesemi/09_iea15_umainesemi_tower.bmi +100 -0
  93. pybmodes/_examples/sample_inputs/reference_turbines/09_iea15_umainesemi/09_iea15_umainesemi_tower_sec_props.dat +15 -0
  94. pybmodes/_examples/sample_inputs/reference_turbines/09_iea15_umainesemi/README.md +54 -0
  95. pybmodes/_examples/sample_inputs/reference_turbines/10_iea22_semi/10_iea22_semi_blade.bmi +51 -0
  96. pybmodes/_examples/sample_inputs/reference_turbines/10_iea22_semi/10_iea22_semi_blade_sec_props.dat +64 -0
  97. pybmodes/_examples/sample_inputs/reference_turbines/10_iea22_semi/10_iea22_semi_tower.bmi +100 -0
  98. pybmodes/_examples/sample_inputs/reference_turbines/10_iea22_semi/10_iea22_semi_tower_sec_props.dat +35 -0
  99. pybmodes/_examples/sample_inputs/reference_turbines/10_iea22_semi/README.md +54 -0
  100. pybmodes/_examples/sample_inputs/reference_turbines/11_upscale25_centraltower/11_upscale25_centraltower_blade.bmi +51 -0
  101. pybmodes/_examples/sample_inputs/reference_turbines/11_upscale25_centraltower/11_upscale25_centraltower_blade_sec_props.dat +58 -0
  102. pybmodes/_examples/sample_inputs/reference_turbines/11_upscale25_centraltower/11_upscale25_centraltower_tower.bmi +100 -0
  103. pybmodes/_examples/sample_inputs/reference_turbines/11_upscale25_centraltower/11_upscale25_centraltower_tower_sec_props.dat +25 -0
  104. pybmodes/_examples/sample_inputs/reference_turbines/11_upscale25_centraltower/README.md +54 -0
  105. pybmodes/_examples/sample_inputs/reference_turbines/README.md +170 -0
  106. pybmodes/_examples/sample_inputs/reference_turbines/build.py +1585 -0
  107. pybmodes/_examples/sample_inputs/verify.py +169 -0
  108. pybmodes/campbell/__init__.py +112 -0
  109. pybmodes/campbell/_classify.py +119 -0
  110. pybmodes/campbell/_mac.py +90 -0
  111. pybmodes/campbell/_models.py +210 -0
  112. pybmodes/campbell/_plot.py +393 -0
  113. pybmodes/campbell/_sweep.py +347 -0
  114. pybmodes/campbell/result.py +271 -0
  115. pybmodes/checks.py +445 -0
  116. pybmodes/cli.py +780 -0
  117. pybmodes/coords.py +120 -0
  118. pybmodes/elastodyn/__init__.py +44 -0
  119. pybmodes/elastodyn/params.py +798 -0
  120. pybmodes/elastodyn/validate.py +432 -0
  121. pybmodes/elastodyn/writer.py +97 -0
  122. pybmodes/fem/__init__.py +14 -0
  123. pybmodes/fem/assembly.py +314 -0
  124. pybmodes/fem/boundary.py +165 -0
  125. pybmodes/fem/element.py +394 -0
  126. pybmodes/fem/gauss.py +40 -0
  127. pybmodes/fem/nondim.py +376 -0
  128. pybmodes/fem/normalize.py +141 -0
  129. pybmodes/fem/platform_modes.py +153 -0
  130. pybmodes/fem/solver.py +245 -0
  131. pybmodes/fitting/__init__.py +18 -0
  132. pybmodes/fitting/poly_fit.py +153 -0
  133. pybmodes/io/__init__.py +56 -0
  134. pybmodes/io/_elastodyn/__init__.py +92 -0
  135. pybmodes/io/_elastodyn/adapter.py +527 -0
  136. pybmodes/io/_elastodyn/lex.py +139 -0
  137. pybmodes/io/_elastodyn/parser.py +558 -0
  138. pybmodes/io/_elastodyn/types.py +213 -0
  139. pybmodes/io/_elastodyn/writer.py +233 -0
  140. pybmodes/io/_precomp/__init__.py +42 -0
  141. pybmodes/io/_precomp/arc_resolver.py +430 -0
  142. pybmodes/io/_precomp/decouple.py +257 -0
  143. pybmodes/io/_precomp/laminate.py +214 -0
  144. pybmodes/io/_precomp/profile.py +201 -0
  145. pybmodes/io/_precomp/reduction.py +329 -0
  146. pybmodes/io/_serialize.py +148 -0
  147. pybmodes/io/bmi.py +733 -0
  148. pybmodes/io/elastodyn_reader.py +129 -0
  149. pybmodes/io/errors.py +191 -0
  150. pybmodes/io/geometry.py +135 -0
  151. pybmodes/io/out_parser.py +314 -0
  152. pybmodes/io/sec_props.py +187 -0
  153. pybmodes/io/subdyn_reader.py +612 -0
  154. pybmodes/io/wamit_reader.py +534 -0
  155. pybmodes/io/windio.py +322 -0
  156. pybmodes/io/windio_blade.py +588 -0
  157. pybmodes/io/windio_floating.py +466 -0
  158. pybmodes/mac.py +306 -0
  159. pybmodes/models/__init__.py +20 -0
  160. pybmodes/models/_pipeline.py +260 -0
  161. pybmodes/models/_platform.py +139 -0
  162. pybmodes/models/_shared.py +69 -0
  163. pybmodes/models/blade.py +271 -0
  164. pybmodes/models/result.py +443 -0
  165. pybmodes/models/tower.py +968 -0
  166. pybmodes/mooring/__init__.py +104 -0
  167. pybmodes/mooring/_catenary.py +113 -0
  168. pybmodes/mooring/_moordyn_parser.py +255 -0
  169. pybmodes/mooring/_rotation.py +41 -0
  170. pybmodes/mooring/system.py +608 -0
  171. pybmodes/mooring/types.py +248 -0
  172. pybmodes/options.py +173 -0
  173. pybmodes/plots/__init__.py +54 -0
  174. pybmodes/plots/environmental.py +365 -0
  175. pybmodes/plots/mode_shapes.py +791 -0
  176. pybmodes/plots/style.py +163 -0
  177. pybmodes/py.typed +0 -0
  178. pybmodes/report.py +623 -0
  179. pybmodes/workflows/__init__.py +118 -0
  180. pybmodes/workflows/_base.py +61 -0
  181. pybmodes/workflows/batch.py +400 -0
  182. pybmodes/workflows/campbell.py +195 -0
  183. pybmodes/workflows/examples.py +197 -0
  184. pybmodes/workflows/patch.py +387 -0
  185. pybmodes/workflows/report.py +206 -0
  186. pybmodes/workflows/validate.py +179 -0
  187. pybmodes/workflows/windio.py +552 -0
  188. pybmodes-1.8.0.dist-info/METADATA +369 -0
  189. pybmodes-1.8.0.dist-info/RECORD +193 -0
  190. pybmodes-1.8.0.dist-info/WHEEL +5 -0
  191. pybmodes-1.8.0.dist-info/entry_points.txt +2 -0
  192. pybmodes-1.8.0.dist-info/licenses/LICENSE +202 -0
  193. pybmodes-1.8.0.dist-info/top_level.txt +1 -0
pybmodes/__init__.py ADDED
@@ -0,0 +1,188 @@
1
+ # Copyright 2024-2026 Jae Hoon Seo
2
+ # Marine Structural Mechanics and Integrity Lab (SMI Lab), Inha University
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """pybmodes — Python finite-element library for wind turbine modal analysis.
17
+
18
+ Public API
19
+ ==========
20
+
21
+ The following subpackage entry points are the stable, semver-protected
22
+ 1.x surface (see the *Compatibility policy* section in the README).
23
+ Anything else in the package tree is internal and may change between
24
+ minor releases.
25
+
26
+ from pybmodes.models import RotatingBlade, Tower, ModalResult
27
+ from pybmodes.elastodyn import (
28
+ compute_blade_params,
29
+ compute_tower_params,
30
+ compute_tower_params_report,
31
+ patch_dat,
32
+ validate_dat_coefficients,
33
+ BladeElastoDynParams,
34
+ TowerElastoDynParams,
35
+ ValidationResult,
36
+ CoeffBlockResult, # tower blocks carry fa/ss/torsion
37
+ # participation + rejected_modes
38
+ )
39
+ from pybmodes.fitting import PolyFitResult, fit_mode_shape
40
+ from pybmodes.campbell import (
41
+ CampbellResult, # save / load / to_csv
42
+ campbell_sweep,
43
+ plot_campbell,
44
+ )
45
+ from pybmodes.checks import check_model, ModelWarning
46
+ from pybmodes.mac import (
47
+ mac_matrix,
48
+ compare_modes,
49
+ ModeComparison,
50
+ plot_mac,
51
+ shape_to_vector,
52
+ )
53
+ from pybmodes.report import generate_report
54
+ from pybmodes.mooring import LineType, Point, Line, MooringSystem
55
+ from pybmodes.io import (
56
+ HydroDynReader, WamitReader, WamitData,
57
+ PlatformSupport, TipMassProps,
58
+ read_out, BModeOutParseError, # read_out(..., strict=True)
59
+ )
60
+ from pybmodes.coords import DOF_NAMES, DOF_INDEX # 6-DOF convention
61
+ from pybmodes.plots import (
62
+ apply_style,
63
+ plot_mode_shapes,
64
+ plot_fit_quality,
65
+ bir_mode_shape_plot,
66
+ bir_mode_shape_subplot,
67
+ plot_environmental_spectra, # wind/wave + 1P/3P vs tower
68
+ kaimal_spectrum,
69
+ jonswap_spectrum,
70
+ )
71
+
72
+ # On Tower:
73
+ # Tower.from_bmi(bmi_path)
74
+ # Tower.from_elastodyn(main_dat)
75
+ # Tower.from_elastodyn_with_subdyn(main_dat, subdyn_dat)
76
+ # Tower.from_elastodyn_with_mooring(main_dat, moordyn_dat,
77
+ # hydrodyn_dat=None)
78
+ # Tower.from_geometry(station_grid, outer_diameter,
79
+ # wall_thickness, *, flexible_length,
80
+ # E, rho, nu, outfitting_factor)
81
+ # Tower.from_windio(yaml_path, *, component, thickness_interp,
82
+ # hub_conn, tip_mass, n_nodes)
83
+ # tip_mass: TipMassProps | float (RNA mass kg); n_nodes:
84
+ # refine the FE mesh onto N even stations (issue #35)
85
+ # Tower.from_windio_floating(yaml_path, *, water_depth,
86
+ # hydrodyn_dat, moordyn_dat,
87
+ # elastodyn_dat) # coupled FOWT
88
+
89
+ # On RotatingBlade:
90
+ # RotatingBlade.from_bmi(bmi_path)
91
+ # RotatingBlade.from_elastodyn(main_dat)
92
+ # RotatingBlade.from_windio(yaml_path, *, component, n_span,
93
+ # rot_rpm, elastic)
94
+ # elastic="auto" (default) uses the WindIO *published*
95
+ # distributed elastic properties when present — the full 6×6
96
+ # decoupled at the elastic/shear centre + principal elastic
97
+ # axes (issue #50; pybmodes.io._precomp.decouple), not the
98
+ # raw diagonal — else the PreComp-class layup reduction;
99
+ # "precomp" forces the reduction; "file" requires the
100
+ # published properties.
101
+
102
+ from pybmodes.io.geometry import tubular_section_props
103
+ # WindIO .yaml input needs the optional [windio] extra (PyYAML);
104
+ # the runtime core stays numpy+scipy only — same stance as
105
+ # [plots]/[notebook]. Tower (tubular tower/monopile):
106
+ from pybmodes.io.windio import read_windio_tubular, WindIOTubular
107
+ # Blade (composite layup -> PreComp-class thin-wall reduction):
108
+ from pybmodes.io.windio_blade import (
109
+ read_windio_blade,
110
+ windio_blade_section_props,
111
+ WindIOBlade,
112
+ )
113
+ # Floating substructure (member-Morison hydro + catenary mooring;
114
+ # used by Tower.from_windio_floating, yaml-first + deck-fallback):
115
+ from pybmodes.io.windio_floating import (
116
+ read_windio_floating,
117
+ hydrostatic_restoring,
118
+ added_mass,
119
+ rigid_body_inertia,
120
+ WindIOFloating,
121
+ )
122
+
123
+ ``ModalResult`` ships ``save(.npz)`` / ``load(.npz)`` /
124
+ ``to_json(.json)`` / ``from_json(.json)`` with metadata (pyBmodes
125
+ version, source file, timestamp, git hash) and optional
126
+ ``participation`` + ``fit_residuals`` + ``mode_labels`` fields
127
+ (``mode_labels`` names the floating-platform rigid-body modes —
128
+ surge / sway / heave / roll / pitch / yaw — for a free-free model;
129
+ ``None`` otherwise). ``CampbellResult`` ships ``save(.npz)`` /
130
+ ``load(.npz)`` / ``to_csv(.csv)``.
131
+
132
+ Known limitations of the 1.0 surface:
133
+
134
+ - ``pybmodes.mooring`` is catenary-only quasi-static — no seabed
135
+ friction (``CB > 0``), no sloped seabed, no U-shape lines, no
136
+ time-domain dynamics, no line drag / added mass.
137
+ - ``pybmodes.io.WamitReader`` extracts ``A_inf`` (infinite-frequency
138
+ added mass), ``A_0`` (zero-frequency), and ``C_hst`` (hydrostatic
139
+ restoring); finite-period frequency-dependent ``A(ω)`` / ``B(ω)``
140
+ are skipped.
141
+ - ``Tower.from_elastodyn_with_mooring`` assembles a free-free floating
142
+ BMI for coupled-frequency prediction; ElastoDyn polynomial-
143
+ coefficient generation continues to use the cantilever
144
+ ``Tower.from_elastodyn`` regardless of platform configuration (see
145
+ ``cases/ECOSYSTEM_FINDING.md`` for the source-code citation).
146
+ - ``BMIFile.support.distr_m`` (distributed hydrodynamic added mass
147
+ per unit tower length) is parsed by ``pybmodes.io.bmi.read_bmi``
148
+ but NOT wired into the FEM mass matrix; ``distr_k`` (distributed
149
+ soil stiffness) IS consumed. A ``UserWarning`` fires at parse time
150
+ if a deck specifies non-empty ``distr_m`` so the gap is not
151
+ silent.
152
+
153
+ Internal modules (``pybmodes.fem.*``, the underscore-prefixed
154
+ ``pybmodes.models._pipeline``, and the private sub-package
155
+ ``pybmodes.io._elastodyn``) carry the implementation and should not
156
+ be imported directly by user code; their signatures may change
157
+ between minor releases. The per-format submodules under
158
+ ``pybmodes.io`` (``pybmodes.io.bmi``, ``elastodyn_reader``,
159
+ ``subdyn_reader``, ``wamit_reader``) are reachable directly but the
160
+ public-freeze contract covers only the re-exports listed above.
161
+
162
+ The CLI is exposed via ``pybmodes`` (see ``pybmodes --help``) and is
163
+ declared in ``[project.scripts]``.
164
+ """
165
+
166
+ from importlib.metadata import PackageNotFoundError, version
167
+
168
+ # Numerical-options dataclasses (Phase 1 of the v1.x architecture
169
+ # refactor) — centralised thresholds for the FEM solver, polynomial
170
+ # fit, and pre-solve sanity checker. Defaults preserve every
171
+ # previously-embedded magic number, so importing this module changes
172
+ # no behaviour; the public-API value is that callers can now find
173
+ # every numerical threshold in one place. Future PRs will accept
174
+ # instances on ``Tower.run()`` / ``RotatingBlade.run()`` /
175
+ # ``check_model()`` for per-call override.
176
+ from pybmodes.options import CheckOptions, FitOptions, SolverOptions
177
+
178
+ try:
179
+ __version__ = version("pybmodes")
180
+ except PackageNotFoundError:
181
+ __version__ = "1.8.0-dev"
182
+
183
+ __all__ = [
184
+ "__version__",
185
+ "CheckOptions",
186
+ "FitOptions",
187
+ "SolverOptions",
188
+ ]
@@ -0,0 +1,45 @@
1
+ # Copyright 2024-2026 Jae Hoon Seo
2
+ # Marine Structural Mechanics and Integrity Lab (SMI Lab), Inha University
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Bundled example inputs and reference decks.
17
+
18
+ This sub-package vendors two trees that ship inside the wheel so a
19
+ ``pip install pybmodes`` user can copy them out to a working directory
20
+ without keeping a full git clone of the repository.
21
+
22
+ - ``sample_inputs/`` — pyBmodes-authored, Apache 2.0-licensed ``.bmi`` and
23
+ section-property ``.dat`` files. Four analytical-reference cases
24
+ (uniform blade, tower with top mass, rotating uniform blade,
25
+ pinned-free cable) plus seven reference-wind-turbine sub-cases under
26
+ ``reference_turbines/``. ``verify.py`` runs the four analytical
27
+ cases against closed-form references.
28
+ - ``reference_decks/`` — six pre-patched ElastoDyn decks (three
29
+ land/monopile + three floating) whose polynomial blocks have been
30
+ regenerated from the structural inputs via ``Tower.from_elastodyn``.
31
+
32
+ The trees are intentionally treated as opaque data; the ``.py``
33
+ helpers inside them (``verify.py``, ``reference_turbines/build.py``)
34
+ are intended to be run *after* vendoring out via
35
+ ``pybmodes examples --copy <dir>``, not imported.
36
+
37
+ Users discover the bundles through three paths:
38
+
39
+ - ``pybmodes examples --copy DIR`` — CLI that copies one or both
40
+ trees into a user-supplied directory.
41
+ - Browsing the wheel install directly under
42
+ ``site-packages/pybmodes/_examples/``.
43
+ - For developers working from a source checkout, the GitHub source
44
+ tree under the same path.
45
+ """
@@ -0,0 +1,144 @@
1
+ <!-- markdownlint-disable MD013 -->
2
+ # Floating cases — the correct ElastoDyn polynomial basis is *cantilever*
3
+
4
+ For floating platforms, ElastoDyn polynomial coefficients
5
+ (`TwFAM1Sh`, `TwFAM2Sh`, `TwSSM1Sh`, `TwSSM2Sh`) must be derived
6
+ from a **cantilever** (`hub_conn = 1`) tower model with the RNA
7
+ lumped at the tower top — **NOT** from a platform-coupled floating
8
+ model.
9
+
10
+ ## Why
11
+
12
+ ElastoDyn represents floating tower dynamics as a clamped-base
13
+ cantilever **in the platform-attached frame**, with platform 6-DOF
14
+ motion handled separately as independent generalised coordinates
15
+ (`Sg / Sw / Hv / R / P / Y`). Three independent code-level evidences
16
+ in OpenFAST `modules/elastodyn/src/ElastoDyn.f90` (main branch):
17
+
18
+ 1. The polynomial ansatz in `SHP` evaluates
19
+ `Σ_{i=1..PolyOrd-1} c_i · (h/H)^(i+1)` (lines 2486–2495). The
20
+ lowest power is `Fract²`, so `SHP(0) = SHP'(0) = 0`
21
+ *identically*. A free-free or pinned-pinned mode shape with
22
+ non-zero base slope cannot be represented in this format.
23
+ 2. The base node is hard-coded zero: `p%TwrFASF(:,0,0:1) = 0`,
24
+ `p%TwrSSSF(:,0,0:1) = 0` (lines 5147–5148).
25
+ 3. The internal tower modal eigenproblem (`Coeff` subroutine,
26
+ lines 5141–5267) integrates `MTFA = TwrTpMass + ∫ ρA φ² dh`
27
+ and `KTFA = ∫ EI φ'' φ'' dh + KTFAGrav`. **No** `PlatformMass`,
28
+ **no** `hydro_K`, **no** `mooring_K`, **no** `i_matrix` enter
29
+ this assembly. The only tip-end inertia is the scalar
30
+ `TwrTpMass` (lumped RNA mass).
31
+
32
+ Platform 6-DOF motion enters the absolute tower kinematics via the
33
+ **rigid-body sum** (lines 7485–7540):
34
+
35
+ ```text
36
+ v_T(J) = v_Z + ω_X × rZT(J) + Σ_k φ_k(h_J) · q̇_k
37
+ ```
38
+
39
+ `Sg/Sw/Hv/R/P/Y` and the tower modal coordinates `q_TFA1 / q_TFA2 /
40
+ q_TSS1 / q_TSS2` are **independent** generalised coordinates;
41
+ platform motion does NOT appear as forcing on `q_TFA1`. Feeding
42
+ ElastoDyn polynomials that already encode platform-coupling
43
+ **double-counts** the platform restoring forces because ElastoDyn
44
+ re-derives those effects independently through the platform DOFs.
45
+
46
+ Same BC for land and floating — only the runtime treatment of the
47
+ clamp point differs (locked in Earth for land; rigidly attached to
48
+ the moving platform for floating).
49
+
50
+ ## How
51
+
52
+ Floating-platform polynomial coefficients are generated with the
53
+ **existing** pyBmodes path — `Tower.from_elastodyn(...)` is
54
+ *already* the cantilever path. It clamps at `TowerBsHt` with the RNA
55
+ lumped at the top, ignores any platform / hydro / mooring matrices,
56
+ and produces exactly the basis ElastoDyn assumes. No flag is needed.
57
+
58
+ ```python
59
+ from pybmodes.models import Tower
60
+ from pybmodes.elastodyn import compute_tower_params, patch_dat
61
+ from pybmodes.io.elastodyn_reader import read_elastodyn_main
62
+
63
+ main_path = "Floating_ElastoDyn.dat"
64
+ tower = Tower.from_elastodyn(main_path) # cantilever, RNA at top, no platform
65
+ result = tower.run(n_modes=10)
66
+ params = compute_tower_params(result)
67
+
68
+ # patch_dat rewrites the *tower* .dat file (where the polynomial
69
+ # blocks live), not the main ElastoDyn .dat.
70
+ main = read_elastodyn_main(main_path)
71
+ patch_dat(main_path.replace("ElastoDyn.dat", main.twr_file), params)
72
+ ```
73
+
74
+ No WAMIT files, no HydroDyn parsing, and no MoorDyn parsing are
75
+ required. The cantilever path is correct and self-contained — the
76
+ ElastoDyn `.dat` (plus the tower file it references) carries every
77
+ input needed.
78
+
79
+ ## What about `Tower.from_bmi()` with `hub_conn = 2`?
80
+
81
+ `Tower.from_bmi("OC3Hywind.bmi")` and similar BModes-format decks
82
+ with a populated `PlatformSupport` block solve the **coupled**
83
+ tower-and-platform eigenproblem (free-free root, full 6×6 hydro /
84
+ mooring / inertia matrices). That path:
85
+
86
+ - **Correctly predicts coupled-system frequencies** for validation
87
+ against BModes JJ. pyBmodes matches BModes JJ to ~ 0.0003 % across
88
+ the first nine OC3 Hywind modes (`test_certtest_oc3hywind`). If
89
+ the goal is "what does the floating tower vibrate at when coupled
90
+ to its platform?", this is the right path.
91
+ - **Produces eigenvectors that include platform rigid-body motion**
92
+ — i.e. the modes have non-zero base displacement and non-zero
93
+ base slope, which the ElastoDyn `SHP` ansatz cannot represent.
94
+ Feeding these eigenvectors into a polynomial fit produces
95
+ coefficients ElastoDyn cannot consume without double-counting the
96
+ platform.
97
+
98
+ The two paths answer different questions; both are correct for
99
+ their intended use:
100
+
101
+ | Goal | Use | BC |
102
+ | --- | --- | --- |
103
+ | ElastoDyn polynomial coefficients (any floating deck) | `Tower.from_elastodyn(...)` | `hub_conn = 1`, RNA at top |
104
+ | Coupled-system frequency validation against BModes JJ | `Tower.from_bmi("OC3Hywind.bmi")` | `hub_conn = 2`, full PlatformSupport |
105
+
106
+ ## Configurations included in `reference_decks/`
107
+
108
+ This directory now ships pre-patched ElastoDyn decks for three
109
+ floating configurations alongside the original three fixed-base
110
+ decks:
111
+
112
+ - [`nrel5mw_oc3spar/`](nrel5mw_oc3spar/) — *NREL 5MW* on the OC3
113
+ Hywind spar (Jonkman 2010). Source: OpenFAST `r-test`
114
+ `5MW_OC3Spar_DLL_WTurb_WavesIrr/`.
115
+ - [`nrel5mw_oc4semi/`](nrel5mw_oc4semi/) — *NREL 5MW* on the OC4
116
+ DeepCwind semi-submersible (Robertson et al. 2014). Source:
117
+ OpenFAST `r-test` `5MW_OC4Semi_WSt_WavesWN/`.
118
+ - [`iea15mw_umainesemi/`](iea15mw_umainesemi/) — *IEA-15-240-RWT*
119
+ on the UMaine VolturnUS-S semi (Allen et al. 2020). Source:
120
+ upstream `IEA-15-240-RWT/OpenFAST/IEA-15-240-RWT-UMaineSemi/`.
121
+
122
+ Each deck is built by `scripts/build_reference_decks.py` using the
123
+ cantilever path documented above; the validator passes on all four
124
+ tower coefficient blocks after patching.
125
+
126
+ ## Citations
127
+
128
+ - Jonkman, J., Butterfield, S., Musial, W., & Scott, G. (2009).
129
+ *Definition of a 5-MW Reference Wind Turbine for Offshore System
130
+ Development*. NREL/TP-500-38060.
131
+ - Jonkman, J. (2010). *Definition of the Floating System for Phase
132
+ IV of OC3*. NREL/TP-500-47535.
133
+ - Robertson, A., Jonkman, J., Masciola, M., Song, H., Goupee, A.,
134
+ Coulling, A., & Luan, C. (2014). *Definition of the
135
+ Semisubmersible Floating System for Phase II of OC4*.
136
+ NREL/TP-5000-60601.
137
+ - Allen, C., Viselli, A., Dagher, H., Goupee, A., Gaertner, E.,
138
+ Abbas, N., Hall, M., & Barter, G. (2020). *Definition of the
139
+ UMaine VolturnUS-S Reference Platform Developed for the
140
+ IEA Wind 15-Megawatt Offshore Reference Wind Turbine*.
141
+ NREL/TP-5000-76773.
142
+ - Gaertner, E., Rinker, J., Sethuraman, L., Zahle, F., Anderson, B.,
143
+ Barter, G., et al. (2020). *Definition of the IEA 15-Megawatt
144
+ Offshore Reference Wind Turbine*. NREL/TP-5000-75698.
@@ -0,0 +1,150 @@
1
+ <!-- markdownlint-disable MD013 -->
2
+ # Reference decks with corrected mode-shape polynomial coefficients
3
+
4
+ ## What is this?
5
+
6
+ Copies of OpenFAST ElastoDyn input files with mode-shape polynomial
7
+ coefficients (`TwFAM1Sh`, `TwFAM2Sh`, `TwSSM1Sh`, `TwSSM2Sh`,
8
+ `BldFl1Sh`, `BldFl2Sh`, `BldEdgSh`) regenerated by pyBmodes from the
9
+ structural-property blocks in the same files.
10
+
11
+ Six turbine configurations are included — three fixed-base and three
12
+ floating:
13
+
14
+ - [`nrel5mw_land/`](nrel5mw_land/) — *NREL 5MW Reference Turbine*
15
+ (Jonkman et al. 2009), land-based.
16
+ - [`nrel5mw_oc3monopile/`](nrel5mw_oc3monopile/) — *NREL 5MW* on the
17
+ OC3 Monopile (Jonkman & Musial 2010); rigid base, no soil flexibility,
18
+ SubDyn substructure included for provenance.
19
+ - [`iea34_land/`](iea34_land/) — *IEA-3.4-130-RWT*
20
+ (Bortolotti et al. 2019, IEA Wind Task 37), land-based.
21
+ - [`nrel5mw_oc3spar/`](nrel5mw_oc3spar/) — *NREL 5MW* on the OC3
22
+ Hywind floating spar (Jonkman 2010).
23
+ - [`nrel5mw_oc4semi/`](nrel5mw_oc4semi/) — *NREL 5MW* on the OC4
24
+ DeepCwind semi-submersible (Robertson et al. 2014).
25
+ - [`iea15mw_umainesemi/`](iea15mw_umainesemi/) — *IEA-15-240-RWT* on
26
+ the UMaine VolturnUS-S semi (Allen et al. 2020); the patched
27
+ TwSSM2Sh ends at WARN — the constrained 6th-order polynomial form
28
+ cannot represent this deck's 2nd-SS bending shape below the 1 %
29
+ PASS gate, a property of the IEA-15 tower's section-property
30
+ gradient, not a pyBmodes bug.
31
+
32
+ The floating decks use the same cantilever (`hub_conn = 1`) modal
33
+ basis as the fixed-base decks — see
34
+ [`FLOATING_CASES.md`](FLOATING_CASES.md) for why this is the correct
35
+ basis for ElastoDyn polynomial coefficients on any floating deck.
36
+
37
+ ## Why?
38
+
39
+ The official upstream `.dat` files for these reference turbines contain
40
+ polynomial coefficients that **do not represent the mode shapes
41
+ produced by the same files' structural inputs**. The 2nd-mode tower
42
+ coefficients (`TwFAM2Sh`, `TwSSM2Sh`) are the worst offenders, with
43
+ RMS residuals 170×–2,500× higher than pyBmodes' own fits. See
44
+ [`cases/ECOSYSTEM_FINDING.md`](https://github.com/SMI-Lab-Inha/pyBModes/blob/master/cases/ECOSYSTEM_FINDING.md)
45
+ for the quantified evidence and the [`VALIDATION_SUMMARY.md`](VALIDATION_SUMMARY.md)
46
+ in this directory for per-block before-and-after numbers across all
47
+ three cases.
48
+
49
+ The cause is upstream pipeline drift: the polynomial blocks were
50
+ generated against an earlier revision of each turbine's structural
51
+ model and have not been regenerated as the structural blocks were
52
+ iteratively refined. OpenFAST accepts any coefficient set whose
53
+ polynomial evaluates to 1 at the tip — there is no warning when the
54
+ polynomials describe a different mode shape than the structural model
55
+ in the same deck. This directory provides drop-in replacements where
56
+ the polynomial block and the structural block describe the same modes.
57
+
58
+ ## Provenance
59
+
60
+ | Source | Repository | Commit | Date |
61
+ | --- | --- | --- | --- |
62
+ | NREL 5MW (land + OC3 monopile) | OpenFAST `r-test` | `dd5feaaa` | 2026-03-12 |
63
+ | IEA-3.4-130-RWT | IEA Wind Task 37 RWT repo | `824f8d6` | 2025-09-26 |
64
+ | Regenerated with | pyBmodes | `0753ec2` | (current) |
65
+
66
+ **Method**: pyBmodes solves the modal eigenvalue problem on the deck's
67
+ structural inputs (15-DOF Bernoulli-Euler beam element, vectorised
68
+ einsum core), then fits constrained 6th-order polynomials with the
69
+ clamped-base + unit-tip constraints
70
+ ($\phi(0)=\phi'(0)=0$, $\phi(1)=1$). For tower modes, the rigid-body
71
+ root component is removed before fitting (the **Improved Direct
72
+ Method** described in BModes' Excel mode-shape worksheet,
73
+ `external/BModes/docs/ModeShapePolyFitting.xls`). After regeneration
74
+ every block is pyBmodes' best constrained fit and no block FAILs;
75
+ all blocks reach PASS except one known WARN
76
+ (`iea15mw_umainesemi/TwSSM2Sh` at 1.6 % RMS, an unavoidable
77
+ representation limit of the constrained 6th-order polynomial form
78
+ for that tower's section-property gradient — see
79
+ [`FLOATING_CASES.md`](FLOATING_CASES.md) for the per-deck
80
+ explanation). See [`VALIDATION_SUMMARY.md`](VALIDATION_SUMMARY.md)
81
+ for the per-block before/after numbers.
82
+
83
+ ## How to use
84
+
85
+ Drop the patched `.dat` files into your OpenFAST case directory,
86
+ replacing the originals. Each case directory is self-contained — the
87
+ ElastoDyn main file references only its sibling `_Tower.dat` and
88
+ `_Blade.dat`, no external `..` traversal — so you can copy the whole
89
+ directory or pick individual files.
90
+
91
+ The structural-property blocks (mass density, EI, drag coefficients,
92
+ geometric data) are **identical to the upstream files**; only the
93
+ polynomial-coefficient lines are rewritten.
94
+
95
+ ## How to regenerate
96
+
97
+ The reference decks here are produced by
98
+ [`scripts/build_reference_decks.py`](https://github.com/SMI-Lab-Inha/pyBModes/blob/master/scripts/build_reference_decks.py),
99
+ which copies the upstream sources, runs `pybmodes patch`, and re-runs
100
+ the validator. To regenerate from scratch:
101
+
102
+ ```bash
103
+ python scripts/build_reference_decks.py
104
+ ```
105
+
106
+ To patch your own ElastoDyn deck:
107
+
108
+ ```bash
109
+ pip install pybmodes
110
+ pybmodes validate path/to/your_ElastoDyn.dat # check consistency
111
+ pybmodes patch path/to/your_ElastoDyn.dat --backup # regenerate
112
+ ```
113
+
114
+ Or programmatically:
115
+
116
+ ```python
117
+ from pybmodes.elastodyn import compute_tower_params, compute_blade_params, patch_dat
118
+ from pybmodes.models import Tower, RotatingBlade
119
+
120
+ tower = Tower.from_elastodyn("path/to/MyTurbine_ElastoDyn.dat")
121
+ patch_dat("path/to/MyTurbine_Tower.dat", compute_tower_params(tower.run(n_modes=10)))
122
+
123
+ blade = RotatingBlade.from_elastodyn("path/to/MyTurbine_ElastoDyn.dat")
124
+ patch_dat("path/to/MyTurbine_Blade.dat", compute_blade_params(blade.run(n_modes=10)))
125
+ ```
126
+
127
+ ## Floating configurations
128
+
129
+ This directory ships three floating cases (OC3 Hywind spar, OC4
130
+ DeepCwind semi, UMaine VolturnUS-S semi) generated via the same
131
+ `Tower.from_elastodyn(...)` path as the fixed-base cases. ElastoDyn
132
+ represents floating tower dynamics as a clamped-base cantilever in
133
+ the platform-attached frame, with platform 6-DOF motion handled
134
+ separately as independent generalised coordinates — so the correct
135
+ modal basis for ElastoDyn polynomials is exactly the cantilever path
136
+ this script already uses, regardless of land or floating. See
137
+ [`FLOATING_CASES.md`](FLOATING_CASES.md) for the ElastoDyn-source-code
138
+ justification and the contrast with `Tower.from_bmi()` (which solves
139
+ the coupled tower + platform eigenproblem and is correct for
140
+ frequency validation against BModes JJ but **not** for ElastoDyn
141
+ polynomial generation — its eigenvectors include platform rigid-body
142
+ motion that the ElastoDyn `SHP` ansatz cannot represent).
143
+
144
+ ## License
145
+
146
+ The original upstream `.dat` files retain their respective licenses
147
+ (see the `r-test` and IEA Wind Task 37 repositories). pyBmodes' patched
148
+ versions are released under the same Apache 2.0 licence as pyBmodes itself —
149
+ the structural content is unchanged from the upstream files; only the
150
+ polynomial-coefficient lines were rewritten.
@@ -0,0 +1,63 @@
1
+ <!-- markdownlint-disable MD013 -->
2
+ # Reference-deck coefficient validation summary
3
+
4
+ Per-block RMS residual of the polynomial coefficients shipped in each upstream deck (Before) and after pyBmodes regenerated them from the structural inputs in the same deck (After). The ratio column is the upstream `file_rms / pybmodes_rms` — values >> 1 indicate the upstream polynomial does not represent the mode shape produced by the deck's structural inputs.
5
+
6
+ | Case | Block | Before RMS | After RMS | Ratio before | Status |
7
+ | --- | --- | ---: | ---: | ---: | :---: |
8
+ | nrel5mw_land | TwFAM1Sh | 0.0081 | 0.0000 | 313× | PASS |
9
+ | nrel5mw_land | TwFAM2Sh | 5.0783 | 0.0024 | 2101× | PASS |
10
+ | nrel5mw_land | TwSSM1Sh | 0.0075 | 0.0000 | 293× | PASS |
11
+ | nrel5mw_land | TwSSM2Sh | 5.9009 | 0.0023 | 2529× | PASS |
12
+ | nrel5mw_land | BldFl1Sh | 0.0022 | 0.0008 | 2.82× | PASS |
13
+ | nrel5mw_land | BldFl2Sh | 0.0088 | 0.0035 | 2.48× | PASS |
14
+ | nrel5mw_land | BldEdgSh | 0.0006 | 0.0002 | 3.17× | PASS |
15
+ | nrel5mw_oc3monopile | TwFAM1Sh | 0.0037 | 0.0000 | 140× | PASS |
16
+ | nrel5mw_oc3monopile | TwFAM2Sh | 5.7805 | 0.0032 | 1813× | PASS |
17
+ | nrel5mw_oc3monopile | TwSSM1Sh | 0.0045 | 0.0000 | 173× | PASS |
18
+ | nrel5mw_oc3monopile | TwSSM2Sh | 7.3266 | 0.0033 | 2220× | PASS |
19
+ | nrel5mw_oc3monopile | BldFl1Sh | 0.0022 | 0.0008 | 2.82× | PASS |
20
+ | nrel5mw_oc3monopile | BldFl2Sh | 0.0088 | 0.0035 | 2.48× | PASS |
21
+ | nrel5mw_oc3monopile | BldEdgSh | 0.0006 | 0.0002 | 3.17× | PASS |
22
+ | iea34_land | TwFAM1Sh | 0.0098 | 0.0002 | 56.9× | PASS |
23
+ | iea34_land | TwFAM2Sh | 0.7230 | 0.0042 | 172× | PASS |
24
+ | iea34_land | TwSSM1Sh | 0.0110 | 0.0002 | 64.0× | PASS |
25
+ | iea34_land | TwSSM2Sh | 1.5494 | 0.0041 | 380× | PASS |
26
+ | iea34_land | BldFl1Sh | 0.0106 | 0.0014 | 7.61× | PASS |
27
+ | iea34_land | BldFl2Sh | 0.0051 | 0.0047 | 1.09× | PASS |
28
+ | iea34_land | BldEdgSh | 0.0065 | 0.0014 | 4.62× | PASS |
29
+ | nrel5mw_oc3spar | TwFAM1Sh | 0.0111 | 0.0000 | 350× | PASS |
30
+ | nrel5mw_oc3spar | TwFAM2Sh | 10.6264 | 0.0024 | 4476× | PASS |
31
+ | nrel5mw_oc3spar | TwSSM1Sh | 0.0165 | 0.0000 | 524× | PASS |
32
+ | nrel5mw_oc3spar | TwSSM2Sh | 14.1050 | 0.0027 | 5311× | PASS |
33
+ | nrel5mw_oc3spar | BldFl1Sh | 0.0022 | 0.0008 | 2.82× | PASS |
34
+ | nrel5mw_oc3spar | BldFl2Sh | 0.0088 | 0.0035 | 2.48× | PASS |
35
+ | nrel5mw_oc3spar | BldEdgSh | 0.0006 | 0.0002 | 3.17× | PASS |
36
+ | nrel5mw_oc4semi | TwFAM1Sh | 0.0034 | 0.0000 | 106× | PASS |
37
+ | nrel5mw_oc4semi | TwFAM2Sh | 8.2805 | 0.0024 | 3488× | PASS |
38
+ | nrel5mw_oc4semi | TwSSM1Sh | 0.0048 | 0.0000 | 152× | PASS |
39
+ | nrel5mw_oc4semi | TwSSM2Sh | 9.0396 | 0.0027 | 3404× | PASS |
40
+ | nrel5mw_oc4semi | BldFl1Sh | 0.0033 | 0.0008 | 3.94× | PASS |
41
+ | nrel5mw_oc4semi | BldFl2Sh | 0.0093 | 0.0036 | 2.59× | PASS |
42
+ | nrel5mw_oc4semi | BldEdgSh | 0.0004 | 0.0002 | 2.29× | PASS |
43
+ | iea15mw_umainesemi | TwFAM1Sh | 0.0078 | 0.0001 | 95.8× | PASS |
44
+ | iea15mw_umainesemi | TwFAM2Sh | 0.7922 | 0.0013 | 619× | PASS |
45
+ | iea15mw_umainesemi | TwSSM1Sh | 0.0088 | 0.0001 | 118× | PASS |
46
+ | iea15mw_umainesemi | TwSSM2Sh | 102.4075 | 0.0163 | 6276× | WARN |
47
+ | iea15mw_umainesemi | BldFl1Sh | 0.0048 | 0.0002 | 24.7× | PASS |
48
+ | iea15mw_umainesemi | BldFl2Sh | 0.0021 | 0.0018 | 1.21× | PASS |
49
+ | iea15mw_umainesemi | BldEdgSh | 0.0028 | 0.0008 | 3.73× | PASS |
50
+
51
+ ## Pattern
52
+
53
+ - **2nd-mode tower coefficients** (`TwFAM2Sh`, `TwSSM2Sh`) show the largest inconsistency on every upstream deck: ratios from ~170× (IEA-3.4) to ~2,500× (NREL 5MW). The shipped polynomials do not represent the 2nd bending mode of the structural inputs by any reasonable metric.
54
+ - **1st-mode tower coefficients** (`TwFAM1Sh`, `TwSSM1Sh`) and blade coefficients (`BldFl1Sh`, `BldFl2Sh`, `BldEdgSh`) show a smaller but non-zero inconsistency (typical ratio ~ 2–300×). Their absolute file RMS values still classify as PASS under the 1 % per-block gate, but they are still drift artefacts from the same generation pipeline.
55
+ - **After patching every block is pyBmodes' best constrained fit and no block FAILs; most blocks reach PASS, one known WARN (`iea15mw_umainesemi / TwSSM2Sh` at 1.6 % RMS) reflects an ElastoDyn basis representation limit for that specific tower's section-property gradient, not a pyBmodes bug.** The After-RMS column matches the pyBmodes-RMS column from the Before report; the polynomials in the patched files are exactly pyBmodes' fits, so the file polynomial reproduces the pyBmodes mode shape modulo the writer's text-precision (~7 sig figs).
56
+
57
+ ## How to reproduce
58
+
59
+ ```bash
60
+ python scripts/build_reference_decks.py
61
+ ```
62
+
63
+ The script copies the upstream sources, runs `pybmodes patch`, and re-runs the validator. See `before_patch.txt` and `validation_report.txt` in each case directory for the raw CLI output.