From bd623fd46b5ab4f86d8a6f22120c7a6cd414d5c1 Mon Sep 17 00:00:00 2001 From: ptrbortolotti Date: Thu, 5 Mar 2026 21:34:15 -0700 Subject: [PATCH 01/29] drop support for python 3.9 and 3.10 due to wombat limits --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 918fe9b23..f437d39bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "wisdem" version = "4.1.0" description = "Wind-Plant Integrated System Design & Engineering Model" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.11" license = {text = "Apache-2.0"} keywords = ["wind", "turbine", "mdao", "design", "optimization"] authors = [ @@ -33,8 +33,6 @@ classifiers = [ # Optional # that you indicate you support Python 3. These classifiers are *not* # checked by "pip install". See instead "python_requires" below. "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", From 396ed8256a33b756599e3d160cb795db2cc8d36b Mon Sep 17 00:00:00 2001 From: dzalkind Date: Wed, 18 Mar 2026 14:32:50 -0600 Subject: [PATCH 02/29] Remove potentially duplicate promotion --- wisdem/floatingse/floating_frame.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/wisdem/floatingse/floating_frame.py b/wisdem/floatingse/floating_frame.py index 0745b7fa1..eed8113f9 100644 --- a/wisdem/floatingse/floating_frame.py +++ b/wisdem/floatingse/floating_frame.py @@ -698,10 +698,7 @@ def setup(self): n_full = get_nfull(opt["floating"]["members"]["n_height"][k], nref=2) shape = opt["floating"]["members"]["outer_shape"][k] kname = opt['floating']['members']['name'][k] - if shape == "circular": - mem_prom = mem_prom_base + [("cd_usr", f"memload{k}.cd_usr"), ("ca_usr", f"memload{k}.ca_usr")] - elif shape == "rectangular": - mem_prom = mem_prom_base + [("cd_usr", f"memload{k}.cd_usr"), ("cdy_usr", f"memload{k}.cdy_usr"), ("ca_usr", f"memload{k}.ca_usr"), ("cay_usr", f"memload{k}.cay_usr")] + self.add_subsystem( f"memload{k}", MemberLoads( @@ -711,7 +708,7 @@ def setup(self): memmax=True, member_shape=shape, ), - promotes=mem_prom + U_prom + [("joint1", f"member{k}_{kname}:joint1"), ("joint2", f"member{k}_{kname}:joint2")], + promotes= mem_prom_base + U_prom + [("joint1", f"member{k}_{kname}:joint1"), ("joint2", f"member{k}_{kname}:joint2")], ) self.add_subsystem("loadsys", PlatformLoads(options=opt), promotes=["*"]) From 7815a623c5021494f3e502a4701110db6546783e Mon Sep 17 00:00:00 2001 From: dzalkind Date: Fri, 27 Mar 2026 16:14:37 -0600 Subject: [PATCH 03/29] Enable relative joints for cylindrical coordinates --- wisdem/glue_code/gc_WT_DataStruc.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/wisdem/glue_code/gc_WT_DataStruc.py b/wisdem/glue_code/gc_WT_DataStruc.py index 4e97a3c31..a27bd8022 100644 --- a/wisdem/glue_code/gc_WT_DataStruc.py +++ b/wisdem/glue_code/gc_WT_DataStruc.py @@ -2282,12 +2282,6 @@ def compute(self, inputs, outputs): locations = inputs["location"] joints_xyz = NULL * np.ones(outputs["joints_xyz"].shape) - # Handle cylindrical coordinate joints - icyl = floating_init_options["joints"]["cylindrical"] - locations_xyz = locations.copy() - locations_xyz[icyl, 0] = locations[icyl, 0] * np.cos(np.deg2rad(locations[icyl, 1])) - locations_xyz[icyl, 1] = locations[icyl, 0] * np.sin(np.deg2rad(locations[icyl, 1])) - # Handle relative joints joint_names = floating_init_options["joints"]["name"] for i_joint in range(floating_init_options["joints"]["n_joints"]): @@ -2298,11 +2292,18 @@ def compute(self, inputs, outputs): f"The relative joint {joint_names[i_joint]} is not relative to an existing joint. Relative joint provided: {rel_joint}" ) - rel_joint_location = locations_xyz[name2idx[rel_joint]] + rel_joint_location = locations[name2idx[rel_joint]] relative_dimensions = np.array( floating_init_options["joints"]["relative_dims"][i_joint] ) # These joints are relative - locations_xyz[i_joint][relative_dimensions] += rel_joint_location[relative_dimensions] + locations[i_joint][relative_dimensions] += rel_joint_location[relative_dimensions] + + # Handle cylindrical coordinate joints + icyl = floating_init_options["joints"]["cylindrical"] + locations_xyz = locations.copy() + locations_xyz[icyl, 0] = locations[icyl, 0] * np.cos(np.deg2rad(locations[icyl, 1])) + locations_xyz[icyl, 1] = locations[icyl, 0] * np.sin(np.deg2rad(locations[icyl, 1])) + joints_xyz[:n_joints, :] = locations_xyz.copy() From 5d60a3cf19c5d27e8196fbca082882292cfa7aa6 Mon Sep 17 00:00:00 2001 From: dzalkind Date: Mon, 30 Mar 2026 12:53:28 -0600 Subject: [PATCH 04/29] Add test for relative joints --- .../test_floatingse/test_relative_joints.py | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 wisdem/test/test_floatingse/test_relative_joints.py diff --git a/wisdem/test/test_floatingse/test_relative_joints.py b/wisdem/test/test_floatingse/test_relative_joints.py new file mode 100644 index 000000000..297da1a09 --- /dev/null +++ b/wisdem/test/test_floatingse/test_relative_joints.py @@ -0,0 +1,165 @@ +""" +Unit tests for AggregateJoints with relative joints in cylindrical coordinates. + +Tests that mooring anchor joints defined relative to column keel joints +are resolved to the correct Cartesian (XYZ) positions. +""" + +import unittest + +import numpy as np +import openmdao.api as om + +from wisdem.glue_code.gc_WT_DataStruc import AggregateJoints + + +def make_floating_init_options(joint_names, cylindrical, relative, relative_dims): + """Build a minimal floating_init_options dict for AggregateJoints with no members.""" + n_joints = len(joint_names) + name2idx = {name: i for i, name in enumerate(joint_names)} + return { + "joints": { + "n_joints": n_joints, + "name": joint_names, + "cylindrical": cylindrical, + "relative": relative, + "relative_dims": relative_dims, + "name2idx": name2idx, + }, + "members": { + "n_members": 0, + "name": [], + "joint1": [], + "joint2": [], + "n_axial_joints": [], + "no_intersect": [], + }, + } + + +class TestRelativeJointsCylindrical(unittest.TestCase): + """Test AggregateJoints resolves relative cylindrical joints correctly.""" + + @staticmethod + def _run_aggregate_joints(floating_init_options, locations_input): + prob = om.Problem() + prob.model.add_subsystem( + "alljoints", + AggregateJoints(floating_init_options=floating_init_options), + promotes=["*"], + ) + prob.setup() + prob["location"] = locations_input + prob.run_model() + return prob["joints_xyz"] + + def test_single_relative_cylindrical_joint(self): + """ + anchor1 is relative to col_keel in the radial dimension only. + After relative offset: anchor1 r = r_anchor_offset + r_col + After cyl->XYZ: x = r_total * cos(theta), y = r_total * sin(theta) + """ + r_col = 51.75 + theta_col = 180.0 + z_keel = -20.0 + r_anchor_offset = 786.05 + z_anchor = -200.0 + + joint_names = ["col_keel", "anchor1"] + locations = np.array([ + [r_col, theta_col, z_keel], # col_keel (cylindrical) + [r_anchor_offset, theta_col, z_anchor], # anchor1 (cylindrical, relative r) + ]) + cylindrical = [True, True] + relative = ["origin", "col_keel"] + relative_dims = [[False, False, False], [True, False, False]] + + opts = make_floating_init_options(joint_names, cylindrical, relative, relative_dims) + joints_xyz = self._run_aggregate_joints(opts, locations.copy()) + + # col_keel XYZ + np.testing.assert_allclose(joints_xyz[0, 0], r_col * np.cos(np.deg2rad(theta_col)), atol=1e-10) + np.testing.assert_allclose(joints_xyz[0, 1], r_col * np.sin(np.deg2rad(theta_col)), atol=1e-10) + np.testing.assert_allclose(joints_xyz[0, 2], z_keel, atol=1e-10) + + # anchor1 XYZ — relative r resolved before cylindrical conversion + r_total = r_anchor_offset + r_col + np.testing.assert_allclose(joints_xyz[1, 0], r_total * np.cos(np.deg2rad(theta_col)), atol=1e-10) + np.testing.assert_allclose(joints_xyz[1, 1], r_total * np.sin(np.deg2rad(theta_col)), atol=1e-10) + np.testing.assert_allclose(joints_xyz[1, 2], z_anchor, atol=1e-10) + + def test_three_mooring_anchors_iea15mw(self): + """ + Reproduce the IEA-15-240-RWT VolturnUS-S mooring anchor geometry. + Column keels at 120-deg spacing; anchors at relative radial offset from each keel. + """ + r_col = 51.75 + z_keel = -20.0 + r_anchor_offset = 786.05 + z_anchor = -200.0 + thetas = [180.0, 60.0, -60.0] + + joint_names = ["col1_keel", "col2_keel", "col3_keel", "anchor1", "anchor2", "anchor3"] + locations = np.array([ + [r_col, thetas[0], z_keel], + [r_col, thetas[1], z_keel], + [r_col, thetas[2], z_keel], + [r_anchor_offset, thetas[0], z_anchor], + [r_anchor_offset, thetas[1], z_anchor], + [r_anchor_offset, thetas[2], z_anchor], + ]) + cylindrical = [True] * 6 + relative = ["origin", "origin", "origin", "col1_keel", "col2_keel", "col3_keel"] + relative_dims = [[False, False, False]] * 3 + [[True, False, False]] * 3 + + opts = make_floating_init_options(joint_names, cylindrical, relative, relative_dims) + joints_xyz = self._run_aggregate_joints(opts, locations.copy()) + + r_total = r_anchor_offset + r_col + for i, (theta, name) in enumerate(zip(thetas, ["anchor1", "anchor2", "anchor3"])): + idx = i + 3 # anchors start at index 3 + np.testing.assert_allclose(joints_xyz[idx, 0], r_total * np.cos(np.deg2rad(theta)), atol=1e-10, + err_msg=f"{name} x mismatch") + np.testing.assert_allclose(joints_xyz[idx, 1], r_total * np.sin(np.deg2rad(theta)), atol=1e-10, + err_msg=f"{name} y mismatch") + np.testing.assert_allclose(joints_xyz[idx, 2], z_anchor, atol=1e-10, + err_msg=f"{name} z mismatch") + + def test_cartesian_relative_to_cylindrical_parent(self): + """ + heave_plate_base is a Cartesian joint positioned below col_keel. + + The relative offset uses the raw location of col_keel (before cyl->XYZ), + so only the z dimension is inherited: heave_plate_base.z = z_offset + z_keel. + x and y are unchanged (absolute Cartesian). + """ + r_col = 51.75 + theta_col = 180.0 + z_keel = -20.0 + z_offset = -0.25 + + joint_names = ["col_keel", "heave_plate_base"] + locations = np.array([ + [r_col, theta_col, z_keel], # col_keel (cylindrical) + [0.0, 0.0, z_offset], # heave_plate_base (Cartesian, relative z) + ]) + cylindrical = [True, False] + relative = ["origin", "col_keel"] + relative_dims = [[False, False, False], [False, False, True]] + + opts = make_floating_init_options(joint_names, cylindrical, relative, relative_dims) + joints_xyz = self._run_aggregate_joints(opts, locations.copy()) + + # col_keel XYZ (from cylindrical) + np.testing.assert_allclose(joints_xyz[0, 0], r_col * np.cos(np.deg2rad(theta_col)), atol=1e-10) + np.testing.assert_allclose(joints_xyz[0, 1], r_col * np.sin(np.deg2rad(theta_col)), atol=1e-10) + np.testing.assert_allclose(joints_xyz[0, 2], z_keel, atol=1e-10) + + # heave_plate_base: x, y unchanged; z = z_offset + z_keel + np.testing.assert_allclose(joints_xyz[1, 0], 0.0, atol=1e-10) + np.testing.assert_allclose(joints_xyz[1, 1], 0.0, atol=1e-10) + np.testing.assert_allclose(joints_xyz[1, 2], z_offset + z_keel, atol=1e-10) + + +if __name__ == "__main__": + unittest.main() From 4b323c8923ee605de43df0d52ab832402641790f Mon Sep 17 00:00:00 2001 From: dzalkind Date: Mon, 30 Mar 2026 12:55:20 -0600 Subject: [PATCH 05/29] Tidy comments in test --- wisdem/test/test_floatingse/test_relative_joints.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wisdem/test/test_floatingse/test_relative_joints.py b/wisdem/test/test_floatingse/test_relative_joints.py index 297da1a09..f0f282d30 100644 --- a/wisdem/test/test_floatingse/test_relative_joints.py +++ b/wisdem/test/test_floatingse/test_relative_joints.py @@ -1,8 +1,9 @@ """ -Unit tests for AggregateJoints with relative joints in cylindrical coordinates. +Unit tests for AggregateJoints with relative joints in rectangular/cylindrical coordinates. Tests that mooring anchor joints defined relative to column keel joints are resolved to the correct Cartesian (XYZ) positions. +Test that a heave plate relative to a column keel is resolved correctly in the z dimension but not x/y. """ import unittest @@ -37,8 +38,8 @@ def make_floating_init_options(joint_names, cylindrical, relative, relative_dims } -class TestRelativeJointsCylindrical(unittest.TestCase): - """Test AggregateJoints resolves relative cylindrical joints correctly.""" +class TestRelativeJoints(unittest.TestCase): + """Test AggregateJoints resolves relative joints correctly in both cylindrical and Cartesian coordinates.""" @staticmethod def _run_aggregate_joints(floating_init_options, locations_input): From 3ee40d303b00c88b7f94fed514c6e7b7951edbf3 Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Mon, 30 Mar 2026 15:35:27 -0600 Subject: [PATCH 06/29] fix tests with more stringest omdao checking --- wisdem/fixed_bottomse/monopile.py | 3 ++- wisdem/floatingse/floating.py | 2 ++ wisdem/test/test_rotorse/test_rotor_power.py | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/wisdem/fixed_bottomse/monopile.py b/wisdem/fixed_bottomse/monopile.py index 42dd84aed..02e2322c7 100644 --- a/wisdem/fixed_bottomse/monopile.py +++ b/wisdem/fixed_bottomse/monopile.py @@ -944,7 +944,7 @@ def setup(self): "tower_torsion_modes", "f1", "f2", - "water_depth" + "water_depth", ], ) @@ -1031,3 +1031,4 @@ def setup(self): self.connect("soil.z_k", "monopile.z_soil") self.connect("soil.k", "monopile.k_soil") + self.set_input_defaults("rho_water", 1025.0, units="kg/m**3") diff --git a/wisdem/floatingse/floating.py b/wisdem/floatingse/floating.py index a87d348e2..bb0670f7d 100644 --- a/wisdem/floatingse/floating.py +++ b/wisdem/floatingse/floating.py @@ -146,3 +146,5 @@ def setup(self): for var in ["z_global", "s_full", "s_all"]: self.connect(f"member{k}_{kname}.{var}", f"memload{k}.{var}") + + self.set_input_defaults("rho_water", 1025.0, units="kg/m**3") diff --git a/wisdem/test/test_rotorse/test_rotor_power.py b/wisdem/test/test_rotorse/test_rotor_power.py index 117a5d372..b04602141 100644 --- a/wisdem/test/test_rotorse/test_rotor_power.py +++ b/wisdem/test/test_rotorse/test_rotor_power.py @@ -15,6 +15,8 @@ def fillprob(prob, n_pc, n_span): prob.setup() for k in NPZFILE.files: + if k in ["generator_efficiency","lss_rpm"]: + continue prob[k] = NPZFILE[k] prob.set_val("v_min", 4.0, units="m/s") From f6a10567e429a6a6ecff5745572ce89819c6e14a Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Mon, 30 Mar 2026 19:49:48 -0600 Subject: [PATCH 07/29] pinning moorpy for now --- environment.yml | 2 +- environment_dev.yml | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/environment.yml b/environment.yml index c3a83366a..861be5e54 100644 --- a/environment.yml +++ b/environment.yml @@ -14,7 +14,7 @@ dependencies: - jsonmerge - jsonschema - matplotlib - - moorpy>=1.2 + - moorpy==1.2.1 - nlopt - numpy - openmdao diff --git a/environment_dev.yml b/environment_dev.yml index 73f4c8838..fa3d16baf 100644 --- a/environment_dev.yml +++ b/environment_dev.yml @@ -13,7 +13,7 @@ dependencies: - jsonmerge - jsonschema - matplotlib - - moorpy>=1.2 + - moorpy==1.2.1 - ninja - nlopt - numpy diff --git a/pyproject.toml b/pyproject.toml index f437d39bf..1d45eee78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ classifiers = [ # Optional dependencies = [ "jsonmerge", "jsonschema", - "moorpy>=1.2", + "moorpy==1.2.1", "numpy", "openmdao", "openpyxl", From 20a9dd72c2f00c4fd671f6ea2b35745c6e625d06 Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Tue, 31 Mar 2026 17:10:07 -0600 Subject: [PATCH 08/29] catch up to previously corrected bug that accounts for cone in blade cm and does not just use hub_cm --- wisdem/postprocessing/wisdem_get.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/wisdem/postprocessing/wisdem_get.py b/wisdem/postprocessing/wisdem_get.py index 6954ece18..fb1e4f814 100644 --- a/wisdem/postprocessing/wisdem_get.py +++ b/wisdem/postprocessing/wisdem_get.py @@ -232,18 +232,21 @@ def get_nacelle_mass(prob): # 'MoI_cm_xx', 'MoI_cm_yy', 'MoI_cm_zz', 'MoI_cm_xy', 'MoI_cm_xz', 'MoI_cm_yz', # 'MoI_TT_xx', 'MoI_TT_yy', 'MoI_TT_zz', 'MoI_TT_xy', 'MoI_TT_xz', 'MoI_TT_yz'] nacDF = prob.model.wt.wt_rna.drivese.nac._mass_table - hub_cm = prob["drivese.hub_system_cm"][0] + hub_cm_in = prob["drivese.hub_system_cm"][0] L_drive = prob["drivese.L_drive"][0] tilt = prob.get_val('drivetrain.uptilt', 'rad')[0] shaft0 = prob["drivese.shaft_start"] - Cup = -1.0 - hub_cm = R = shaft0 + (L_drive + hub_cm) * np.array([Cup * np.cos(tilt), 0.0, np.sin(tilt)]) + Cup = -1.0 + cm_array = np.array([Cup * np.cos(tilt), 0.0, np.sin(tilt)]) + hub_cm = R = shaft0 + (L_drive + hub_cm_in) * cm_array hub_mass = prob['drivese.hub_system_mass'] hub_I = prob["drivese.hub_system_I"] hub_I_TT = util.rotateI(hub_I, -Cup * tilt, axis="y") hub_I_TT = util.unassembleI( util.assembleI(hub_I_TT) + hub_mass * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) ) + blades_cm_in = prob["drivese.blades_cm"][0] blades_mass = prob['drivese.blades_mass'] + blades_cm = R = shaft0 + (L_drive + hub_cm_in + blades_cm_in) * cm_array blades_I = prob["drivese.blades_I"] blades_I_TT = util.rotateI(blades_I, -Cup * tilt, axis="y") blades_I_TT = util.unassembleI( util.assembleI(blades_I_TT) + @@ -256,6 +259,7 @@ def get_nacelle_mass(prob): nacDF.loc['Blades'] = np.r_[blades_mass, hub_cm, blades_I, blades_I_TT].tolist() nacDF.loc['Hub_System'] = np.r_[hub_mass, hub_cm, hub_I, hub_I_TT].tolist() nacDF.loc['RNA'] = np.r_[rna_mass, rna_cm, rna_I, rna_I_TT].tolist() + return nacDF From 492baa5bdb825b2136b1d4dd6874d281395d5774 Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Tue, 31 Mar 2026 17:10:32 -0600 Subject: [PATCH 09/29] delete unit entries for unit-less quantities --- wisdem/inputs/modeling_schema.yaml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/wisdem/inputs/modeling_schema.yaml b/wisdem/inputs/modeling_schema.yaml index 77f4036ea..896b314ef 100644 --- a/wisdem/inputs/modeling_schema.yaml +++ b/wisdem/inputs/modeling_schema.yaml @@ -500,21 +500,18 @@ properties: n_ctv: type: number description: Number of crew transfer vessels (offshore) or onsite trucks (land-based) that should be made available to the wind farm. - units: unitless minimum: 1 maximum: 20 default: 3 n_hlv: type: number description: Number of heavy lift vessels (fixed-bottom offshore) or crawler cranes (land-based) that should be made available to the wind farm. - units: unitless minimum: 1 maximum: 10 default: 1 n_tugboat: type: number description: Number of tugboat groups that should be available to the port to tow floating turbines to port and back. - units: unitless minimum: 1 maximum: 10 default: 2 @@ -535,14 +532,12 @@ properties: n_port_crews: type: number description: Number of port-side crews available to work on simultaneous repairs for any at-port turbine. - units: unitless minimum: 1 maximum: 100 default: 2 max_port_operations: type: number description: Number of turbines that can be at port at once. - units: unitless minimum: 1 maximum: 100 default: 2 @@ -556,27 +551,22 @@ properties: maintenance_start: type: string description: Starting date, in MM/DD format; year will be inserted automatically based on input to `years`. - units: unitless default: "none" non_operational_start: type: string description: Starting date, in MM/DD format, for an annual period where the site is inaccessible. - units: string default: "none" non_operational_end: type: string description: Ending date, in MM/DD format, for an annual period where the site is inaccessible. - units: unitless default: "none" reduced_speed_start: type: string description: Starting date, in MM/DD format, for an annual period where traveling speed is reduced. - units: unitless default: "none" reduced_speed_end: type: string description: Ending date, in MM/DD format, for an annual period where traveling speed is reduced. - units: unitless default: "none" reduced_speed: type: number @@ -588,7 +578,6 @@ properties: random_seed: type: number description: Random seed for the internal random generator. - units: "none" minimum: 1 maximum: 4294967295 default: 42 From 1d43135a2650f51a3a850d58249a2c84b6f281bf Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Tue, 31 Mar 2026 17:10:58 -0600 Subject: [PATCH 10/29] remove older windio syntax --- examples/09_floating/IEA-22-280-RWT_Floater.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/09_floating/IEA-22-280-RWT_Floater.yaml b/examples/09_floating/IEA-22-280-RWT_Floater.yaml index 4be41ad26..e40777ec5 100644 --- a/examples/09_floating/IEA-22-280-RWT_Floater.yaml +++ b/examples/09_floating/IEA-22-280-RWT_Floater.yaml @@ -1118,10 +1118,9 @@ components: generator_rpm_efficiency_user: grid: [1.98, 2.8, 3.47, 3.98, 4.31, 4.49, 5.1, 5.66, 6.39, 7.16] values: [0.7878, 0.9071, 0.941, 0.9526, 0.9566, 0.9582, 0.9614, 0.9622, 0.9608, 0.9541] - elastic_properties_mb: - system_mass: 508040.0 length: 3.047 radius: 4.89 + mass: 508040.0 airfoils: - name: FFA-W3-211 coordinates: @@ -1838,4 +1837,4 @@ control: pitch_actuator_damping: 0.707 yaw_rate: 0.49847328176381617 max_allowable_blade_tip_speed: 95.0 - peak_thrust_shaving: 1.0 \ No newline at end of file + peak_thrust_shaving: 1.0 From f6184eb529345af9268a6d75ce6bd5617c15be8a Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Wed, 1 Apr 2026 10:14:13 -0600 Subject: [PATCH 11/29] be sure to output the right cm --- wisdem/postprocessing/wisdem_get.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wisdem/postprocessing/wisdem_get.py b/wisdem/postprocessing/wisdem_get.py index fb1e4f814..2f77439c8 100644 --- a/wisdem/postprocessing/wisdem_get.py +++ b/wisdem/postprocessing/wisdem_get.py @@ -256,7 +256,7 @@ def get_nacelle_mass(prob): rna_I_TT = prob['drivese.rna_I_TT'] rna_I = util.unassembleI( util.assembleI(rna_I_TT) + rna_mass * (np.dot(R, R) * np.eye(3) + np.outer(R, R)) ) - nacDF.loc['Blades'] = np.r_[blades_mass, hub_cm, blades_I, blades_I_TT].tolist() + nacDF.loc['Blades'] = np.r_[blades_mass, blades_cm, blades_I, blades_I_TT].tolist() nacDF.loc['Hub_System'] = np.r_[hub_mass, hub_cm, hub_I, hub_I_TT].tolist() nacDF.loc['RNA'] = np.r_[rna_mass, rna_cm, rna_I, rna_I_TT].tolist() From 6c84d938c88cbbf9c149d36878a220a362324ebc Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 2 Apr 2026 08:52:40 -0700 Subject: [PATCH 12/29] allow for custom array layout in layout generation step --- wisdem/orbit/orbit_api.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wisdem/orbit/orbit_api.py b/wisdem/orbit/orbit_api.py index 5eb3a9809..8149e8073 100644 --- a/wisdem/orbit/orbit_api.py +++ b/wisdem/orbit/orbit_api.py @@ -805,4 +805,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): outputs["installation_time"] = project.installation_time outputs["installation_capex"] = project.installation_capex outputs["capacity"] = project.capacity - discrete_outputs["layout"] = project.phases["ArraySystemDesign"].create_layout_df() + if "ArraySystemDesign" in project.phases: + discrete_outputs["layout"] = project.phases["ArraySystemDesign"].create_layout_df() + elif "CustomArraySystemDesign" in project.phases: + discrete_outputs["layout"] = project.phases["CustomArraySystemDesign"].create_layout_df() From 793f1c8619cc874de0dbbf686dff8ed7d1145e54 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 2 Apr 2026 08:52:54 -0700 Subject: [PATCH 13/29] bump patch version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 918fe9b23..14ff729cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "wisdem" -version = "4.1.0" +version = "4.1.1" description = "Wind-Plant Integrated System Design & Engineering Model" readme = "README.md" requires-python = ">=3.9" From 3badb26f962f1ac1faf4259a3ce5c5cdc212b30b Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 2 Apr 2026 08:53:21 -0700 Subject: [PATCH 14/29] add missing __version__ attribute --- wisdem/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wisdem/__init__.py b/wisdem/__init__.py index dc446aef5..bfb51242b 100644 --- a/wisdem/__init__.py +++ b/wisdem/__init__.py @@ -1 +1,5 @@ +from importlib.metadata import version + from wisdem.glue_code.runWISDEM import run_wisdem + +__version__ = version("wisdem") From 79b4ba485e99a8eaa2dae8f5dc437ece5894c6cf Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 2 Apr 2026 08:54:30 -0700 Subject: [PATCH 15/29] update orbit min version for patch --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 14ff729cb..591d5e40a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ dependencies = [ "numpy", "openmdao", "openpyxl", - "orbit-nrel>=1.2.5", + "orbit-nrel>=1.2.6", "pandas", "pydoe3", "pyyaml", From ccc997445ba257579da072a511de633dc5c577f9 Mon Sep 17 00:00:00 2001 From: ptrbortolotti Date: Fri, 3 Apr 2026 15:41:12 -0600 Subject: [PATCH 16/29] update start and end nd arc in output yaml as blade shape gets optimized --- wisdem/glue_code/gc_LoadInputs.py | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/wisdem/glue_code/gc_LoadInputs.py b/wisdem/glue_code/gc_LoadInputs.py index 0e7ebb703..8f0f37830 100644 --- a/wisdem/glue_code/gc_LoadInputs.py +++ b/wisdem/glue_code/gc_LoadInputs.py @@ -866,6 +866,8 @@ def update_ontology(self, wt_opt): if not self.modeling_options["user_elastic"]["blade"]: # Webs positions TBD # Structural layers + anchors = self.wt_init["components"]["blade"]["structure"]["anchors"] + n_anchors = len(anchors) for i in range(self.modeling_options["WISDEM"]["RotorSE"]["n_layers"]): self.wt_init["components"]["blade"]["structure"]["layers"][i]["thickness"]["grid"] = wt_opt[ "blade.outer_shape.s" @@ -873,6 +875,60 @@ def update_ontology(self, wt_opt): self.wt_init["components"]["blade"]["structure"]["layers"][i]["thickness"]["values"] = wt_opt[ "blade.ps.layer_thickness_param" ][i, :].tolist() + # Update start_nd_arc for the anchor + if "anchor" in self.wt_init["components"]["blade"]["structure"]["layers"][i]["start_nd_arc"]: + anchor_name = self.wt_init["components"]["blade"]["structure"]["layers"][i]["start_nd_arc"]["anchor"]["name"] + anchor_handle = self.wt_init["components"]["blade"]["structure"]["layers"][i]["start_nd_arc"]["anchor"]["handle"] + for j in range(n_anchors): + if anchors[j]["name"] == anchor_name: + self.wt_init["components"]["blade"]["structure"]["anchors"][j][anchor_handle]["grid"] = wt_opt[ + "blade.outer_shape.s" + ].tolist() + self.wt_init["components"]["blade"]["structure"]["anchors"][j][anchor_handle]["values"] = wt_opt[ + "blade.structure.layer_start_nd" + ][i, :].tolist() + break + # Update end_nd_arc for the anchor + if "anchor" in self.wt_init["components"]["blade"]["structure"]["layers"][i]["end_nd_arc"]: + anchor_name = self.wt_init["components"]["blade"]["structure"]["layers"][i]["end_nd_arc"]["anchor"]["name"] + anchor_handle = self.wt_init["components"]["blade"]["structure"]["layers"][i]["end_nd_arc"]["anchor"]["handle"] + for j in range(n_anchors): + if anchors[j]["name"] == anchor_name: + self.wt_init["components"]["blade"]["structure"]["anchors"][j][anchor_handle]["grid"] = wt_opt[ + "blade.outer_shape.s" + ].tolist() + self.wt_init["components"]["blade"]["structure"]["anchors"][j][anchor_handle]["values"] = wt_opt[ + "blade.structure.layer_end_nd" + ][i, :].tolist() + break + for i in range(self.modeling_options["WISDEM"]["RotorSE"]["n_webs"]): + # Update start_nd_arc for the anchor + if "anchor" in self.wt_init["components"]["blade"]["structure"]["webs"][i]["start_nd_arc"]: + anchor_name = self.wt_init["components"]["blade"]["structure"]["webs"][i]["start_nd_arc"]["anchor"]["name"] + anchor_handle = self.wt_init["components"]["blade"]["structure"]["webs"][i]["start_nd_arc"]["anchor"]["handle"] + for j in range(n_anchors): + if anchors[j]["name"] == anchor_name: + self.wt_init["components"]["blade"]["structure"]["anchors"][j][anchor_handle]["grid"] = wt_opt[ + "blade.outer_shape.s" + ].tolist() + self.wt_init["components"]["blade"]["structure"]["anchors"][j][anchor_handle]["values"] = wt_opt[ + "blade.structure.web_start_nd" + ][i, :].tolist() + break + # Update end_nd_arc for the anchor + if "anchor" in self.wt_init["components"]["blade"]["structure"]["webs"][i]["end_nd_arc"]: + anchor_name = self.wt_init["components"]["blade"]["structure"]["webs"][i]["end_nd_arc"]["anchor"]["name"] + anchor_handle = self.wt_init["components"]["blade"]["structure"]["webs"][i]["end_nd_arc"]["anchor"]["handle"] + for j in range(n_anchors): + if anchors[j]["name"] == anchor_name: + self.wt_init["components"]["blade"]["structure"]["anchors"][j][anchor_handle]["grid"] = wt_opt[ + "blade.outer_shape.s" + ].tolist() + self.wt_init["components"]["blade"]["structure"]["anchors"][j][anchor_handle]["values"] = wt_opt[ + "blade.structure.web_end_nd" + ][i, :].tolist() + break + if "elastic_properties" not in self.wt_init["components"]["blade"]["structure"]: self.wt_init["components"]["blade"]["structure"]["elastic_properties"] = {} From d4912e0f33a2e72d7cefee8d6c372761b74fcd5b Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Sun, 5 Apr 2026 15:54:29 -0600 Subject: [PATCH 17/29] correct RNA MoI moving from TT to CoM --- wisdem/postprocessing/wisdem_get.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wisdem/postprocessing/wisdem_get.py b/wisdem/postprocessing/wisdem_get.py index 2f77439c8..170ae301b 100644 --- a/wisdem/postprocessing/wisdem_get.py +++ b/wisdem/postprocessing/wisdem_get.py @@ -254,8 +254,9 @@ def get_nacelle_mass(prob): rna_mass = prob['drivese.rna_mass'] rna_cm = R = prob['drivese.rna_cm'] rna_I_TT = prob['drivese.rna_I_TT'] - rna_I = util.unassembleI( util.assembleI(rna_I_TT) + - rna_mass * (np.dot(R, R) * np.eye(3) + np.outer(R, R)) ) + # Moving from TT-coord sys back to CoM, so subtract. + rna_I = util.unassembleI( util.assembleI(rna_I_TT) - + rna_mass * (np.dot(R, R) * np.eye(3) + np.outer(R, R)) ) nacDF.loc['Blades'] = np.r_[blades_mass, blades_cm, blades_I, blades_I_TT].tolist() nacDF.loc['Hub_System'] = np.r_[hub_mass, hub_cm, hub_I, hub_I_TT].tolist() nacDF.loc['RNA'] = np.r_[rna_mass, rna_cm, rna_I, rna_I_TT].tolist() From 535f84fe05fb9649652fbcd565feb20c11f5a6ef Mon Sep 17 00:00:00 2001 From: ptrbortolotti Date: Mon, 6 Apr 2026 09:56:39 -0600 Subject: [PATCH 18/29] fix typo in iea10 --- examples/02_reference_turbines/IEA-10-198-RWT.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/02_reference_turbines/IEA-10-198-RWT.yaml b/examples/02_reference_turbines/IEA-10-198-RWT.yaml index 87cdc7360..cd4c93851 100644 --- a/examples/02_reference_turbines/IEA-10-198-RWT.yaml +++ b/examples/02_reference_turbines/IEA-10-198-RWT.yaml @@ -455,7 +455,7 @@ components: start_nd_arc: anchor: name: DP02_DP00_triax - handle: DP02_DP00_uniax + handle: start_nd_arc end_nd_arc: anchor: name: TE From b5de2db0cb7e53f879e3ae93b44bbf44090034f3 Mon Sep 17 00:00:00 2001 From: ptrbortolotti Date: Tue, 7 Apr 2026 20:37:43 -0600 Subject: [PATCH 19/29] best to increase max_pitch_perf_surfaces to 40 deg to avoid errors in weis --- wisdem/inputs/modeling_schema.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wisdem/inputs/modeling_schema.yaml b/wisdem/inputs/modeling_schema.yaml index 896b314ef..a7e952a3e 100644 --- a/wisdem/inputs/modeling_schema.yaml +++ b/wisdem/inputs/modeling_schema.yaml @@ -73,7 +73,7 @@ properties: description: Min pitch angle of the Cp-Ct-Cq-surfaces max_pitch_perf_surfaces: type: number - default: 30. + default: 40. description: Max pitch angle of the Cp-Ct-Cq-surfaces n_tsr_perf_surfaces: type: integer From df7ce124e9137a3a10a03362a236b3b2eaec9164 Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Wed, 8 Apr 2026 07:10:29 -0600 Subject: [PATCH 20/29] hopefully one last sign correction --- wisdem/postprocessing/wisdem_get.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wisdem/postprocessing/wisdem_get.py b/wisdem/postprocessing/wisdem_get.py index 170ae301b..93f30d14c 100644 --- a/wisdem/postprocessing/wisdem_get.py +++ b/wisdem/postprocessing/wisdem_get.py @@ -256,7 +256,7 @@ def get_nacelle_mass(prob): rna_I_TT = prob['drivese.rna_I_TT'] # Moving from TT-coord sys back to CoM, so subtract. rna_I = util.unassembleI( util.assembleI(rna_I_TT) - - rna_mass * (np.dot(R, R) * np.eye(3) + np.outer(R, R)) ) + rna_mass * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) ) nacDF.loc['Blades'] = np.r_[blades_mass, blades_cm, blades_I, blades_I_TT].tolist() nacDF.loc['Hub_System'] = np.r_[hub_mass, hub_cm, hub_I, hub_I_TT].tolist() nacDF.loc['RNA'] = np.r_[rna_mass, rna_cm, rna_I, rna_I_TT].tolist() From 4132b0b7b098fc3ea33f4aeff5e31588e41cb74b Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Wed, 8 Apr 2026 07:11:23 -0600 Subject: [PATCH 21/29] move pypi packages to conda in environment yamls --- environment.yml | 6 +++--- environment_dev.yml | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/environment.yml b/environment.yml index 861be5e54..cdccb0b64 100644 --- a/environment.yml +++ b/environment.yml @@ -19,6 +19,7 @@ dependencies: - numpy - openmdao - openpyxl + - orbit-nrel>=1.2.5 - pandas - pydoe3 - pyoptsparse @@ -27,8 +28,7 @@ dependencies: - sortedcontainers - statsmodels>=0.14.5 - pip + - windIO + - wombat>=0.13.1 - pip: - - orbit-nrel>=1.2.5 - - wombat>=0.13.1 - dearpygui - - windIO diff --git a/environment_dev.yml b/environment_dev.yml index fa3d16baf..9caa2ef97 100644 --- a/environment_dev.yml +++ b/environment_dev.yml @@ -10,6 +10,7 @@ dependencies: # - petsc4py # [unix] # - mpi4py # [unix] # only needed if local system has mpi built and is used - cython + - docstring-parser - jsonmerge - jsonschema - matplotlib @@ -19,6 +20,7 @@ dependencies: - numpy - openmdao - openpyxl + - orbit-nrel>=1.2.5 - pandas - pydoe3 - pyoptsparse @@ -35,13 +37,11 @@ dependencies: - pytest - pytest-cov - pip + - sphinxcontrib-bibtex + - sphinx-copybutton + - sphinx-jsonschema + - sphinx_rtd_theme>=1.3 + - windIO>=2.1.1 + - wombat>=0.13.1 - pip: - - orbit-nrel>=1.2.5 - - wombat>=0.13.1 - dearpygui - - windIO - - sphinxcontrib-bibtex - - sphinx_rtd_theme>=1.3 - - sphinx-jsonschema - - sphinx-copybutton - - docstring-parser From 4dfb9537afaaaf1f80ba668a7bf8aeda745b4a50 Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Wed, 8 Apr 2026 07:20:48 -0600 Subject: [PATCH 22/29] fix package name --- environment_dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment_dev.yml b/environment_dev.yml index 9caa2ef97..4f0e23b2a 100644 --- a/environment_dev.yml +++ b/environment_dev.yml @@ -10,7 +10,7 @@ dependencies: # - petsc4py # [unix] # - mpi4py # [unix] # only needed if local system has mpi built and is used - cython - - docstring-parser + - docstring_parser - jsonmerge - jsonschema - matplotlib From 9b59cb8ce659e76d98076ac2253af4951698503a Mon Sep 17 00:00:00 2001 From: ptrbortolotti Date: Wed, 8 Apr 2026 09:49:49 -0600 Subject: [PATCH 23/29] issue #709 --- examples/03_blade/analysis_options_aero.yaml | 2 +- .../03_blade/analysis_options_aerostruct.yaml | 6 +-- examples/03_blade/analysis_options_user.yaml | 6 +-- .../analysis_options.yaml | 4 +- .../analysis_options_rotor.yaml | 6 +-- .../analysis_options.yaml | 6 +-- .../analysis_options.yaml | 6 +-- wisdem/ccblade/ccblade_component.py | 2 +- wisdem/glue_code/gc_PoseOptimization.py | 6 +-- wisdem/inputs/analysis_schema.yaml | 48 +++++++++---------- 10 files changed, 46 insertions(+), 46 deletions(-) diff --git a/examples/03_blade/analysis_options_aero.yaml b/examples/03_blade/analysis_options_aero.yaml index 530d12a19..02542b3a3 100644 --- a/examples/03_blade/analysis_options_aero.yaml +++ b/examples/03_blade/analysis_options_aero.yaml @@ -40,7 +40,7 @@ constraints: blade: stall: flag: False # Constraint on minimum stall margin - margin: 0.1 # Value of minimum stall margin in [rad] + margin: 5.0 # Value of minimum stall margin in [deg] chord: flag: True # Constraint max chord to its default value (4.75 m) max: 4.75 # Max chord value diff --git a/examples/03_blade/analysis_options_aerostruct.yaml b/examples/03_blade/analysis_options_aerostruct.yaml index cb4090804..6679f1b03 100644 --- a/examples/03_blade/analysis_options_aerostruct.yaml +++ b/examples/03_blade/analysis_options_aerostruct.yaml @@ -13,8 +13,8 @@ design_variables: flag: True # Flag to optimize the twist inverse: False # Flag to determine twist from the user-defined desired margin to stall (defined in constraints) n_opt: 4 # Number of control points along blade span - max_decrease: 0.08722222222222221 # Maximum decrease for the twist in [rad] at the n_opt locations - max_increase: 0.08722222222222221 # Maximum increase for the twist in [rad] at the n_opt locations + max_decrease: 5.0 # Maximum decrease for the twist in [deg] at the n_opt locations + max_increase: 5.0 # Maximum increase for the twist in [deg] at the n_opt locations index_start: 2 # Lock the first two DVs from blade root index_end: 4 # All DVs close to blade tip are active chord: @@ -67,7 +67,7 @@ constraints: margin: 1.4175 stall: flag: True # Constraint on minimum stall margin - margin: 0.087 # Value of minimum stall margin in [rad] + margin: 5.0 # Value of minimum stall margin in [deg] moment_coefficient: flag: True # Constraint on minimum stall margin max: 0.16 diff --git a/examples/03_blade/analysis_options_user.yaml b/examples/03_blade/analysis_options_user.yaml index 2754e6da8..cda382e44 100644 --- a/examples/03_blade/analysis_options_user.yaml +++ b/examples/03_blade/analysis_options_user.yaml @@ -9,8 +9,8 @@ design_variables: flag: True # Flag to optimize the twist inverse: False # Flag to determine twist from the user-defined desired margin to stall (defined in constraints) n_opt: 4 # Number of control points along blade span - max_decrease: 0.08722222222222221 # Maximum decrease for the twist in [rad] at the n_opt locations - max_increase: 0.08722222222222221 # Maximum increase for the twist in [rad] at the n_opt locations + max_decrease: 5.0 # Maximum decrease for the twist in [deg] at the n_opt locations + max_increase: 5.0 # Maximum increase for the twist in [deg] at the n_opt locations index_start: 2 # Lock the first two DVs from blade root index_end: 4 # All DVs close to blade tip are active chord: @@ -42,7 +42,7 @@ constraints: blade: stall: flag: True # Constraint on minimum stall margin - margin: 0.087 # Value of minimum stall margin in [rad] + margin: 5.0 # Value of minimum stall margin in [deg] chord: flag: False # Constraint max chord to its default value (4.75 m) root_circle_diameter: diff --git a/examples/13_design_of_experiments/analysis_options.yaml b/examples/13_design_of_experiments/analysis_options.yaml index 826c8d81d..342a5da55 100644 --- a/examples/13_design_of_experiments/analysis_options.yaml +++ b/examples/13_design_of_experiments/analysis_options.yaml @@ -8,8 +8,8 @@ design_variables: flag: False # Flag to optimize the twist inverse: False # Flag to determine twist from the user-defined desired margin to stall (defined in constraints) n_opt: 8 # Number of control points along blade span - max_decrease: 0.08722222222222221 # Maximum decrease for the twist in [rad] at the n_opt locations - max_increase: 0.08722222222222221 # Maximum increase for the twist in [rad] at the n_opt locations + max_decrease: 5.0 # Maximum decrease for the twist in [deg] at the n_opt locations + max_increase: 5.0 # Maximum increase for the twist in [deg] at the n_opt locations chord: flag: True # Flag to optimize the chord n_opt: 8 # Number of control points along blade span diff --git a/examples/16_inverse_design/analysis_options_rotor.yaml b/examples/16_inverse_design/analysis_options_rotor.yaml index 59aa66055..4f9a04087 100644 --- a/examples/16_inverse_design/analysis_options_rotor.yaml +++ b/examples/16_inverse_design/analysis_options_rotor.yaml @@ -9,8 +9,8 @@ design_variables: twist: flag: True # Flag to optimize the twist n_opt: 4 # Number of control points along blade span - max_decrease: 0.08722222222222221 # Maximum decrease for the twist in [rad] at the n_opt locations - max_increase: 0.08722222222222221 # Maximum increase for the twist in [rad] at the n_opt locations + max_decrease: 5.0 # Maximum decrease for the twist in [deg] at the n_opt locations + max_increase: 5.0 # Maximum increase for the twist in [deg] at the n_opt locations index_start: 2 # Lock the first two DVs from blade root index_end: 4 # All DVs close to blade tip are active chord: @@ -95,7 +95,7 @@ constraints: margin: 1.4175 stall: flag: True # Constraint on minimum stall margin - margin: 0.087 # Value of minimum stall margin in [rad] + margin: 5.0 # Value of minimum stall margin in [deg] moment_coefficient: flag: True # Constraint on minimum stall margin max: 0.16 diff --git a/examples/18_rotor_tower_monopile/analysis_options.yaml b/examples/18_rotor_tower_monopile/analysis_options.yaml index dd70808ed..96f314588 100644 --- a/examples/18_rotor_tower_monopile/analysis_options.yaml +++ b/examples/18_rotor_tower_monopile/analysis_options.yaml @@ -9,8 +9,8 @@ design_variables: flag: True # Flag to optimize the twist inverse: False # Flag to determine twist from the user-defined desired margin to stall (defined in constraints) n_opt: 6 # Number of control points along blade span - max_decrease: 0.08722222222222221 # Maximum decrease for the twist in [rad] at the n_opt locations - max_increase: 0.08722222222222221 # Maximum increase for the twist in [rad] at the n_opt locations + max_decrease: 5.0 # Maximum decrease for the twist in [deg] at the n_opt locations + max_increase: 5.0 # Maximum increase for the twist in [deg] at the n_opt locations index_start: 1 # Lock the first DV at blade root index_end: 5 # All DVs close to blade tip are active chord: @@ -71,7 +71,7 @@ constraints: margin: 1.4175 stall: flag: True # Constraint on minimum stall margin - margin: 0.05233 # Value of minimum stall margin in [rad] + margin: 5.0 # Value of minimum stall margin in [deg] moment_coefficient: flag: True max: 0.16 diff --git a/examples/19_rotor_drivetrain_tower/analysis_options.yaml b/examples/19_rotor_drivetrain_tower/analysis_options.yaml index b014c4e3b..c5ec56a70 100644 --- a/examples/19_rotor_drivetrain_tower/analysis_options.yaml +++ b/examples/19_rotor_drivetrain_tower/analysis_options.yaml @@ -13,8 +13,8 @@ design_variables: flag: True inverse: False n_opt: 8 - max_decrease: 0.08722222222222223 - max_increase: 0.08722222222222223 + max_decrease: 5.0 + max_increase: 5.0 index_start: 2 index_end: 8 chord: @@ -82,7 +82,7 @@ constraints: blade: stall: flag: True - margin: 0.05233 + margin: 5.0 chord: flag: True max: 4.3 diff --git a/wisdem/ccblade/ccblade_component.py b/wisdem/ccblade/ccblade_component.py index 7c31c5e04..95bad1974 100644 --- a/wisdem/ccblade/ccblade_component.py +++ b/wisdem/ccblade/ccblade_component.py @@ -538,7 +538,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Cap twist root region to 20 degrees for i in range(len(ccblade.theta)): - cap_twist_root = self.options["opt_options"]["design_variables"]["blade"]["aero_shape"]["twist"]["cap_twist_root"] + cap_twist_root = np.deg2rad(self.options["opt_options"]["design_variables"]["blade"]["aero_shape"]["twist"]["cap_twist_root"]) if ccblade.theta[-i - 1] > cap_twist_root: ccblade.theta[0 : len(ccblade.theta) - i] = cap_twist_root break diff --git a/wisdem/glue_code/gc_PoseOptimization.py b/wisdem/glue_code/gc_PoseOptimization.py index fb197ae77..842322c7b 100644 --- a/wisdem/glue_code/gc_PoseOptimization.py +++ b/wisdem/glue_code/gc_PoseOptimization.py @@ -449,8 +449,8 @@ def set_design_variables(self, wt_opt, wt_init): wt_opt.model.add_design_var( "blade.opt_var.twist_opt", indices=indices_twist, - lower=init_twist_opt[indices_twist] - blade_opt["aero_shape"]["twist"]["max_decrease"], - upper=init_twist_opt[indices_twist] + blade_opt["aero_shape"]["twist"]["max_increase"], + lower=init_twist_opt[indices_twist] - np.deg2rad(blade_opt["aero_shape"]["twist"]["max_decrease"]), + upper=init_twist_opt[indices_twist] + np.deg2rad(blade_opt["aero_shape"]["twist"]["max_increase"]), ) chord_options = blade_opt["aero_shape"]["chord"] @@ -1376,7 +1376,7 @@ def set_initial(self, wt_opt, wt_init): wt_opt["rotorse.rs.constr.max_strainU_te"] = blade_constr["strains_te_ss"]["max"] wt_opt["rotorse.rs.constr.max_strainL_te"] = blade_constr["strains_te_ps"]["max"] - wt_opt["rotorse.stall_check.stall_margin"] = blade_constr["stall"]["margin"] * 180.0 / np.pi + wt_opt["rotorse.stall_check.stall_margin"] = blade_constr["stall"]["margin"] if self.modeling["flags"]["tower"]: wt_opt["tcons.max_allowable_td_ratio"] = blade_constr["tip_deflection"]["margin"] diff --git a/wisdem/inputs/analysis_schema.yaml b/wisdem/inputs/analysis_schema.yaml index c614cb76c..01f39e703 100644 --- a/wisdem/inputs/analysis_schema.yaml +++ b/wisdem/inputs/analysis_schema.yaml @@ -58,7 +58,7 @@ properties: twist: type: object default: {} - description: Blade twist as a design variable by adding or subtracting radians from the initial value at spline control points along the span. + description: Blade twist as a design variable by adding or subtracting degrees from the initial value at spline control points along the span. properties: flag: *flag inverse: @@ -78,13 +78,13 @@ properties: max_decrease: type: number description: Maximum allowable decrease of twist at each DV location along blade span. - default: 0.1 - unit: rad + default: 5.0 + unit: deg max_increase: type: number description: Maximum allowable increase of twist at each DV location along blade span. - default: 0.1 - unit: rad + default: 5.0 + unit: deg index_start: &index_start type: integer default: 0 @@ -100,8 +100,8 @@ properties: cap_twist_root: type: number description: Maximum allowable twist during an inverse design. Close to blade root, twist will be capped to this value. - default: 0.349 - unit: rad + default: 20.0 + unit: deg chord: type: object default: {} @@ -194,9 +194,9 @@ properties: lower_bound: &angbound type: number minimum: 0.0 - maximum: 0.5235987756 # 30 deg + maximum: 30.0 default: 0.0 - unit: rad + unit: deg description: Design variable bound upper_bound: *angbound hub_diameter: @@ -733,18 +733,18 @@ properties: properties: lower_bound: type: number - unit: rad + unit: deg description: Design variable bound default: 0.0 minimum: 0.0 - maximum: 3.141592653589793 #180 deg + maximum: 180.0 #180 deg upper_bound: type: number - unit: rad + unit: deg description: Design variable bound - default: 0.1 + default: 5.0 minimum: 0.0 - maximum: 3.141592653589793 #180 deg + maximum: 180.0 #180 deg mooring: type: object description: Design variables associated with the mooring system @@ -931,10 +931,10 @@ properties: flag: *flag margin: type: number - default: 0.05233 # 3 deg + default: 5.0 # 5 deg minimum: 0.0 - maximum: 0.5 - unit: radians + maximum: 30.0 + unit: deg chord: type: object description: Enforcing the maximum chord length limit at all points along blade span. @@ -1288,8 +1288,8 @@ properties: type: number default: 1e-3 minimum: 1e-5 - maximum: 1.0 - unit: radian + maximum: 30.0 + unit: deg description: Upper limit of angular deflection stator_deflection: type: object @@ -1301,7 +1301,7 @@ properties: stator_angle: type: object default: {} - description: Allowable non-torque angular deflection of the nose or bedplate, in radians, at the generator stator attachment + description: Allowable non-torque angular deflection of the nose or bedplate, in degrees, at the generator stator attachment properties: flag: *flag upper_bound: *angular_upper @@ -1322,10 +1322,10 @@ properties: properties: upper_bound: &upang type: number - default: 0.17453292519943295 # 10 deg - minimum: 0.017453292519943295 # 1 deg - maximum: 0.7853981633974483 # 45 deg - unit: rad + default: 10.0 + minimum: 1.0 + maximum: 45.0 + unit: deg survival_heel: *heelang max_surge: type: object From 45df5ad4f0987a41fb3b685aff33bd13476d832a Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Wed, 8 Apr 2026 13:41:40 -0600 Subject: [PATCH 24/29] changing up the windows compilers? --- .github/workflows/CI_WISDEM.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI_WISDEM.yml b/.github/workflows/CI_WISDEM.yml index a3fd18083..4f8104467 100644 --- a/.github/workflows/CI_WISDEM.yml +++ b/.github/workflows/CI_WISDEM.yml @@ -46,7 +46,7 @@ jobs: - name: Add dependencies windows specific if: contains( matrix.os, 'windows') run: | - conda install -y m2w64-toolchain libpython + conda install -y gfortran gcc - name: Debug config run: | From 13cae4e2a943ecc0fe9209145f254a3b5bdcdd4d Mon Sep 17 00:00:00 2001 From: ptrbortolotti Date: Wed, 8 Apr 2026 20:28:10 -0600 Subject: [PATCH 25/29] fix a couple of missing deg2rad, rad2deg --- examples/03_blade/analysis_options_aero.yaml | 4 ++-- wisdem/ccblade/ccblade_component.py | 2 +- wisdem/glue_code/gc_PoseOptimization.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/03_blade/analysis_options_aero.yaml b/examples/03_blade/analysis_options_aero.yaml index 02542b3a3..afd4b35bb 100644 --- a/examples/03_blade/analysis_options_aero.yaml +++ b/examples/03_blade/analysis_options_aero.yaml @@ -18,9 +18,9 @@ design_variables: # corresponding to 'max_efficiency' or 'stall_margin' n_opt: 8 # Number of control points along blade span. During inverse design, # twist is smoothened with a spline with these - # max_decrease: 0.08722222222222221 # Maximum decrease for the twist + # max_decrease: 5 # Maximum decrease for the twist # in [rad] at the n_opt locations. Only used if flag is set to True - # max_increase: 0.08722222222222221 # Maximum increase for the twist + # max_increase: 5 # Maximum increase for the twist # in [rad] at the n_opt locations. Only used if flag is set to True # index_start: 2 # Lock the first two DVs from blade root # index_end: 8 # All DVs close to blade tip are active diff --git a/wisdem/ccblade/ccblade_component.py b/wisdem/ccblade/ccblade_component.py index 95bad1974..ede4ed409 100644 --- a/wisdem/ccblade/ccblade_component.py +++ b/wisdem/ccblade/ccblade_component.py @@ -498,7 +498,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): cd = np.zeros(self.n_span) alpha = np.zeros(self.n_span) Emax = np.zeros(self.n_span) - margin2stall = self.options["opt_options"]["constraints"]["blade"]["stall"]["margin"] * 180.0 / np.pi + margin2stall = self.options["opt_options"]["constraints"]["blade"]["stall"]["margin"] Re = np.array(Omega * inputs["r"] * inputs["chord"] * inputs["rho"][0] / inputs["mu"][0]) aoa_op = inputs["aoa_op"] for i in range(self.n_span): diff --git a/wisdem/glue_code/gc_PoseOptimization.py b/wisdem/glue_code/gc_PoseOptimization.py index 842322c7b..e3b6ab79c 100644 --- a/wisdem/glue_code/gc_PoseOptimization.py +++ b/wisdem/glue_code/gc_PoseOptimization.py @@ -1384,10 +1384,10 @@ def set_initial(self, wt_opt, wt_init): drive_constr = self.opt["constraints"]["drivetrain"] wt_opt["drivese.shaft_deflection_allowable"] = drive_constr["shaft_deflection"]["upper_bound"] - wt_opt["drivese.shaft_angle_allowable"] = drive_constr["shaft_angle"]["upper_bound"] + wt_opt["drivese.shaft_angle_allowable"] = np.deg2rad(drive_constr["shaft_angle"]["upper_bound"]) wt_opt["drivese.stator_deflection_allowable"] = drive_constr["stator_deflection"]["upper_bound"] - wt_opt["drivese.stator_angle_allowable"] = drive_constr["stator_angle"]["upper_bound"] + wt_opt["drivese.stator_angle_allowable"] = np.deg2rad(drive_constr["stator_angle"]["upper_bound"]) if self.modeling["WISDEM"]["DriveSE"]["direct"]: wt_opt["drivese.access_diameter"] = drive_constr["access"]["lower_bound"] From e2026de3e3526256cbd5e58a835102c87bd12a02 Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Thu, 9 Apr 2026 05:32:01 -0600 Subject: [PATCH 26/29] add py314, drop py311 since numpy dropping support --- .github/workflows/CI_WISDEM.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI_WISDEM.yml b/.github/workflows/CI_WISDEM.yml index 4f8104467..cb906300c 100644 --- a/.github/workflows/CI_WISDEM.yml +++ b/.github/workflows/CI_WISDEM.yml @@ -17,7 +17,7 @@ jobs: fail-fast: False matrix: os: [ubuntu-latest, macos-14, windows-latest] - python-version: ["3.11", "3.12", "3.13"] + python-version: ["3.12", "3.13", "3.14"] steps: - name: checkout repository @@ -75,7 +75,7 @@ jobs: # Run coveralls - name: Run coveralls - if: contains( matrix.os, 'ubuntu') && contains( matrix.python-version, '3.12') + if: contains( matrix.os, 'ubuntu') && contains( matrix.python-version, '3.13') #uses: coverallsapp/github-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -94,7 +94,7 @@ jobs: fail-fast: False matrix: os: [ubuntu-latest, windows-latest, macos-14] #mac-13 intel, mac-latest is arm - python-version: ["3.12"] + python-version: ["3.13"] steps: - name: Setup GNU Fortran From 6f1fcbb323d2a84ef87dd6b443d9c873947c9896 Mon Sep 17 00:00:00 2001 From: ptrbortolotti Date: Thu, 9 Apr 2026 12:15:26 -0600 Subject: [PATCH 27/29] fix example 09 --- examples/09_floating/analysis_options_mooropt.yaml | 4 ++-- examples/09_floating/analysis_options_semiopt.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/09_floating/analysis_options_mooropt.yaml b/examples/09_floating/analysis_options_mooropt.yaml index fd2f0cf6f..d9126ec7f 100644 --- a/examples/09_floating/analysis_options_mooropt.yaml +++ b/examples/09_floating/analysis_options_mooropt.yaml @@ -26,9 +26,9 @@ merit_figure: mooring_mass constraints: floating: operational_heel: - upper_bound: 0.08726646259971647 # 5 deg + upper_bound: 5.0 survival_heel: - upper_bound: 0.17453292519943295 # 10 deg + upper_bound: 10.0 max_surge: flag: false upper_bound: 0.1 diff --git a/examples/09_floating/analysis_options_semiopt.yaml b/examples/09_floating/analysis_options_semiopt.yaml index a12a315dd..9e143621d 100644 --- a/examples/09_floating/analysis_options_semiopt.yaml +++ b/examples/09_floating/analysis_options_semiopt.yaml @@ -84,9 +84,9 @@ merit_figure: LCOE constraints: floating: operational_heel: - upper_bound: 0.08726646259971647 # 5 deg + upper_bound: 5.0 survival_heel: - upper_bound: 0.17453292519943295 # 10 deg + upper_bound: 10.0 max_surge: flag: false upper_bound: 0.1 From 7efe734751df72a81e0dab136e5c2e8879280216 Mon Sep 17 00:00:00 2001 From: ptrbortolotti Date: Thu, 9 Apr 2026 15:19:18 -0600 Subject: [PATCH 28/29] forgot one file... --- examples/09_floating/analysis_options_sparopt.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/09_floating/analysis_options_sparopt.yaml b/examples/09_floating/analysis_options_sparopt.yaml index 655f2cfff..5439edf0d 100644 --- a/examples/09_floating/analysis_options_sparopt.yaml +++ b/examples/09_floating/analysis_options_sparopt.yaml @@ -28,9 +28,9 @@ merit_figure: platform_mass constraints: floating: operational_heel: - upper_bound: 0.08726646259971647 # 5 deg + upper_bound: 5.0 survival_heel: - upper_bound: 0.17453292519943295 # 10 deg + upper_bound: 10.0 max_surge: flag: false upper_bound: 0.1 From dca2913b6fb38af1910ba4618a7ff0d8d1a60df3 Mon Sep 17 00:00:00 2001 From: Garrett Barter Date: Fri, 10 Apr 2026 08:16:17 -0600 Subject: [PATCH 29/29] updating windows packages for install per conda forge updates --- environment.yml | 6 +++--- environment_dev.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/environment.yml b/environment.yml index cdccb0b64..3de791f1e 100644 --- a/environment.yml +++ b/environment.yml @@ -4,8 +4,8 @@ channels: dependencies: # NOTE: uncomment the OS-dependent lines below as needed - # - m2w64-toolchain # [win] - # - libpython # [win] + # - gfortran # [win] + # - gcc # [win] # - gfortran # [osx] # only if not installed via homebrew or macports - petsc4py # [unix] - mpi4py # [unix] # only needed if local system has mpi built and is used @@ -28,7 +28,7 @@ dependencies: - sortedcontainers - statsmodels>=0.14.5 - pip - - windIO + - windIO>=2.1.1 - wombat>=0.13.1 - pip: - dearpygui diff --git a/environment_dev.yml b/environment_dev.yml index 4f0e23b2a..b1adb99b3 100644 --- a/environment_dev.yml +++ b/environment_dev.yml @@ -4,8 +4,8 @@ channels: dependencies: # NOTE: uncomment the OS-dependent lines below as needed - # - m2w64-toolchain # [win] - # - libpython # [win] + # - gfortran # [win] + # - gcc # [win] # - gfortran # [osx] # only if not installed via homebrew or macports # - petsc4py # [unix] # - mpi4py # [unix] # only needed if local system has mpi built and is used