From 13e7dd2ba2e4b5d2b1782c1003b959860a5eab63 Mon Sep 17 00:00:00 2001 From: pibo Date: Sun, 17 Jan 2021 16:47:36 -0700 Subject: [PATCH 1/4] Squashed 'WISDEM/' changes from 3f58d337f..93ccdbd60 93ccdbd60 Merge pull request #218 from WISDEM/b/modes_again 3f16d6de2 changing tolerances 2df3c70ae going back to polyfit with more specific inputs because it is faster and expanded testing to ensure correct results e796ed8ef going back to polyfit with more specific inputs because it is faster and expanded testing to ensure correct results 5b94c8d48 Merge pull request #217 from WISDEM/b/mode_shapes 1c899fc97 switching to non-linear least squares for curve fit d502b2bbd Merge pull request #216 from nikhar-abbas/f/design_of_experiments 90de1f8cb Add design of optimization and design of experiments subsections to driver section 9bad0c0ef Include design of experiments driver options 5ff6ec931 Add design of experiments driver options 27e59c6d4 Merge pull request #214 from WISDEM/f/span_joint d19526b66 Add design of experiments example 8dc98a461 Add design of experiments d61375c7a Merge pull request #215 from WISDEM/f/floating_ghost_nodes 5968f35a6 Merge branch 'develop' into f/floating_ghost_nodes b92c334e6 Merge branch 'develop' of https://github.com/WISDEM/WISDEM into f/span_joint b6d0e2ed4 Fixed Coveralls bce69fdf3 ghost element effort complete 3c7100d74 progress on member ghost nodes 18a34ff7c first draft of ghost nodes with no mass and high stiffness ec66c4901 not required properties 9b314d489 blade spanwise joint supported f67c1529d Merge pull request #213 from WISDEM/weis_support e213ba724 Merge pull request #212 from WISDEM/consistent_modopt c5da375f2 Merge branch 'consistent_modopt' into weis_support 3d4275249 include distance_tt_hub for openfast consistency 65ef70882 fixing last syntax mishap ee9736b04 allow for backwards compatibility in modelinng options 6cf6a57d0 indenting all module options under WISDEM header for WEIS consistency b136a0d03 indenting mod options cea5451d7 additional weis bug fixes c83ec7d82 support for openfast via WEIS a86ed3c8c Merge pull request #210 from WISDEM/weis_support 902eb25db incrementing minor version 93ff3dabb unused code, but may resurrect later d066e7d6b remove version number from readme so that it doesn't fall out of date so easily d3835ba05 Merge branch 'develop' into weis_support a2ebf188d combine floating example directories 39744c801 all tests and examples should now be working 71394068b spar working 48133219b connectivity progress c9130addb redo sizing to just use a max value 236394cdb floatingse tests now working again 5ac8e7da6 fix tower tests again 12b175cea tower examples back to working f30af0fb7 update to latest LandBOSSE that uses openpyxl only. Also fix glue code and promote issues 74b01ad8b adding new dependency 95650f230 nearing testing of full module bf672226f Merge pull request #209 from WISDEM/f/test_rail a6a36edea looser tolerance 9ab913d76 update scipy f84f97559 good first draft bb5093324 Merge branch 'develop' into weis_support 2338b8d0a prepping for glue code work a33b2ff0f tes rail transport module with bar blade 2cb48cdbd putting multiple members together 696011bb9 Merge pull request #208 from johnjasa/develop 670671317 Bugfix for compare_designs 002b6e91c Merge branch 'develop' of https://github.com/WISDEM/WISDEM into develop 7222c33ca full member mostly complete with tests passing 61a4a484c incremental progress check-inn 0a49fdd01 Merge pull request #207 from WISDEM/b/theta 404d280d5 regulation_reg_III default True 9a07c9ba6 tsr to right control sub key dfeaf58bd fix connections blade twist 8fb6b469e Merge pull request #206 from WISDEM/b/twist_inverse 261818f02 check that twist opt and inverse are not both true 2f13b1462 fix input yamls 6211f1e83 uptilt, not uptilt_angle 448d00f9f import pchip 7889d4324 Merge branch 'develop' into weis_support 99b64fd54 Merge pull request #205 from WISDEM/made3d 5d24fa616 go back to old tolerance settings 9e4b3389d new values with new efficiencies and bos models 6e41b78a6 fix tests d876d15ee sync latest versions of landbosse 46e491e53 adding new orbit test files 0e6dc4180 add transformer and converter efficiencies and more tests 5f62b2312 add csv-file to output too 797bdb1fd change default buckling length 5542cb9ec upgrade to latest orbit for lots of bug fixes 017557d75 loosen tolerance on test too 9ff38e151 seeing if reducing tolerance speeds things up 06b1ef26a tests should now be passing b6f48d895 completed enhanced generator cost model and towre foundation options 691c75aec various bug fixes and improvements for made3d generator analysis 1b817f083 inncorporatting changes elsewhere 1fe53bf1b pulling in changes f4f38ea86 allow deflections for stiffness-defined BCs 7b7501a77 sync variable renaming 8d631b59f bringing in other updates 84f3a0680 Merge pull request #193 from WISDEM/develop f725fc215 forgot water depth 3f4fb38ff restarting soil work b2ded6396 Merge pull request #191 from WISDEM/develop 9c9f44461 Merge pull request #190 from WISDEM/develop 5593919bf Merge pull request #189 from WISDEM/develop e87006daa Update README.md 3f3cfad3f remove develop-branch warnings and links git-subtree-dir: WISDEM git-subtree-split: 93ccdbd603bd4fba5611d62c1e1768e8214379c3 --- .github/workflows/CI_WISDEM.yml | 2 +- README.md | 9 +- docs/index.rst | 5 +- docs/inputs/analysis_schema.rst | 8 +- docs/wisdem/floatingse/execution.rst | 4 +- docs/wisdem/floatingse/geometry.rst | 2 +- environment.yml | 30 +- .../02_reference_turbines/IEA-15-240-RWT.yaml | 2 +- .../IEA-3p4-130-RWT.yaml | 2 +- .../analysis_options.yaml | 26 +- .../modeling_options.yaml | 21 +- examples/02_reference_turbines/nrel5mw.yaml | 2 +- examples/03_blade/analysis_options_aero.yaml | 24 +- .../03_blade/analysis_options_aerostruct.yaml | 23 +- .../03_blade/analysis_options_no_opt.yaml | 24 +- .../03_blade/analysis_options_struct.yaml | 23 +- examples/03_blade/blade.yaml | 2 +- examples/03_blade/modeling_options.yaml | 21 +- .../05_tower_monopile/analysis_options.yaml | 21 +- .../analysis_options_monopile.yaml | 16 + .../05_tower_monopile/modeling_options.yaml | 65 +- examples/05_tower_monopile/monopile_direct.py | 45 +- examples/05_tower_monopile/tower_direct.py | 49 +- examples/06_drivetrain/drivetrain_direct.py | 26 +- examples/06_drivetrain/drivetrain_geared.py | 26 +- .../analysis_options.yaml | 23 +- examples/09_floating/modeling_options.yaml | 14 + examples/09_floating/mooring_opt.py | 48 +- .../nrel5mw-semi_oc4.yaml | 2 +- .../nrel5mw-semi_oc4_driver.py} | 7 +- .../nrel5mw-spar_oc3.yaml | 2 +- .../nrel5mw-spar_oc3_driver.py} | 7 +- examples/09_floating/semi_example.py | 293 ++- examples/09_floating/spar_example.py | 253 +-- examples/09_floating/spar_opt.py | 311 ++- examples/09_floating/tlp_example.py | 279 ++- .../09_weis_floating/modeling_options.yaml | 13 - .../IEA-15-240-RWT.yaml | 970 ++++++++++ .../analysis_options.yaml | 179 ++ .../13_design_of_experiments/doe_driver.py | 25 + .../modeling_options.yaml | 91 + setup.py | 18 +- wisdem/ccblade/ccblade_component.py | 24 +- wisdem/commonse/environment.py | 42 +- wisdem/commonse/fileIO.py | 1 + wisdem/commonse/turbine_constraints.py | 8 +- wisdem/commonse/utilities.py | 33 +- wisdem/commonse/vertical_cylinder.py | 31 +- wisdem/commonse/wind_wave_drag.py | 125 +- wisdem/drivetrainse/drive_components.py | 14 +- wisdem/drivetrainse/drive_structure.py | 12 +- wisdem/drivetrainse/drivetrain.py | 7 +- wisdem/drivetrainse/generator.py | 98 +- wisdem/drivetrainse/generator_models.py | 18 +- wisdem/floatingse/column.py | 1680 ---------------- wisdem/floatingse/floating.py | 237 +-- wisdem/floatingse/floating_frame.py | 677 +++++++ wisdem/floatingse/loading.py | 1723 ----------------- wisdem/floatingse/map_mooring.py | 345 ++-- wisdem/floatingse/member.py | 1539 +++++++++++++++ wisdem/floatingse/substructure.py | 704 ------- wisdem/floatingse/visualize.py | 1 - wisdem/glue_code/gc_LoadInputs.py | 311 +-- wisdem/glue_code/gc_PoseOptimization.py | 210 +- wisdem/glue_code/gc_WT_DataStruc.py | 305 ++- wisdem/glue_code/gc_WT_InitModel.py | 169 +- wisdem/glue_code/glue_code.py | 151 +- wisdem/glue_code/runWISDEM.py | 2 +- wisdem/inputs/analysis_schema.yaml | 173 +- wisdem/inputs/geometry_schema.yaml | 37 +- wisdem/inputs/modeling_schema.yaml | 549 +++--- .../landbosse_omdao/OpenMDAODataframeCache.py | 12 +- .../landbosse_omdao/XlsxValidator.py | 3 +- wisdem/landbosse/landbosse_omdao/landbosse.py | 20 +- wisdem/landbosse/model/CollectionCost.py | 4 +- wisdem/landbosse/model/DevelopmentCost.py | 5 +- wisdem/landbosse/model/ErectionCost.py | 9 +- wisdem/landbosse/model/FoundationCost.py | 8 +- wisdem/landbosse/model/GridConnectionCost.py | 5 +- wisdem/landbosse/model/ManagementCost.py | 2 +- wisdem/landbosse/model/Manager.py | 10 +- wisdem/landbosse/model/SitePreparationCost.py | 33 +- wisdem/landbosse/model/SubstationCost.py | 4 +- wisdem/orbit/_version.py | 4 +- wisdem/orbit/api/wisdem.py | 30 +- wisdem/orbit/core/_defaults.py | 65 - wisdem/orbit/core/components.py | 41 +- wisdem/orbit/core/library.py | 1 - wisdem/orbit/core/logic/__init__.py | 2 + wisdem/orbit/core/logic/vessel_logic.py | 87 +- wisdem/orbit/core/vessel.py | 19 +- wisdem/orbit/manager.py | 193 +- wisdem/orbit/phases/base.py | 37 +- wisdem/orbit/phases/design/__init__.py | 1 - wisdem/orbit/phases/design/_cables.py | 13 +- .../phases/design/array_system_design.py | 5 +- .../phases/design/export_system_design.py | 8 +- wisdem/orbit/phases/design/monopile_design.py | 69 +- .../phases/design/mooring_system_design.py | 49 +- wisdem/orbit/phases/design/oss_design.py | 65 +- .../phases/design/project_development.py | 241 --- .../phases/design/scour_protection_design.py | 34 +- .../phases/design/semi_submersible_design.py | 37 +- wisdem/orbit/phases/design/spar_design.py | 37 +- .../phases/install/cable_install/array.py | 8 +- .../phases/install/cable_install/common.py | 1 - .../phases/install/cable_install/export.py | 7 +- wisdem/orbit/phases/install/install_phase.py | 19 +- .../phases/install/monopile_install/common.py | 16 +- .../install/monopile_install/standard.py | 30 +- .../phases/install/mooring_install/mooring.py | 26 +- .../phases/install/oss_install/common.py | 7 +- .../phases/install/oss_install/standard.py | 27 +- .../quayside_assembly_tow/gravity_base.py | 8 +- .../install/quayside_assembly_tow/moored.py | 8 +- .../scour_protection_install/standard.py | 30 +- .../install/turbine_install/standard.py | 39 +- wisdem/postprocessing/compare_designs.py | 22 +- wisdem/rotorse/rail_transport.py | 26 +- wisdem/rotorse/rotor_cost.py | 9 +- wisdem/rotorse/rotor_elasticity.py | 28 +- wisdem/rotorse/rotor_power.py | 53 +- wisdem/rotorse/rotor_structure.py | 10 +- wisdem/test/test_ccblade/test_om_gradients.py | 44 +- wisdem/test/test_commonse/test_utilities.py | 11 + .../test/test_drivetrainse/test_components.py | 24 +- .../test_drivetrainse/test_drivetrainse.py | 147 +- .../test_generator_driver.py | 154 ++ .../test_generator_models.py | 24 +- wisdem/test/test_examples/test_examples.py | 5 +- wisdem/test/test_floatingse/test_column.py | 699 ------- wisdem/test/test_floatingse/test_floating.py | 261 +-- wisdem/test/test_floatingse/test_frame.py | 411 ++++ wisdem/test/test_floatingse/test_loading.py | 467 ----- .../test/test_floatingse/test_map_mooring.py | 96 +- wisdem/test/test_floatingse/test_member.py | 886 +++++++++ .../test/test_floatingse/test_substructure.py | 269 --- .../test/test_gluecode/test_gc_loadinputs.py | 8 +- wisdem/test/test_gluecode/test_gluecode.py | 24 +- wisdem/test/test_landbosse/test_landbosse.py | 31 +- wisdem/test/test_orbit/conftest.py | 3 +- .../test/test_orbit/data/library/__init__.py | 0 .../data/library/cables/__init__.py | 0 .../data/library/defaults/__init__.py | 0 .../data/library/project/__init__.py | 0 .../data/library/project/config/__init__.py | 0 .../project/config/array_cable_install.yaml | 1 + .../config/complete_floating_project.yaml | 50 + .../project/config/export_cable_install.yaml | 1 + .../project/config/floating_oss_install.yaml | 20 + .../floating_turbine_install_feeder.yaml | 24 + .../project/config/moored_install.yaml | 1 + .../config/moored_install_no_supply.yaml | 1 + .../config/mooring_system_install.yaml | 2 + .../config/multi_wtiv_mono_install.yaml | 2 + .../library/project/config/oss_install.yaml | 2 + .../oss_multi_feeder_substation_install.yaml | 2 + .../project/config/project_manager.yaml | 2 + .../config/scour_protection_install.yaml | 3 +- .../config/single_wtiv_mono_install.yaml | 2 + .../data/library/turbines/__init__.py | 0 .../data/library/vessels/__init__.py | 0 .../vessels/test_cable_lay_vessel.yaml | 1 - .../data/library/vessels/test_feeder.yaml | 5 - .../library/vessels/test_floating_barge.yaml | 14 + .../test_floating_heavy_lift_vessel.yaml | 16 + .../vessels/test_heavy_lift_vessel.yaml | 5 - .../vessels/test_phase_specific_wtiv.yaml | 7 - .../vessels/test_scour_protection_vessel.yaml | 4 - .../library/vessels/test_towing_vessel.yaml | 4 - .../data/library/vessels/test_wtiv.yaml | 7 - .../library/vessels/test_wtiv_mobilize.yaml | 7 - .../phases/design/test_array_system_design.py | 1 - .../test_orbit/phases/design/test_cable.py | 1 - .../design/test_export_system_design.py | 1 - .../design/test_mooring_system_design.py | 5 +- .../phases/design/test_oss_design.py | 7 +- .../phases/design/test_project_development.py | 47 - .../design/test_scour_protection_design.py | 12 +- .../design/test_semisubmersible_design.py | 5 +- .../phases/design/test_spar_design.py | 5 +- .../cable_install/test_array_install.py | 3 +- .../cable_install/test_export_install.py | 8 +- .../monopile_install/test_monopile_install.py | 3 +- .../monopile_install/test_monopile_tasks.py | 2 - .../mooring_install/test_mooring_install.py | 5 +- .../install/oss_install/test_oss_install.py | 25 +- .../quayside_assembly_tow/test_common.py | 6 +- .../test_gravity_based.py | 3 +- .../quayside_assembly_tow/test_moored.py | 3 +- .../test_scour_protection.py | 5 +- .../turbine_install/test_turbine_install.py | 29 +- .../test_design_install_phase_interactions.py | 128 +- .../test/test_orbit/test_project_manager.py | 87 +- wisdem/test/test_rotorse/rail.npz | Bin 0 -> 103014 bytes .../test/test_rotorse/test_rail_transport.py | 91 + wisdem/test/test_rotorse/test_rotor_power.py | 36 +- .../test/test_rotorse/test_rotor_structure.py | 33 +- wisdem/test/test_towerse/test_tower.py | 411 +++- wisdem/towerse/tower.py | 328 ++-- 200 files changed, 9435 insertions(+), 9368 deletions(-) rename examples/{09_weis_floating => 09_floating}/analysis_options.yaml (94%) create mode 100644 examples/09_floating/modeling_options.yaml rename examples/{09_weis_floating => 09_floating}/nrel5mw-semi_oc4.yaml (99%) rename examples/{09_weis_floating/wisdem_driver_oc4.py => 09_floating/nrel5mw-semi_oc4_driver.py} (95%) rename examples/{09_weis_floating => 09_floating}/nrel5mw-spar_oc3.yaml (99%) rename examples/{09_weis_floating/wisdem_driver_oc3.py => 09_floating/nrel5mw-spar_oc3_driver.py} (95%) delete mode 100644 examples/09_weis_floating/modeling_options.yaml create mode 100644 examples/13_design_of_experiments/IEA-15-240-RWT.yaml create mode 100644 examples/13_design_of_experiments/analysis_options.yaml create mode 100644 examples/13_design_of_experiments/doe_driver.py create mode 100644 examples/13_design_of_experiments/modeling_options.yaml delete mode 100644 wisdem/floatingse/column.py create mode 100644 wisdem/floatingse/floating_frame.py delete mode 100644 wisdem/floatingse/loading.py create mode 100644 wisdem/floatingse/member.py delete mode 100644 wisdem/floatingse/substructure.py delete mode 100644 wisdem/orbit/core/_defaults.py delete mode 100644 wisdem/orbit/phases/design/project_development.py create mode 100644 wisdem/test/test_drivetrainse/test_generator_driver.py delete mode 100644 wisdem/test/test_floatingse/test_column.py create mode 100644 wisdem/test/test_floatingse/test_frame.py delete mode 100644 wisdem/test/test_floatingse/test_loading.py create mode 100644 wisdem/test/test_floatingse/test_member.py delete mode 100644 wisdem/test/test_floatingse/test_substructure.py create mode 100644 wisdem/test/test_orbit/data/library/__init__.py create mode 100644 wisdem/test/test_orbit/data/library/cables/__init__.py create mode 100644 wisdem/test/test_orbit/data/library/defaults/__init__.py create mode 100644 wisdem/test/test_orbit/data/library/project/__init__.py create mode 100644 wisdem/test/test_orbit/data/library/project/config/__init__.py create mode 100644 wisdem/test/test_orbit/data/library/project/config/complete_floating_project.yaml create mode 100644 wisdem/test/test_orbit/data/library/project/config/floating_oss_install.yaml create mode 100644 wisdem/test/test_orbit/data/library/project/config/floating_turbine_install_feeder.yaml create mode 100644 wisdem/test/test_orbit/data/library/turbines/__init__.py create mode 100644 wisdem/test/test_orbit/data/library/vessels/__init__.py create mode 100644 wisdem/test/test_orbit/data/library/vessels/test_floating_barge.yaml create mode 100644 wisdem/test/test_orbit/data/library/vessels/test_floating_heavy_lift_vessel.yaml delete mode 100644 wisdem/test/test_orbit/phases/design/test_project_development.py create mode 100644 wisdem/test/test_rotorse/rail.npz create mode 100644 wisdem/test/test_rotorse/test_rail_transport.py diff --git a/.github/workflows/CI_WISDEM.yml b/.github/workflows/CI_WISDEM.yml index 0d15d24fd..3b66b2252 100644 --- a/.github/workflows/CI_WISDEM.yml +++ b/.github/workflows/CI_WISDEM.yml @@ -65,4 +65,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - coveralls + coveralls --service=github diff --git a/README.md b/README.md index 77b9e1ddd..a45f50e40 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,9 @@ The Wind-Plant Integrated System Design and Engineering Model (WISDEM®) is a Author: [NREL WISDEM Team](mailto:systems.engineering@nrel.gov) - -## Version - -This software is a version 3.0.0. - ## Documentation -See local documentation in the `docs`-directory or access the online version at +See local documentation in the `docs`-directory or access the online version at ## Packages @@ -59,7 +54,7 @@ The installation instructions below use the environment name, "wisdem-env," but conda remove --force wisdem conda install compilers # (Mac / Linux only) conda install m2w64-toolchain libpython # (Windows only) - pip install simpy marmot-agents + pip install simpy marmot-agents git clone https://github.com/WISDEM/WISDEM.git cd WISDEM git checkout develop diff --git a/docs/index.rst b/docs/index.rst index 5b7d5955d..83ca82246 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,9 +4,6 @@ WISDEM Documentation ==================== -.. warning:: - This documentation is currently under development and is only valid for the develop branch of WISDEM. There is no guarantee of applicability for any version of WISDEM. - The Wind-plant Integrated System Design and Engineering Model (WISDEM) includes integrated assemblies for the assessment of system behavior of wind turbines and plants. These assemblies can be used as is, but a richer use-case involves treating the assemblies as temples, modifying the source code and `OpenMDAO `_ problems to answer specific research questions. For example, any variable in these assemblies can be a design variable, an objective, or part of a constraint in a multidisciplinary optimization. WISDEM should therefore be viewed a toolbox of analysis tools and the basic structure for connecting tools across subsystems and fidelity levels, which can be extended in a multitude of directions according to the user’s needs. License @@ -20,7 +17,7 @@ This software is provided as-is and without warranty. There are no guarantees it Important Links --------------- -- `Source Code Repository `_ +- `Source Code Repository `_ - `OpenMDAO `_ Feedback diff --git a/docs/inputs/analysis_schema.rst b/docs/inputs/analysis_schema.rst index c24d6e499..3d00e8c91 100644 --- a/docs/inputs/analysis_schema.rst +++ b/docs/inputs/analysis_schema.rst @@ -90,7 +90,10 @@ Blade twist as a design variable by adding or subtracting radians from the initi *Default* = False :code:`inverse` : Boolean - Words TODO? + When set to True, the twist is defined inverting the + blade-element momentum equations to achieve a desired margin to stall, + which is defined among the constraints. + :code:`flag` and :code:`inverse` cannot be simultaneously be set to True *Default* = False @@ -172,7 +175,8 @@ Adjust airfoil positions along the blade span. :code:`af_start` : Integer Index of airfoil where the optimization can start shifting airfoil - position. The airfoil at blade tip is always locked. + position. The airfoil at blade tip is always locked. It is advised + to keep the airfoils close to blade root locked. *Default* = 4 diff --git a/docs/wisdem/floatingse/execution.rst b/docs/wisdem/floatingse/execution.rst index b45edf44d..d156ad4f2 100644 --- a/docs/wisdem/floatingse/execution.rst +++ b/docs/wisdem/floatingse/execution.rst @@ -10,7 +10,7 @@ environment, and the operational constraints, are required to evaluate the total mass, cost, and code compliance. These variables are also included in the `WindIO `_ effort or found in the `floating-specific examples -`_ +`_ for standalone execution. @@ -71,7 +71,7 @@ Examples -------- As mentioned previously `floating-specific examples -`_ +`_ examples are provided. These files are encoded with default starting configurations (from :cite:`OC3` and :cite:`OC4`, respectively), with some modifications. There is an additional spar example that also has diff --git a/docs/wisdem/floatingse/geometry.rst b/docs/wisdem/floatingse/geometry.rst index dabc19748..0a13b5d41 100644 --- a/docs/wisdem/floatingse/geometry.rst +++ b/docs/wisdem/floatingse/geometry.rst @@ -51,7 +51,7 @@ ones shown. Inputs: WindIO -------------- -The parameterization of the input variables in the Geometry YAML file into *FloatingSE* is documented within the larger `WindIO `_ effort. When running *FloatingSE* directly as a standalone with a python script, users are encouraged to review the `floating-specific examples `_ for syntax. +The parameterization of the input variables in the Geometry YAML file into *FloatingSE* is documented within the larger `WindIO `_ effort. When running *FloatingSE* directly as a standalone with a python script, users are encouraged to review the `floating-specific examples `_ for syntax. Tapered Cylinders (Vertical Frustums) ------------------------------------- diff --git a/environment.yml b/environment.yml index a3411a754..bc40660cf 100644 --- a/environment.yml +++ b/environment.yml @@ -5,27 +5,27 @@ channels: - defaults dependencies: - - python - - pytest - - pytest-cov - coveralls - - openmdao + - cython + - git - jsonschema - - ruamel_yaml - - pyyaml - make - - xlrd + - matplotlib + - numpy + - openmdao - openpyxl - - cython - - swig - pandas - - setuptools - - git - - numpy - - scipy - - matplotlib - - pyside2 - pip + - pyside2 + - pytest + - pytest-cov + - python + - pyyaml + - ruamel_yaml + - scipy + - setuptools + - sortedcontainers + - swig - pip: - simpy - marmot-agents diff --git a/examples/02_reference_turbines/IEA-15-240-RWT.yaml b/examples/02_reference_turbines/IEA-15-240-RWT.yaml index c77ca32ed..8dc25446c 100644 --- a/examples/02_reference_turbines/IEA-15-240-RWT.yaml +++ b/examples/02_reference_turbines/IEA-15-240-RWT.yaml @@ -347,7 +347,7 @@ components: spinner_material: glass_uni nacelle: drivetrain: - uptilt_angle: 0.10471975511965977 # 6 deg + uptilt: 0.10471975511965977 # 6 deg distance_tt_hub: 5.614 overhang: 12.0313 drag_coefficient: 0.5 diff --git a/examples/02_reference_turbines/IEA-3p4-130-RWT.yaml b/examples/02_reference_turbines/IEA-3p4-130-RWT.yaml index d7a29e6fb..70da00927 100644 --- a/examples/02_reference_turbines/IEA-3p4-130-RWT.yaml +++ b/examples/02_reference_turbines/IEA-3p4-130-RWT.yaml @@ -280,7 +280,7 @@ components: nacelle: drivetrain: diameter: 3.0 - uptilt_angle: 0.08726 # 5 deg + uptilt: 0.08726 # 5 deg distance_tt_hub: 2. distance_hub2mb: 1.912 distance_mb2mb: 0.368 diff --git a/examples/02_reference_turbines/analysis_options.yaml b/examples/02_reference_turbines/analysis_options.yaml index 5771047e6..6fc089b25 100644 --- a/examples/02_reference_turbines/analysis_options.yaml +++ b/examples/02_reference_turbines/analysis_options.yaml @@ -144,9 +144,9 @@ constraints: flag: False margin: 1.4175 rail_transport: - flag: True + flag: False 8_axle: False - 4_axle: True + 4_axle: False stall: flag: False # Constraint on minimum stall margin margin: 0.05233 # Value of minimum stall margin in [rad] @@ -216,13 +216,21 @@ constraints: flag: False driver: - tol: 1.e-2 # Optimality tolerance - max_major_iter: 10 # Maximum number of major design iterations (SNOPT) - max_minor_iter: 100 # Maximum number of minor design iterations (SNOPT) - max_iter: 100 # Maximum number of iterations (SLSQP) - solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' - step_size: 1.e-3 # Step size for finite differencing - form: central # Finite differencing mode, either forward or central + optimization: + flag: False # Flag to enable optimization + tol: 1.e-2 # Optimality tolerance + max_major_iter: 10 # Maximum number of major design iterations (SNOPT) + max_minor_iter: 100 # Maximum number of minor design iterations (SNOPT) + max_iter: 100 # Maximum number of iterations (SLSQP) + solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' + step_size: 1.e-3 # Step size for finite differencing + form: central # Finite differencing mode, either forward or central + design_of_experiments: + flag: False # Flag to enable design of experiments + run_parallel: True # Flag to run using parallel processing + generator: Uniform # Type of input generator. (Uniform) + num_samples: 5 # number of samples for (Uniform only) + recorder: flag: True # Flag to activate OpenMDAO recorder diff --git a/examples/02_reference_turbines/modeling_options.yaml b/examples/02_reference_turbines/modeling_options.yaml index 6c9bce153..c05414da9 100644 --- a/examples/02_reference_turbines/modeling_options.yaml +++ b/examples/02_reference_turbines/modeling_options.yaml @@ -1,13 +1,14 @@ # Generic modeling options file to run standard WISDEM case General: verbosity: False # When set to True, the code prints to screen many infos -RotorSE: - flag: True - spar_cap_ss: Spar_Cap_SS # Name in the yaml of the spar cap laminate on the suction side - spar_cap_ps: Spar_Cap_PS # Name in the yaml of the spar cap laminate on the suction side -DriveSE: - flag: True -TowerSE: # Options of TowerSE module - flag: True -BOS: - flag: True \ No newline at end of file +WISDEM: + RotorSE: + flag: True + spar_cap_ss: Spar_Cap_SS # Name in the yaml of the spar cap laminate on the suction side + spar_cap_ps: Spar_Cap_PS # Name in the yaml of the spar cap laminate on the suction side + DriveSE: + flag: True + TowerSE: # Options of TowerSE module + flag: True + BOS: + flag: True diff --git a/examples/02_reference_turbines/nrel5mw.yaml b/examples/02_reference_turbines/nrel5mw.yaml index 99ecc38cb..57e969f3c 100644 --- a/examples/02_reference_turbines/nrel5mw.yaml +++ b/examples/02_reference_turbines/nrel5mw.yaml @@ -196,7 +196,7 @@ components: nacelle: drivetrain: diameter: 3.0 - uptilt_angle: 0.08726 + uptilt: 0.08726 distance_tt_hub: 2.3 distance_hub2mb: 1.912 distance_mb2mb: 0.368 diff --git a/examples/03_blade/analysis_options_aero.yaml b/examples/03_blade/analysis_options_aero.yaml index f1552b941..b3c1ebbad 100644 --- a/examples/03_blade/analysis_options_aero.yaml +++ b/examples/03_blade/analysis_options_aero.yaml @@ -215,15 +215,21 @@ constraints: flag: False driver: - tol: 1.e-3 # Optimality tolerance - #max_major_iter: 10 # Maximum number of major design iterations - #max_minor_iter: 100 # Maximum number of minor design iterations - max_iter: 2 # Maximum number of minor design iterations - solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' - step_size: 1.e-3 # Step size for finite differencing - form: forward # Finite differencing mode, either forward or central - - + optimization: + flag: True # Flag to enable optimization + tol: 1.e-3 # Optimality tolerance + # max_major_iter: 10 # Maximum number of major design iterations (SNOPT) + # max_minor_iter: 100 # Maximum number of minor design iterations (SNOPT) + max_iter: 2 # Maximum number of iterations (SLSQP) + solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' + step_size: 1.e-3 # Step size for finite differencing + form: forward # Finite differencing mode, either forward or central + design_of_experiments: + flag: False # Flag to enable design of experiments + run_parallel: True # Flag to run using parallel processing + generator: Uniform # Type of input generator. (Uniform) + num_samples: 5 # number of samples for (Uniform only) + recorder: flag: False # Flag to activate OpenMDAO recorder file_name: log_opt.sql # Name of OpenMDAO recorder diff --git a/examples/03_blade/analysis_options_aerostruct.yaml b/examples/03_blade/analysis_options_aerostruct.yaml index dfebe8ea1..9b8f36723 100644 --- a/examples/03_blade/analysis_options_aerostruct.yaml +++ b/examples/03_blade/analysis_options_aerostruct.yaml @@ -215,14 +215,21 @@ constraints: flag: False driver: - tol: 1.e-5 # Optimality tolerance - #max_major_iter: 10 # Maximum number of major design iterations - #max_minor_iter: 100 # Maximum number of minor design iterations - max_iter: 5 # Maximum number of minor design iterations - solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' - step_size: 1.e-3 # Step size for finite differencing - form: forward # Finite differencing mode, either forward or central - + optimization: + flag: True # Flag to enable optimization + tol: 1.e-5 # Optimality tolerance + # max_major_iter: 10 # Maximum number of major design iterations (SNOPT) + # max_minor_iter: 100 # Maximum number of minor design iterations (SNOPT) + max_iter: 5 # Maximum number of iterations (SLSQP) + solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' + step_size: 1.e-3 # Step size for finite differencing + form: forward # Finite differencing mode, either forward or central + design_of_experiments: + flag: False # Flag to enable design of experiments + run_parallel: True # Flag to run using parallel processing + generator: Uniform # Type of input generator. (Uniform) + num_samples: 5 # number of samples for (Uniform only) + recorder: flag: False # Flag to activate OpenMDAO recorder file_name: log_opt.sql # Name of OpenMDAO recorder diff --git a/examples/03_blade/analysis_options_no_opt.yaml b/examples/03_blade/analysis_options_no_opt.yaml index 47a878b58..c246dcabe 100644 --- a/examples/03_blade/analysis_options_no_opt.yaml +++ b/examples/03_blade/analysis_options_no_opt.yaml @@ -215,15 +215,21 @@ constraints: flag: False driver: - tol: 1.e-3 # Optimality tolerance - #max_major_iter: 10 # Maximum number of major design iterations - #max_minor_iter: 100 # Maximum number of minor design iterations - max_iter: 2 # Maximum number of minor design iterations - solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' - step_size: 1.e-3 # Step size for finite differencing - form: forward # Finite differencing mode, either forward or central - - + optimization: + flag: False # Flag to enable optimization + tol: 1.e-3 # Optimality tolerance + # max_major_iter: 10 # Maximum number of major design iterations (SNOPT) + # max_minor_iter: 100 # Maximum number of minor design iterations (SNOPT) + max_iter: 2 # Maximum number of iterations (SLSQP) + solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' + step_size: 1.e-3 # Step size for finite differencing + form: forward # Finite differencing mode, either forward or central + design_of_experiments: + flag: False # Flag to enable design of experiments + run_parallel: True # Flag to run using parallel processing + generator: Uniform # Type of input generator. (Uniform) + num_samples: 5 # number of samples for (Uniform only) + recorder: flag: False # Flag to activate OpenMDAO recorder file_name: log_opt.sql # Name of OpenMDAO recorder diff --git a/examples/03_blade/analysis_options_struct.yaml b/examples/03_blade/analysis_options_struct.yaml index b69abedf8..79a114b8e 100644 --- a/examples/03_blade/analysis_options_struct.yaml +++ b/examples/03_blade/analysis_options_struct.yaml @@ -215,14 +215,21 @@ constraints: flag: False driver: - tol: 1.e-3 # Optimality tolerance - #max_major_iter: 10 # Maximum number of major design iterations - #max_minor_iter: 100 # Maximum number of minor design iterations - max_iter: 5 # Maximum number of minor design iterations - solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' - step_size: 1.e-3 # Step size for finite differencing - form: forward # Finite differencing mode, either forward or central - + optimization: + flag: True # Flag to enable optimization + tol: 1.e-3 # Optimality tolerance + # max_major_iter: 10 # Maximum number of major design iterations (SNOPT) + # max_minor_iter: 100 # Maximum number of minor design iterations (SNOPT) + max_iter: 5 # Maximum number of iterations (SLSQP) + solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' + step_size: 1.e-3 # Step size for finite differencing + form: forward # Finite differencing mode, either forward or central + design_of_experiments: + flag: False # Flag to enable design of experiments + run_parallel: True # Flag to run using parallel processing + generator: Uniform # Type of input generator. (Uniform) + num_samples: 5 # number of samples for (Uniform only) + recorder: flag: False # Flag to activate OpenMDAO recorder file_name: log_opt.sql # Name of OpenMDAO recorder diff --git a/examples/03_blade/blade.yaml b/examples/03_blade/blade.yaml index da9221490..dc8562238 100644 --- a/examples/03_blade/blade.yaml +++ b/examples/03_blade/blade.yaml @@ -338,7 +338,7 @@ components: nacelle: drivetrain: diameter: 3.0 - uptilt_angle: 0.10471975511965977 # 6 deg + uptilt: 0.10471975511965977 # 6 deg distance_tt_hub: 3.0 distance_hub2mb: 1.912 distance_mb2mb: 0.368 diff --git a/examples/03_blade/modeling_options.yaml b/examples/03_blade/modeling_options.yaml index 2b2c59b3a..7d0b7d71c 100644 --- a/examples/03_blade/modeling_options.yaml +++ b/examples/03_blade/modeling_options.yaml @@ -1,13 +1,14 @@ # Generic modeling options file to run standard WISDEM case General: verbosity: False # When set to True, the code prints to screen many infos -RotorSE: - flag: True - spar_cap_ss: Spar_cap_ss - spar_cap_ps: Spar_cap_ps -DriveSE: - flag: True -TowerSE: # Options of TowerSE module - flag: True -BOS: - flag: True \ No newline at end of file +WISDEM: + RotorSE: + flag: True + spar_cap_ss: Spar_cap_ss + spar_cap_ps: Spar_cap_ps + DriveSE: + flag: True + TowerSE: # Options of TowerSE module + flag: True + BOS: + flag: True diff --git a/examples/05_tower_monopile/analysis_options.yaml b/examples/05_tower_monopile/analysis_options.yaml index f77ff3db5..ebd38e904 100644 --- a/examples/05_tower_monopile/analysis_options.yaml +++ b/examples/05_tower_monopile/analysis_options.yaml @@ -42,12 +42,21 @@ constraints: upper_bound: 0.40 driver: - tol: 1.e-6 # Optimality tolerance - max_iter: 100 # Maximum number of iterations (SLSQP) - solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' - step_size: 1.e-6 # Step size for finite differencing - form: forward # Finite differencing mode, either forward or central - + optimization: + flag: True # Flag to enable optimization + tol: 1.e-6 # Optimality tolerance + # max_major_iter: 10 # Maximum number of major design iterations (SNOPT) + # max_minor_iter: 100 # Maximum number of minor design iterations (SNOPT) + max_iter: 100 # Maximum number of iterations (SLSQP) + solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' + step_size: 1.e-6 # Step size for finite differencing + form: forward # Finite differencing mode, either forward or central + design_of_experiments: + flag: False # Flag to enable design of experiments + run_parallel: True # Flag to run using parallel processing + generator: Uniform # Type of input generator. (Uniform) + num_samples: 5 # number of samples for (Uniform only) + recorder: flag: False # Flag to activate OpenMDAO recorder file_name: log_opt.sql # Name of OpenMDAO recorder diff --git a/examples/05_tower_monopile/analysis_options_monopile.yaml b/examples/05_tower_monopile/analysis_options_monopile.yaml index a70c0381c..84b54684d 100644 --- a/examples/05_tower_monopile/analysis_options_monopile.yaml +++ b/examples/05_tower_monopile/analysis_options_monopile.yaml @@ -82,6 +82,22 @@ driver: step_size: 1.e-6 # Step size for finite differencing form: forward # Finite differencing mode, either forward or central +driver: + optimization: + flag: True # Flag to enable optimization + tol: 1.e-3 # Optimality tolerance + max_major_iter: 10 # Maximum number of major design iterations (SNOPT) + max_minor_iter: 100 # Maximum number of minor design iterations (SNOPT) + max_iter: 100 # Maximum number of iterations (SLSQP) + solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' + step_size: 1.e-3 # Step size for finite differencing + form: forward # Finite differencing mode, either forward or central + design_of_experiments: + flag: False # Flag to enable design of experiments + run_parallel: True # Flag to run using parallel processing + generator: Uniform # Type of input generator. (Uniform) + num_samples: 5 # number of samples for (Uniform only) + recorder: flag: False # Flag to activate OpenMDAO recorder file_name: log_opt.sql # Name of OpenMDAO recorder diff --git a/examples/05_tower_monopile/modeling_options.yaml b/examples/05_tower_monopile/modeling_options.yaml index ed204991c..cf92407e6 100644 --- a/examples/05_tower_monopile/modeling_options.yaml +++ b/examples/05_tower_monopile/modeling_options.yaml @@ -1,35 +1,38 @@ # Generic modeling options file to run standard WISDEM case General: verbosity: False # When set to True, the code prints to screen many infos -RotorSE: - flag: False -DriveSE: - flag: False -TowerSE: # Options of TowerSE module - flag: True - nLC: 2 # Number of design load cases - wind: PowerWind # Wind used - gamma_f: 1.35 # Safety factor for fatigue loads - gamma_m: 1.3 # Safety factor for material properties - gamma_n: 1.0 # Safety factor for ... - gamma_b: 1.1 # Safety factor for ... - gamma_fatigue: 1.755 # Safety factor for fatigue loads - buckling_length: 30 # Buckling parameter - frame3dd: - shear: True - geom: True - tol: 1e-9 -BOS: - flag: False +WISDEM: + RotorSE: + flag: False + DriveSE: + flag: False + TowerSE: # Options of TowerSE module + flag: True + nLC: 2 # Number of design load cases + wind: PowerWind # Wind used + gamma_f: 1.35 # Safety factor for fatigue loads + gamma_m: 1.3 # Safety factor for material properties + gamma_n: 1.0 # Safety factor for ... + gamma_b: 1.1 # Safety factor for ... + gamma_fatigue: 1.755 # Safety factor for fatigue loads + buckling_length: 30 # Buckling parameter + soil_springs: True + gravity_foundation: False + frame3dd: + shear: True + geom: True + tol: 1e-9 + BOS: + flag: False -Loading: - mass: 285598.8 - center_of_mass: [-1.13197635, 0.0, 0.50875268] - moment_of_inertia: [1.14930678e08, 2.20354030e07, 1.87597425e07, 0.0, 5.03710467e05, 0.0] - loads: - - force: [1284744.19620519, 0.0, -2914124.84400512] - moment: [3963732.76208099, -2275104.79420872, -346781.68192839] - velocity: 11.73732 - - force: [930198.60063279, 0.0, -2883106.12368949] - moment: [-1683669.22411597, -2522475.34625363, 147301.97023764] - velocity: 70.0 + Loading: + mass: 285598.8 + center_of_mass: [-1.13197635, 0.0, 0.50875268] + moment_of_inertia: [1.14930678e08, 2.20354030e07, 1.87597425e07, 0.0, 5.03710467e05, 0.0] + loads: + - force: [1284744.19620519, 0.0, -2914124.84400512] + moment: [3963732.76208099, -2275104.79420872, -346781.68192839] + velocity: 11.73732 + - force: [930198.60063279, 0.0, -2883106.12368949] + moment: [-1683669.22411597, -2522475.34625363, 147301.97023764] + velocity: 70.0 diff --git a/examples/05_tower_monopile/monopile_direct.py b/examples/05_tower_monopile/monopile_direct.py index 4bc5bb2f9..80f8ab968 100644 --- a/examples/05_tower_monopile/monopile_direct.py +++ b/examples/05_tower_monopile/monopile_direct.py @@ -35,29 +35,34 @@ modeling_options = {} modeling_options["flags"] = {} modeling_options["materials"] = {} -modeling_options["TowerSE"] = {} -modeling_options["TowerSE"]["buckling_length"] = 30.0 +modeling_options["WISDEM"] = {} +modeling_options["WISDEM"]["TowerSE"] = {} +modeling_options["WISDEM"]["TowerSE"]["buckling_length"] = 30.0 modeling_options["flags"]["monopile"] = True +# Monopile foundation +modeling_options["WISDEM"]["TowerSE"]["soil_springs"] = True +modeling_options["WISDEM"]["TowerSE"]["gravity_foundation"] = False + # safety factors -modeling_options["TowerSE"]["gamma_f"] = 1.35 -modeling_options["TowerSE"]["gamma_m"] = 1.3 -modeling_options["TowerSE"]["gamma_n"] = 1.0 -modeling_options["TowerSE"]["gamma_b"] = 1.1 -modeling_options["TowerSE"]["gamma_fatigue"] = 1.35 * 1.3 * 1.0 +modeling_options["WISDEM"]["TowerSE"]["gamma_f"] = 1.35 +modeling_options["WISDEM"]["TowerSE"]["gamma_m"] = 1.3 +modeling_options["WISDEM"]["TowerSE"]["gamma_n"] = 1.0 +modeling_options["WISDEM"]["TowerSE"]["gamma_b"] = 1.1 +modeling_options["WISDEM"]["TowerSE"]["gamma_fatigue"] = 1.35 * 1.3 * 1.0 # Frame3DD options -modeling_options["TowerSE"]["frame3dd"] = {} -modeling_options["TowerSE"]["frame3dd"]["shear"] = True -modeling_options["TowerSE"]["frame3dd"]["geom"] = True -modeling_options["TowerSE"]["frame3dd"]["tol"] = 1e-9 - -modeling_options["TowerSE"]["n_height_tower"] = n_control_points -modeling_options["TowerSE"]["n_layers_tower"] = 1 -modeling_options["TowerSE"]["n_height_monopile"] = n_control_points -modeling_options["TowerSE"]["n_layers_monopile"] = 1 -modeling_options["TowerSE"]["wind"] = "PowerWind" -modeling_options["TowerSE"]["nLC"] = n_load_cases +modeling_options["WISDEM"]["TowerSE"]["frame3dd"] = {} +modeling_options["WISDEM"]["TowerSE"]["frame3dd"]["shear"] = True +modeling_options["WISDEM"]["TowerSE"]["frame3dd"]["geom"] = True +modeling_options["WISDEM"]["TowerSE"]["frame3dd"]["tol"] = 1e-9 + +modeling_options["WISDEM"]["TowerSE"]["n_height_tower"] = n_control_points +modeling_options["WISDEM"]["TowerSE"]["n_layers_tower"] = 1 +modeling_options["WISDEM"]["TowerSE"]["n_height_monopile"] = n_control_points +modeling_options["WISDEM"]["TowerSE"]["n_layers_monopile"] = 1 +modeling_options["WISDEM"]["TowerSE"]["wind"] = "PowerWind" +modeling_options["WISDEM"]["TowerSE"]["nLC"] = n_load_cases modeling_options["materials"]["n_mat"] = n_materials # --- @@ -154,7 +159,7 @@ # wind & wave values prob["wind_reference_height"] = 90.0 -prob["wind_z0"] = 0.0 +prob["z0"] = 0.0 prob["cd_usr"] = -1.0 prob["rho_air"] = 1.225 prob["rho_water"] = 1025.0 @@ -163,7 +168,7 @@ prob["beta_wind"] = 0.0 prob["Hsig_wave"] = 4.52 prob["Tsig_wave"] = 9.52 -if modeling_options["TowerSE"]["wind"] == "PowerWind": +if modeling_options["WISDEM"]["TowerSE"]["wind"] == "PowerWind": prob["shearExp"] = 0.1 # --- diff --git a/examples/05_tower_monopile/tower_direct.py b/examples/05_tower_monopile/tower_direct.py index 91613a83b..92671b6c0 100644 --- a/examples/05_tower_monopile/tower_direct.py +++ b/examples/05_tower_monopile/tower_direct.py @@ -26,29 +26,34 @@ modeling_options = {} modeling_options["flags"] = {} modeling_options["materials"] = {} -modeling_options["TowerSE"] = {} -modeling_options["TowerSE"]["buckling_length"] = 30.0 +modeling_options["WISDEM"] = {} +modeling_options["WISDEM"]["TowerSE"] = {} +modeling_options["WISDEM"]["TowerSE"]["buckling_length"] = 30.0 modeling_options["flags"]["monopile"] = False +# Monopile foundation only +modeling_options["WISDEM"]["TowerSE"]["soil_springs"] = False +modeling_options["WISDEM"]["TowerSE"]["gravity_foundation"] = False + # safety factors -modeling_options["TowerSE"]["gamma_f"] = 1.35 -modeling_options["TowerSE"]["gamma_m"] = 1.3 -modeling_options["TowerSE"]["gamma_n"] = 1.0 -modeling_options["TowerSE"]["gamma_b"] = 1.1 -modeling_options["TowerSE"]["gamma_fatigue"] = 1.35 * 1.3 * 1.0 +modeling_options["WISDEM"]["TowerSE"]["gamma_f"] = 1.35 +modeling_options["WISDEM"]["TowerSE"]["gamma_m"] = 1.3 +modeling_options["WISDEM"]["TowerSE"]["gamma_n"] = 1.0 +modeling_options["WISDEM"]["TowerSE"]["gamma_b"] = 1.1 +modeling_options["WISDEM"]["TowerSE"]["gamma_fatigue"] = 1.35 * 1.3 * 1.0 # Frame3DD options -modeling_options["TowerSE"]["frame3dd"] = {} -modeling_options["TowerSE"]["frame3dd"]["shear"] = True -modeling_options["TowerSE"]["frame3dd"]["geom"] = True -modeling_options["TowerSE"]["frame3dd"]["tol"] = 1e-9 - -modeling_options["TowerSE"]["n_height_tower"] = n_control_points -modeling_options["TowerSE"]["n_layers_tower"] = 1 -modeling_options["TowerSE"]["n_height_monopile"] = 0 -modeling_options["TowerSE"]["n_layers_monopile"] = 0 -modeling_options["TowerSE"]["wind"] = "PowerWind" -modeling_options["TowerSE"]["nLC"] = n_load_cases +modeling_options["WISDEM"]["TowerSE"]["frame3dd"] = {} +modeling_options["WISDEM"]["TowerSE"]["frame3dd"]["shear"] = True +modeling_options["WISDEM"]["TowerSE"]["frame3dd"]["geom"] = True +modeling_options["WISDEM"]["TowerSE"]["frame3dd"]["tol"] = 1e-9 + +modeling_options["WISDEM"]["TowerSE"]["n_height_tower"] = n_control_points +modeling_options["WISDEM"]["TowerSE"]["n_layers_tower"] = 1 +modeling_options["WISDEM"]["TowerSE"]["n_height_monopile"] = 0 +modeling_options["WISDEM"]["TowerSE"]["n_layers_monopile"] = 0 +modeling_options["WISDEM"]["TowerSE"]["wind"] = "PowerWind" +modeling_options["WISDEM"]["TowerSE"]["nLC"] = n_load_cases modeling_options["materials"]["n_mat"] = n_materials # --- @@ -98,10 +103,6 @@ prob["tower_outfitting_factor"] = 1.07 prob["yaw"] = 0.0 -# offshore specific -prob["G_soil"] = 140e6 -prob["nu_soil"] = 0.4 - # material properties prob["E_mat"] = 210e9 * np.ones((n_materials, 3)) prob["G_mat"] = 80.8e9 * np.ones((n_materials, 3)) @@ -127,12 +128,12 @@ # wind & wave values prob["wind_reference_height"] = 90.0 -prob["wind_z0"] = 0.0 +prob["z0"] = 0.0 prob["cd_usr"] = -1.0 prob["rho_air"] = 1.225 prob["mu_air"] = 1.7934e-5 prob["beta_wind"] = 0.0 -if modeling_options["TowerSE"]["wind"] == "PowerWind": +if modeling_options["WISDEM"]["TowerSE"]["wind"] == "PowerWind": prob["shearExp"] = 0.2 # --- diff --git a/examples/06_drivetrain/drivetrain_direct.py b/examples/06_drivetrain/drivetrain_direct.py index fcf63abcf..5dc627634 100644 --- a/examples/06_drivetrain/drivetrain_direct.py +++ b/examples/06_drivetrain/drivetrain_direct.py @@ -1,26 +1,26 @@ #!/usr/bin/env python3 # Import needed libraries -import openmdao.api as om import numpy as np - -from wisdem.drivetrainse.drivetrain import DrivetrainSE +import openmdao.api as om from wisdem.commonse.fileIO import save_data +from wisdem.drivetrainse.drivetrain import DrivetrainSE opt_flag = True # --- # Set input options opt = {} -opt["DriveSE"] = {} -opt["DriveSE"]["direct"] = True -opt["DriveSE"]["hub"] = {} -opt["DriveSE"]["hub"]["hub_gamma"] = 2.0 -opt["DriveSE"]["hub"]["spinner_gamma"] = 1.5 -opt["DriveSE"]["gamma_f"] = 1.35 -opt["DriveSE"]["gamma_m"] = 1.3 -opt["DriveSE"]["gamma_n"] = 1.0 -opt["RotorSE"] = {} -opt["RotorSE"]["n_pc"] = 20 +opt["WISDEM"] = {} +opt["WISDEM"]["DriveSE"] = {} +opt["WISDEM"]["DriveSE"]["direct"] = True +opt["WISDEM"]["DriveSE"]["hub"] = {} +opt["WISDEM"]["DriveSE"]["hub"]["hub_gamma"] = 2.0 +opt["WISDEM"]["DriveSE"]["hub"]["spinner_gamma"] = 1.5 +opt["WISDEM"]["DriveSE"]["gamma_f"] = 1.35 +opt["WISDEM"]["DriveSE"]["gamma_m"] = 1.3 +opt["WISDEM"]["DriveSE"]["gamma_n"] = 1.0 +opt["WISDEM"]["RotorSE"] = {} +opt["WISDEM"]["RotorSE"]["n_pc"] = 20 opt["materials"] = {} opt["materials"]["n_mat"] = 4 opt["flags"] = {} diff --git a/examples/06_drivetrain/drivetrain_geared.py b/examples/06_drivetrain/drivetrain_geared.py index 303bb6415..b334cf800 100644 --- a/examples/06_drivetrain/drivetrain_geared.py +++ b/examples/06_drivetrain/drivetrain_geared.py @@ -1,26 +1,26 @@ #!/usr/bin/env python3 # Import needed libraries -import openmdao.api as om import numpy as np - -from wisdem.drivetrainse.drivetrain import DrivetrainSE +import openmdao.api as om from wisdem.commonse.fileIO import save_data +from wisdem.drivetrainse.drivetrain import DrivetrainSE opt_flag = False # True # --- # Set input options opt = {} -opt["DriveSE"] = {} -opt["DriveSE"]["direct"] = False -opt["DriveSE"]["hub"] = {} -opt["DriveSE"]["hub"]["hub_gamma"] = 2.0 -opt["DriveSE"]["hub"]["spinner_gamma"] = 1.5 -opt["DriveSE"]["gamma_f"] = 1.35 -opt["DriveSE"]["gamma_m"] = 1.3 -opt["DriveSE"]["gamma_n"] = 1.0 -opt["RotorSE"] = {} -opt["RotorSE"]["n_pc"] = 20 +opt["WISDEM"] = {} +opt["WISDEM"]["DriveSE"] = {} +opt["WISDEM"]["DriveSE"]["direct"] = False +opt["WISDEM"]["DriveSE"]["hub"] = {} +opt["WISDEM"]["DriveSE"]["hub"]["hub_gamma"] = 2.0 +opt["WISDEM"]["DriveSE"]["hub"]["spinner_gamma"] = 1.5 +opt["WISDEM"]["DriveSE"]["gamma_f"] = 1.35 +opt["WISDEM"]["DriveSE"]["gamma_m"] = 1.3 +opt["WISDEM"]["DriveSE"]["gamma_n"] = 1.0 +opt["WISDEM"]["RotorSE"] = {} +opt["WISDEM"]["RotorSE"]["n_pc"] = 20 opt["materials"] = {} opt["materials"]["n_mat"] = 4 opt["flags"] = {} diff --git a/examples/09_weis_floating/analysis_options.yaml b/examples/09_floating/analysis_options.yaml similarity index 94% rename from examples/09_weis_floating/analysis_options.yaml rename to examples/09_floating/analysis_options.yaml index 49af99154..e2cbac6da 100644 --- a/examples/09_weis_floating/analysis_options.yaml +++ b/examples/09_floating/analysis_options.yaml @@ -350,14 +350,21 @@ constraints: flag: False driver: - tol: 1.e-2 # Optimality tolerance - max_major_iter: 10 # Maximum number of major design iterations (SNOPT) - max_minor_iter: 100 # Maximum number of minor design iterations (SNOPT) - max_iter: 100 # Maximum number of iterations (SLSQP) - solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' - step_size: 1.e-3 # Step size for finite differencing - form: central # Finite differencing mode, either forward or central - + optimization: + flag: False # Flag to enable optimization + tol: 1.e-2 # Optimality tolerance + max_major_iter: 10 # Maximum number of major design iterations (SNOPT) + max_minor_iter: 100 # Maximum number of minor design iterations (SNOPT) + max_iter: 100 # Maximum number of iterations (SLSQP) + solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' + step_size: 1.e-3 # Step size for finite differencing + form: central # Finite differencing mode, either forward or central + design_of_experiments: + flag: False # Flag to enable design of experiments + run_parallel: True # Flag to run using parallel processing + generator: Uniform # Type of input generator. (Uniform) + num_samples: 5 # number of samples for (Uniform only) + recorder: flag: True # Flag to activate OpenMDAO recorder file_name: log_opt.sql # Name of OpenMDAO recorder diff --git a/examples/09_floating/modeling_options.yaml b/examples/09_floating/modeling_options.yaml new file mode 100644 index 000000000..401fe8a4e --- /dev/null +++ b/examples/09_floating/modeling_options.yaml @@ -0,0 +1,14 @@ +# Generic modeling options file to run standard WISDEM case +General: + verbosity: False # When set to True, the code prints to screen many infos +WISDEM: + RotorSE: + flag: True + spar_cap_ss: Spar_Cap_SS + spar_cap_ps: Spar_Cap_PS + DriveSE: + flag: True + TowerSE: # Options of TowerSE module + flag: True + BOS: + flag: True diff --git a/examples/09_floating/mooring_opt.py b/examples/09_floating/mooring_opt.py index 3a3c94be1..4b7c1e175 100644 --- a/examples/09_floating/mooring_opt.py +++ b/examples/09_floating/mooring_opt.py @@ -11,11 +11,12 @@ # Analysis options opt = {} -opt["gamma_f"] = 1.35 +opt["n_attach"] = 3 +opt["n_anchors"] = 3 # OpenMDAO initialization prob = om.Problem() -prob.model.add_subsystem("moor", MapMooring(modeling_options=opt), promotes=["*"]) +prob.model.add_subsystem("moor", MapMooring(options=opt, gamma=1.35), promotes=["*"]) # Setup up optimization problem if opt_flag: @@ -26,15 +27,15 @@ prob.model.add_objective("mooring_cost", scaler=1e-6) # ---------------------- - prob.model.add_design_var("mooring_diameter", lower=1e-3, upper=0.35) - prob.model.add_design_var("mooring_line_length", lower=1.0, upper=10e4) + prob.model.add_design_var("line_diameter", lower=1e-3, upper=2.0) + prob.model.add_design_var("line_length", lower=1.0, upper=10e4) prob.model.add_design_var("anchor_radius", lower=1.0, upper=10e4) # --- Constraints --- # Make sure chain doesn't break during extreme events - prob.model.add_constraint("axial_unity", upper=1.0) - prob.model.add_constraint("max_offset_restoring_force", lower=1e6) # N - prob.model.add_constraint("mooring_length_max", upper=10e4) + prob.model.add_constraint("constr_axial_load", upper=1.0) + prob.model.add_constraint("max_surge_restoring_force", lower=1e6) # N + prob.model.add_constraint("constr_mooring_length", upper=1.0) # ---------------------- prob.setup() @@ -48,21 +49,19 @@ prob["fairlead_radius"] = 5.0 # m from (0,0) # Mooring design variables initial conditions -prob["mooring_diameter"] = 0.2 # m chain half-diameter -prob["anchor_radius"] = 800.0 # m -prob["mooring_line_length"] = 0.9 * np.sqrt(prob["anchor_radius"] ** 2 + prob["water_depth"] ** 2) +prob["anchor_radius"] = 900.0 # m +prob["anchor_cost"] = 1e4 # m +prob["line_length"] = 0.5 * np.sqrt(prob["anchor_radius"] ** 2 + prob["water_depth"] ** 2) +prob["line_diameter"] = 1.0 # m chain half-diameter +prob["line_mass_density_coeff"] = 19.9e3 +prob["line_stiffness_coeff"] = 8.54e10 +prob["line_breaking_load_coeff"] = 818125253.0 +prob["line_cost_rate_coeff"] = 3.415e4 # User inputs (could be design variables) -prob["number_of_mooring_connections"] = 3 -prob["mooring_lines_per_connection"] = 1 -prob["mooring_type"] = "CHAIN" -prob["anchor_type"] = "DRAGEMBEDMENT" -prob["max_offset"] = 0.1 * prob["water_depth"] # m +prob["max_surge_fraction"] = 0.1 prob["operational_heel"] = 6.0 # deg -prob["max_survival_heel"] = 10.0 # deg - -# Cost rates -prob["mooring_cost_factor"] = 1.0 +prob["survival_heel"] = 10.0 # deg # Use FD and run optimization prob.model.approx_totals() @@ -70,13 +69,14 @@ # Simple screen outputs print("Design Variables:") -print("Mooring diameter:", prob["mooring_diameter"]) -print("Mooring line length:", prob["mooring_line_length"]) +print("Mooring diameter:", prob["line_diameter"]) +print("Mooring line length:", prob["line_length"]) print("Anchor distance:", prob["anchor_radius"]) print("") print("Constraints") -print("Axial tension utilization:", prob["axial_unity"]) -print("Force at max offset:", prob["max_offset_restoring_force"]) +print("Axial tension utilization:", prob["constr_axial_load"]) +print("Line length max:", prob["constr_mooring_length"]) +print("Force at max offset:", prob["max_surge_restoring_force"]) print("Force at max heel:", prob["operational_heel_restoring_force"][:3, :]) # Plot mooring system if requested @@ -84,7 +84,7 @@ import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D - nlines = int(prob["number_of_mooring_connections"] * prob["mooring_lines_per_connection"]) + nlines = int(opt["n_anchors"]) data = prob["mooring_plot_matrix"] fig = plt.figure() ax = Axes3D(fig) diff --git a/examples/09_weis_floating/nrel5mw-semi_oc4.yaml b/examples/09_floating/nrel5mw-semi_oc4.yaml similarity index 99% rename from examples/09_weis_floating/nrel5mw-semi_oc4.yaml rename to examples/09_floating/nrel5mw-semi_oc4.yaml index b7be1443d..07d0d3a57 100644 --- a/examples/09_weis_floating/nrel5mw-semi_oc4.yaml +++ b/examples/09_floating/nrel5mw-semi_oc4.yaml @@ -197,7 +197,7 @@ components: nacelle: drivetrain: diameter: 3.0 - uptilt_angle: 0.08726 + uptilt: 0.08726 distance_tt_hub: 2.3 distance_hub2mb: 1.912 distance_mb2mb: 0.368 diff --git a/examples/09_weis_floating/wisdem_driver_oc4.py b/examples/09_floating/nrel5mw-semi_oc4_driver.py similarity index 95% rename from examples/09_weis_floating/wisdem_driver_oc4.py rename to examples/09_floating/nrel5mw-semi_oc4_driver.py index fd35b6ab9..1624a747c 100644 --- a/examples/09_weis_floating/wisdem_driver_oc4.py +++ b/examples/09_floating/nrel5mw-semi_oc4_driver.py @@ -1,6 +1,9 @@ -from wisdem.glue_code.runWISDEM import run_wisdem +import os +import sys +import time + from wisdem.commonse.mpi_tools import MPI -import os, time, sys +from wisdem.glue_code.runWISDEM import run_wisdem ## File management run_dir = os.path.dirname(os.path.realpath(__file__)) diff --git a/examples/09_weis_floating/nrel5mw-spar_oc3.yaml b/examples/09_floating/nrel5mw-spar_oc3.yaml similarity index 99% rename from examples/09_weis_floating/nrel5mw-spar_oc3.yaml rename to examples/09_floating/nrel5mw-spar_oc3.yaml index dda4e1387..3c7669ebb 100644 --- a/examples/09_weis_floating/nrel5mw-spar_oc3.yaml +++ b/examples/09_floating/nrel5mw-spar_oc3.yaml @@ -197,7 +197,7 @@ components: nacelle: drivetrain: diameter: 3.0 - uptilt_angle: 0.08726 + uptilt: 0.08726 distance_tt_hub: 2.3 distance_hub2mb: 1.912 distance_mb2mb: 0.368 diff --git a/examples/09_weis_floating/wisdem_driver_oc3.py b/examples/09_floating/nrel5mw-spar_oc3_driver.py similarity index 95% rename from examples/09_weis_floating/wisdem_driver_oc3.py rename to examples/09_floating/nrel5mw-spar_oc3_driver.py index 1cdb8c38c..265651f5f 100644 --- a/examples/09_weis_floating/wisdem_driver_oc3.py +++ b/examples/09_floating/nrel5mw-spar_oc3_driver.py @@ -1,6 +1,9 @@ -from wisdem.glue_code.runWISDEM import run_wisdem +import os +import sys +import time + from wisdem.commonse.mpi_tools import MPI -import os, time, sys +from wisdem.glue_code.runWISDEM import run_wisdem ## File management run_dir = os.path.dirname(os.path.realpath(__file__)) diff --git a/examples/09_floating/semi_example.py b/examples/09_floating/semi_example.py index 77bd7b284..2798a6092 100644 --- a/examples/09_floating/semi_example.py +++ b/examples/09_floating/semi_example.py @@ -9,190 +9,157 @@ opt_flag = False npts = 5 -nsection = npts - 1 opt = {} -opt["platform"] = {} -opt["platform"]["columns"] = {} -opt["platform"]["columns"]["main"] = {} -opt["platform"]["columns"]["offset"] = {} -opt["platform"]["columns"]["main"]["n_height"] = npts -opt["platform"]["columns"]["main"]["n_layers"] = 1 -opt["platform"]["columns"]["main"]["n_bulkhead"] = 4 -opt["platform"]["columns"]["main"]["buckling_length"] = 30.0 -opt["platform"]["columns"]["offset"]["n_height"] = npts -opt["platform"]["columns"]["offset"]["n_layers"] = 1 -opt["platform"]["columns"]["offset"]["n_bulkhead"] = 4 -opt["platform"]["columns"]["offset"]["buckling_length"] = 30.0 -opt["platform"]["tower"] = {} -opt["platform"]["tower"]["buckling_length"] = 30.0 -opt["platform"]["frame3dd"] = {} -opt["platform"]["frame3dd"]["shear"] = True -opt["platform"]["frame3dd"]["geom"] = False -opt["platform"]["frame3dd"]["dx"] = -1 -# opt['platform']['frame3dd']['nM'] = 2 -opt["platform"]["frame3dd"]["Mmethod"] = 1 -opt["platform"]["frame3dd"]["lump"] = 0 -opt["platform"]["frame3dd"]["tol"] = 1e-6 -# opt['platform']['frame3dd']['shift'] = 0.0 -opt["platform"]["gamma_f"] = 1.35 # Safety factor on loads -opt["platform"]["gamma_m"] = 1.3 # Safety factor on materials -opt["platform"]["gamma_n"] = 1.0 # Safety factor on consequence of failure -opt["platform"]["gamma_b"] = 1.1 # Safety factor on buckling -opt["platform"]["gamma_fatigue"] = 1.755 # Not used -opt["platform"]["run_modal"] = True # Not used - -opt["flags"] = {} -opt["flags"]["monopile"] = False - -opt["TowerSE"] = {} -opt["TowerSE"]["n_height_tower"] = npts -opt["TowerSE"]["n_layers_tower"] = 1 +opt["floating"] = {} +opt["WISDEM"] = {} +opt["WISDEM"]["FloatingSE"] = {} +opt["floating"]["members"] = {} +opt["floating"]["members"]["n_members"] = 7 +opt["floating"]["members"]["n_height"] = [npts, npts, npts, npts, 2, 2, 2] +opt["floating"]["members"]["n_bulkheads"] = [4, 4, 4, 4, 2, 2, 2] +opt["floating"]["members"]["n_layers"] = [1, 1, 1, 1, 1, 1, 1] +opt["floating"]["members"]["n_ballasts"] = [2, 2, 2, 2, 0, 0, 0] +opt["floating"]["members"]["n_axial_joints"] = [0, 0, 0, 0, 0, 0, 0] +opt["floating"]["tower"] = {} +opt["floating"]["tower"]["n_height"] = [npts] +opt["floating"]["tower"]["n_bulkheads"] = [0] +opt["floating"]["tower"]["n_layers"] = [1] +opt["floating"]["tower"]["n_ballasts"] = [0] +opt["floating"]["tower"]["n_axial_joints"] = [0] +opt["WISDEM"]["FloatingSE"]["frame3dd"] = {} +opt["WISDEM"]["FloatingSE"]["frame3dd"]["shear"] = True +opt["WISDEM"]["FloatingSE"]["frame3dd"]["geom"] = True +opt["WISDEM"]["FloatingSE"]["frame3dd"]["modal"] = True +opt["WISDEM"]["FloatingSE"]["frame3dd"]["tol"] = 1e-6 +opt["WISDEM"]["FloatingSE"]["gamma_f"] = 1.35 # Safety factor on loads +opt["WISDEM"]["FloatingSE"]["gamma_m"] = 1.3 # Safety factor on materials +opt["WISDEM"]["FloatingSE"]["gamma_n"] = 1.0 # Safety factor on consequence of failure +opt["WISDEM"]["FloatingSE"]["gamma_b"] = 1.1 # Safety factor on buckling +opt["WISDEM"]["FloatingSE"]["gamma_fatigue"] = 1.755 # Not used +opt["WISDEM"]["FloatingSE"]["run_modal"] = True # Not used +opt["mooring"] = {} +opt["mooring"]["n_attach"] = 3 +opt["mooring"]["n_anchors"] = 3 opt["materials"] = {} -opt["materials"]["n_mat"] = 1 +opt["materials"]["n_mat"] = 2 -# Initialize OpenMDAO problem and FloatingSE Group prob = om.Problem() prob.model = FloatingSE(modeling_options=opt) prob.setup() -# Add in offset columns and truss elements -prob["number_of_offset_columns"] = 3 -prob["cross_attachment_pontoons_int"] = 1 # Lower-Upper main-to-offset connecting cross braces -prob["lower_attachment_pontoons_int"] = 1 # Lower main-to-offset connecting pontoons -prob["upper_attachment_pontoons_int"] = 1 # Upper main-to-offset connecting pontoons -prob["lower_ring_pontoons_int"] = 1 # Lower ring of pontoons connecting offset columns -prob["upper_ring_pontoons_int"] = 1 # Upper ring of pontoons connecting offset columns -prob["outer_cross_pontoons_int"] = 1 # Auxiliary ring connecting V-cross braces - -# Set environment to that used in OC4 testing campaign -prob["water_depth"] = 200.0 # Distance to sea floor [m] -prob["hsig_wave"] = 10.8 # Significant wave height [m] -prob["Tsig_wave"] = 9.8 # Wave period [s] -prob["Uref"] = 11.0 # Wind reference speed [m/s] -prob["zref"] = 119.0 # Wind reference height [m] - -# Column geometry -prob["main.permanent_ballast_height"] = 10.0 # Height above keel for permanent ballast [m] -prob["main_freeboard"] = 10.0 # Height extension above waterline [m] -prob["main.height"] = np.sum([10.0, 5.0, 5.0, 10.0]) -prob["main.s"] = np.cumsum([0.0, 10.0, 5.0, 5.0, 10.0]) / prob["main.height"] -prob["main.outer_diameter_in"] = 6.5 * np.ones(5) -prob["main.layer_thickness"] = 0.03 * np.ones((1, nsection)) -prob["main.bulkhead_thickness"] = 0.05 * np.ones(4) -prob["main.bulkhead_locations"] = np.array([0.0, 0.25, 0.9, 1.0]) - -# Auxiliary column geometry -prob["radius_to_offset_column"] = 33.333 * np.cos(np.pi / 6) -prob["off.permanent_ballast_height"] = 0.1 -prob["offset_freeboard"] = 12.0 -prob["off.height"] = np.sum([6.0, 0.1, 15.9, 10]) -prob["off.s"] = np.cumsum([0.0, 6.0, 0.1, 15.9, 10]) / prob["off.height"] -prob["off.outer_diameter_in"] = np.array([24, 24, 12, 12, 12]) -prob["off.layer_thickness"] = 0.06 * np.ones((1, nsection)) - -# Column ring stiffener parameters -prob["main.stiffener_web_height"] = 0.10 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_web_thickness"] = 0.04 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_flange_width"] = 0.10 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_flange_thickness"] = 0.02 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_spacing"] = 0.40 * np.ones(nsection) # (by section) [m] - -# Auxiliary column ring stiffener parameters -prob["off.stiffener_web_height"] = 0.10 * np.ones(nsection) # (by section) [m] -prob["off.stiffener_web_thickness"] = 0.04 * np.ones(nsection) # (by section) [m] -prob["off.stiffener_flange_width"] = 0.01 * np.ones(nsection) # (by section) [m] -prob["off.stiffener_flange_thickness"] = 0.02 * np.ones(nsection) # (by section) [m] -prob["off.stiffener_spacing"] = 0.40 * np.ones(nsection) # (by section) [m] - -# Pontoon parameters -prob["pontoon_outer_diameter"] = 3.2 # Diameter of all pontoon/truss elements [m] -prob["pontoon_wall_thickness"] = 0.0175 # Thickness of all pontoon/truss elements [m] -prob["main_pontoon_attach_lower"] = -20.0 # Lower z-coordinate on main where truss attaches [m] -prob["main_pontoon_attach_upper"] = 10.0 # Upper z-coordinate on main where truss attaches [m] - -# Mooring parameters -prob["mooring_diameter"] = 0.0766 # Diameter of mooring line/chain [m] -prob["fairlead"] = 14.0 # Distance below waterline for attachment [m] -prob["fairlead_offset_from_shell"] = 0.5 # Offset from shell surface for mooring attachment [m] -prob["mooring_line_length"] = 835.5 + 300 # Unstretched mooring line length -prob["anchor_radius"] = 837.6 + 300.0 # Distance from centerline to sea floor landing [m] -prob["fairlead_support_outer_diameter"] = 3.2 # Diameter of all fairlead support elements [m] -prob["fairlead_support_wall_thickness"] = 0.0175 # Thickness of all fairlead support elements [m] - -# Mooring parameters -prob["number_of_mooring_connections"] = 3 # Evenly spaced around structure -prob["mooring_lines_per_connection"] = 1 # Evenly spaced around structure -prob["mooring_type"] = "chain" # Options are chain, nylon, polyester, fiber, or iwrc -prob["anchor_type"] = "DRAGEMBEDMENT" # Options are SUCTIONPILE or DRAGEMBEDMENT - - -### Variables common to these spar, semi, TLP examples ### -# Set environment to that used in OC4 testing campaign -prob["shearExp"] = 0.11 # Shear exponent in wind power law -prob["cm"] = 2.0 # Added mass coefficient -prob["Uc"] = 0.0 # Mean current speed -prob["main.wind.z0"] = 0.0 # Water line -prob["yaw"] = 0.0 # Turbine yaw angle -prob["beta_wind"] = prob["beta_wave"] = 0.0 # Wind/water beta angle -prob["cd_usr"] = -1.0 # Compute drag coefficient - -# Wind and water properties -prob["rho_air"] = 1.226 # Density of air [kg/m^3] -prob["mu_air"] = 1.78e-5 # Viscosity of air [kg/m/s] -prob["rho_water"] = 1025.0 # Density of water [kg/m^3] -prob["mu_water"] = 1.08e-3 # Viscosity of water [kg/m/s] - # Material properties -prob["rho_mat"] = np.array([7850.0]) # Steel [kg/m^3] -prob["E_mat"] = 200e9 * np.ones((1, 3)) # Young's modulus [N/m^2] -prob["G_mat"] = 79.3e9 * np.ones((1, 3)) # Shear modulus [N/m^2] -prob["sigma_y_mat"] = np.array([3.45e8]) # Elastic yield stress [N/m^2] -prob["permanent_ballast_density"] = 4492.0 # [kg/m^3] +prob["rho_mat"] = np.array([7850.0, 5000.0]) # Steel, ballast slurry [kg/m^3] +prob["E_mat"] = 200e9 * np.ones((2, 3)) # Young's modulus [N/m^2] +prob["G_mat"] = 79.3e9 * np.ones((2, 3)) # Shear modulus [N/m^2] +prob["sigma_y_mat"] = 3.45e8 * np.ones(2) # Elastic yield stress [N/m^2] +prob["unit_cost_mat"] = np.array([2.0, 1.0]) +prob["material_names"] = ["steel", "slurry"] # Mass and cost scaling factors -prob["outfitting_factor"] = 0.06 # Fraction of additional outfitting mass for each column -prob["ballast_cost_rate"] = 0.1 # Cost factor for ballast mass [$/kg] -prob["unit_cost_mat"] = np.array([1.1]) # Cost factor for column mass [$/kg] prob["labor_cost_rate"] = 1.0 # Cost factor for labor time [$/min] prob["painting_cost_rate"] = 14.4 # Cost factor for column surface finishing [$/m^2] -prob["outfitting_cost_rate"] = 1.5 * 1.1 # Cost factor for outfitting mass [$/kg] -prob["mooring_cost_factor"] = 1.1 # Cost factor for mooring mass [$/kg] -# Mooring parameters -prob["number_of_mooring_connections"] = 3 # Evenly spaced around structure -prob["mooring_lines_per_connection"] = 1 # Evenly spaced around structure +# Main geometry +h = np.array([10.0, 5.0, 5.0, 10.0]) +prob["member0.outfitting_factor_in"] = 1.05 # Fraction of additional outfitting mass for each column +prob["member0.grid_axial_joints"] = [] +prob["member0.ballast_grid"] = np.array([[0, 0.25], [0, 0.5]]) +prob["member0.ballast_volume"] = [np.pi * 3.25 ** 2 * 10, 0.0] +prob["member0.s"] = np.cumsum(np.r_[0, h]) / h.sum() +prob["member0.outer_diameter_in"] = 6.5 * np.ones(npts) +prob["member0.layer_thickness"] = 0.03 * np.ones((1, npts)) +prob["member0.layer_materials"] = ["steel"] +prob["member0.ballast_materials"] = ["slurry", "seawater"] +prob["member0.joint1"] = np.array([0.0, 0.0, 10.0 - h.sum()]) +prob["member0.joint2"] = np.array([0.0, 0.0, 10.0]) # Freeboard=10 +prob["member0.transition_flag"] = [False, True] +prob["member0.bulkhead_thickness"] = 0.05 * np.ones(4) # Locations of internal bulkheads +prob["member0.bulkhead_grid"] = np.array([0.0, 0.25, 0.5, 1.0]) + +# Offset columns +angs = np.linspace(0, 2 * np.pi, 1 + opt["mooring"]["n_attach"]) +r = 33.333 * np.cos(np.pi / 6) +h = np.array([6.0, 0.1, 15.9, 10]) +for k in range(1, 4): + xc, yc = r * np.cos(angs[k - 1]), r * np.sin(angs[k - 1]) + prob["member" + str(k) + ".outfitting_factor_in"] = 1.05 # Fraction of additional outfitting mass for each column + prob["member" + str(k) + ".grid_axial_joints"] = [] + prob["member" + str(k) + ".s"] = np.cumsum(np.r_[0, h]) / h.sum() + prob["member" + str(k) + ".outer_diameter_in"] = np.array([24, 24, 12, 12, 12]) + prob["member" + str(k) + ".layer_thickness"] = 0.06 * np.ones((1, npts)) + prob["member" + str(k) + ".layer_materials"] = ["steel"] + prob["member" + str(k) + ".ballast_grid"] = np.array([[0, 0.25], [0, 0.5]]) + prob["member" + str(k) + ".ballast_volume"] = [np.pi * 12 ** 2 * 0.1, 0.0] + prob["member" + str(k) + ".ballast_materials"] = ["slurry", "seawater"] + prob["member" + str(k) + ".joint1"] = np.array([xc, yc, 12.0 - h.sum()]) + prob["member" + str(k) + ".joint2"] = np.array([xc, yc, 12.0]) # Freeboard=10 + prob["member" + str(k) + ".transition_flag"] = [False, False] + prob["member" + str(k) + ".bulkhead_thickness"] = 0.05 * np.ones(4) # Locations of internal bulkheads + prob["member" + str(k) + ".bulkhead_grid"] = np.array([0.0, 0.25, 0.5, 1.0]) + +# Pontoons: Y pattern on bottom +for k in range(4, 7): + xc, yc = r * np.cos(angs[k - 4]), r * np.sin(angs[k - 4]) + prob["member" + str(k) + ".s"] = np.array([0.0, 1.0]) + prob["member" + str(k) + ".outer_diameter_in"] = 3.2 * np.ones(2) + prob["member" + str(k) + ".layer_thickness"] = 0.02 * np.ones((1, 2)) + prob["member" + str(k) + ".layer_materials"] = ["steel"] + prob["member" + str(k) + ".joint1"] = np.array([xc, yc, 12.0 - h.sum()]) + prob["member" + str(k) + ".joint2"] = np.array([0, 0, 12.0 - h.sum()]) + prob["member" + str(k) + ".transition_flag"] = [False, False] + prob["member" + str(k) + ".bulkhead_thickness"] = 0.02 * np.ones(2) # Locations of internal bulkheads + prob["member" + str(k) + ".bulkhead_grid"] = np.array([0.0, 1.0]) + +# Mooring parameters: Chain +prob["line_diameter"] = 0.0766 # Diameter of mooring line/chain [m] +prob["line_length"] = 835.5 + 300 # Unstretched mooring line length +prob["line_mass_density_coeff"] = 19.9e3 +prob["line_stiffness_coeff"] = 8.54e10 +prob["line_breaking_load_coeff"] = 818125253.0 +prob["line_cost_rate_coeff"] = 3.415e4 +prob["fairlead_radius"] = r + 6 +prob["fairlead"] = 14.0 +prob["anchor_radius"] = 837.6 + 300.0 +prob["anchor_cost"] = 1e5 -# Porperties of turbine tower -nTower = prob.model.options["modeling_options"]["TowerSE"]["n_height_tower"] - 1 -prob["tower_height"] = prob["hub_height"] = 77.6 -prob["tower_s"] = np.linspace(0.0, 1.0, nTower + 1) -prob["tower_outer_diameter_in"] = np.linspace(8.0, 3.87, nTower + 1) -prob["tower_layer_thickness"] = np.linspace(0.04, 0.02, nTower + 1).reshape((1, nTower + 1)) -prob["tower_outfitting_factor"] = 1.07 +# Mooring constraints +prob["max_surge_fraction"] = 0.1 # Max surge/sway offset [m] +prob["survival_heel"] = 10.0 # Max heel (pitching) angle [deg] +prob["operational_heel"] = 5.0 # Max heel (pitching) angle [deg] + +# Set environment to that used in OC3 testing campaign +# prob["rho_air"] = 1.226 # Density of air [kg/m^3] +# prob["mu_air"] = 1.78e-5 # Viscosity of air [kg/m/s] +prob["rho_water"] = 1025.0 # Density of water [kg/m^3] +# prob["mu_water"] = 1.08e-3 # Viscosity of water [kg/m/s] +prob["water_depth"] = 320.0 # Distance to sea floor [m] +# prob["Hsig_wave"] = 10.8 # Significant wave height [m] +# prob["Tsig_wave"] = 9.8 # Wave period [s] +# prob["shearExp"] = 0.11 # Shear exponent in wind power law +# prob["cm"] = 2.0 # Added mass coefficient +# prob["Uc"] = 0.0 # Mean current speed +# prob["yaw"] = 0.0 # Turbine yaw angle +# prob["beta_wind"] = prob["beta_wave"] = 0.0 +# prob["cd_usr"] = -1.0 # Compute drag coefficient +# prob["Uref"] = 11.0 +# prob["zref"] = 119.0 -# Materials -prob["material_names"] = ["steel"] -prob["main.layer_materials"] = prob["off.layer_materials"] = prob["tow.tower_layer_materials"] = ["steel"] +# Porperties of turbine tower +nTower = prob.model.options["modeling_options"]["floating"]["tower"]["n_height"][0] +prob["hub_height"] = 85.0 +prob["tower.s"] = np.linspace(0.0, 1.0, nTower) +prob["tower.outer_diameter_in"] = np.linspace(6.5, 3.87, nTower) +prob["tower.layer_thickness"] = np.linspace(0.027, 0.019, nTower).reshape((1, nTower)) +prob["tower.layer_materials"] = ["steel"] +prob["tower.outfitting_factor"] = 1.07 # Properties of rotor-nacelle-assembly (RNA) prob["rna_mass"] = 350e3 prob["rna_I"] = 1e5 * np.array([1149.307, 220.354, 187.597, 0, 5.037, 0]) prob["rna_cg"] = np.array([-1.132, 0, 0.509]) -prob["rna_force"] = np.array([1284744.196, 0, -112400.5527]) -prob["rna_moment"] = np.array([3963732.762, 896380.8464, -346781.682]) - -# Mooring constraints -prob["max_draft"] = 150.0 # Max surge/sway offset [m] -prob["max_offset"] = 100.0 # Max surge/sway offset [m] -prob["operational_heel"] = 10.0 # Max heel (pitching) angle [deg] - -# Design constraints -prob["connection_ratio_max"] = 0.25 # For welding pontoons to columns - -# API 2U flag -prob["loading"] = "hydrostatic" - +prob["rna_F"] = np.array([1284744.196, 0, -112400.5527]) +prob["rna_M"] = np.array([3963732.762, 896380.8464, -346781.682]) # Use FD and run optimization prob.run_model() diff --git a/examples/09_floating/spar_example.py b/examples/09_floating/spar_example.py index 9cd3b206b..872e044ea 100644 --- a/examples/09_floating/spar_example.py +++ b/examples/09_floating/spar_example.py @@ -6,181 +6,130 @@ from wisdem.floatingse import FloatingSE plot_flag = False # True -opt_flag = False npts = 5 -nsection = npts - 1 opt = {} -opt["platform"] = {} -opt["platform"]["columns"] = {} -opt["platform"]["columns"]["main"] = {} -opt["platform"]["columns"]["offset"] = {} -opt["platform"]["columns"]["main"]["n_height"] = npts -opt["platform"]["columns"]["main"]["n_layers"] = 1 -opt["platform"]["columns"]["main"]["n_bulkhead"] = 4 -opt["platform"]["columns"]["main"]["buckling_length"] = 30.0 -opt["platform"]["columns"]["offset"]["n_height"] = npts -opt["platform"]["columns"]["offset"]["n_layers"] = 1 -opt["platform"]["columns"]["offset"]["n_bulkhead"] = 4 -opt["platform"]["columns"]["offset"]["buckling_length"] = 30.0 -opt["platform"]["tower"] = {} -opt["platform"]["tower"]["buckling_length"] = 30.0 -opt["platform"]["frame3dd"] = {} -opt["platform"]["frame3dd"]["shear"] = True -opt["platform"]["frame3dd"]["geom"] = False -opt["platform"]["frame3dd"]["dx"] = -1 -# opt['platform']['frame3dd']['nM'] = 2 -opt["platform"]["frame3dd"]["Mmethod"] = 1 -opt["platform"]["frame3dd"]["lump"] = 0 -opt["platform"]["frame3dd"]["tol"] = 1e-6 -# opt['platform']['frame3dd']['shift'] = 0.0 -opt["platform"]["gamma_f"] = 1.35 # Safety factor on loads -opt["platform"]["gamma_m"] = 1.3 # Safety factor on materials -opt["platform"]["gamma_n"] = 1.0 # Safety factor on consequence of failure -opt["platform"]["gamma_b"] = 1.1 # Safety factor on buckling -opt["platform"]["gamma_fatigue"] = 1.755 # Not used -opt["platform"]["run_modal"] = True # Not used - -opt["flags"] = {} -opt["flags"]["monopile"] = False - -opt["TowerSE"] = {} -opt["TowerSE"]["n_height_tower"] = npts -opt["TowerSE"]["n_layers_tower"] = 1 +opt["floating"] = {} +opt["WISDEM"] = {} +opt["WISDEM"]["FloatingSE"] = {} +opt["floating"]["members"] = {} +opt["floating"]["members"]["n_members"] = 1 +opt["floating"]["members"]["n_height"] = [npts] +opt["floating"]["members"]["n_bulkheads"] = [4] +opt["floating"]["members"]["n_layers"] = [1] +opt["floating"]["members"]["n_ballasts"] = [2] +opt["floating"]["members"]["n_axial_joints"] = [1] +opt["floating"]["tower"] = {} +opt["floating"]["tower"]["n_height"] = [npts] +opt["floating"]["tower"]["n_bulkheads"] = [0] +opt["floating"]["tower"]["n_layers"] = [1] +opt["floating"]["tower"]["n_ballasts"] = [0] +opt["floating"]["tower"]["n_axial_joints"] = [0] +opt["WISDEM"]["FloatingSE"]["frame3dd"] = {} +opt["WISDEM"]["FloatingSE"]["frame3dd"]["shear"] = True +opt["WISDEM"]["FloatingSE"]["frame3dd"]["geom"] = True +opt["WISDEM"]["FloatingSE"]["frame3dd"]["modal"] = True +opt["WISDEM"]["FloatingSE"]["frame3dd"]["tol"] = 1e-6 +opt["WISDEM"]["FloatingSE"]["gamma_f"] = 1.35 # Safety factor on loads +opt["WISDEM"]["FloatingSE"]["gamma_m"] = 1.3 # Safety factor on materials +opt["WISDEM"]["FloatingSE"]["gamma_n"] = 1.0 # Safety factor on consequence of failure +opt["WISDEM"]["FloatingSE"]["gamma_b"] = 1.1 # Safety factor on buckling +opt["WISDEM"]["FloatingSE"]["gamma_fatigue"] = 1.755 # Not used +opt["WISDEM"]["FloatingSE"]["run_modal"] = True # Not used +opt["mooring"] = {} +opt["mooring"]["n_attach"] = 3 +opt["mooring"]["n_anchors"] = 3 opt["materials"] = {} -opt["materials"]["n_mat"] = 1 +opt["materials"]["n_mat"] = 2 -# Initialize OpenMDAO problem and FloatingSE Group prob = om.Problem() prob.model = FloatingSE(modeling_options=opt) prob.setup() -# Remove all offset columns -prob["number_of_offset_columns"] = 0 -prob["cross_attachment_pontoons_int"] = 0 -prob["lower_attachment_pontoons_int"] = 0 -prob["upper_attachment_pontoons_int"] = 0 -prob["lower_ring_pontoons_int"] = 0 -prob["upper_ring_pontoons_int"] = 0 -prob["outer_cross_pontoons_int"] = 0 - -# Set environment to that used in OC3 testing campaign -prob["water_depth"] = 320.0 # Distance to sea floor [m] -prob["hsig_wave"] = 10.8 # Significant wave height [m] -prob["Tsig_wave"] = 9.8 # Wave period [s] -prob["Uref"] = 11.0 # Wind reference speed [m/s] -prob["zref"] = 119.0 # Wind reference height [m] - -# Column geometry -prob["main.permanent_ballast_height"] = 10.0 # Height above keel for permanent ballast [m] -prob["main_freeboard"] = 10.0 # Height extension above waterline [m] -prob["main.height"] = np.sum([49.0, 59.0, 8.0, 14.0]) -prob["main.s"] = np.cumsum([0.0, 49.0, 59.0, 8.0, 14.0]) / prob["main.height"] -prob["main.outer_diameter_in"] = np.array([9.4, 9.4, 9.4, 6.5, 6.5]) -prob["main.layer_thickness"] = 0.05 * np.ones((1, nsection)) -prob["main.bulkhead_thickness"] = 0.05 * np.ones(4) -prob["main.bulkhead_locations"] = np.array([0.0, 0.25, 0.9, 1.0]) - -# Column ring stiffener parameters -prob["main.stiffener_web_height"] = 0.10 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_web_thickness"] = 0.04 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_flange_width"] = 0.10 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_flange_thickness"] = 0.02 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_spacing"] = 0.40 * np.ones(nsection) # (by section) [m] - -# Mooring parameters -prob["mooring_diameter"] = 0.09 # Diameter of mooring line/chain [m] -prob["fairlead"] = 70.0 # Distance below waterline for attachment [m] -prob["fairlead_offset_from_shell"] = 0.5 # Offset from shell surface for mooring attachment [m] -prob["mooring_line_length"] = 902.2 # Unstretched mooring line length -prob["anchor_radius"] = 853.87 # Distance from centerline to sea floor landing [m] -prob["fairlead_support_outer_diameter"] = 3.2 # Diameter of all fairlead support elements [m] -prob["fairlead_support_wall_thickness"] = 0.0175 # Thickness of all fairlead support elements [m] - -# Other variables to avoid divide by zeros, even though it won't matter -prob["radius_to_offset_column"] = 15.0 -prob["offset_freeboard"] = 0.1 -prob["off.height"] = 1.0 -prob["off.s"] = np.linspace(0, 1, nsection + 1) -prob["off.outer_diameter_in"] = 5.0 * np.ones(nsection + 1) -prob["off.layer_thickness"] = 0.1 * np.ones((1, nsection)) -prob["off.permanent_ballast_height"] = 0.1 -prob["off.stiffener_web_height"] = 0.1 * np.ones(nsection) -prob["off.stiffener_web_thickness"] = 0.1 * np.ones(nsection) -prob["off.stiffener_flange_width"] = 0.1 * np.ones(nsection) -prob["off.stiffener_flange_thickness"] = 0.1 * np.ones(nsection) -prob["off.stiffener_spacing"] = 0.1 * np.ones(nsection) -prob["pontoon_outer_diameter"] = 1.0 -prob["pontoon_wall_thickness"] = 0.1 - - -### Variables common to these spar, semi, TLP examples ### -# Set environment to that used in OC4 testing campaign -prob["shearExp"] = 0.11 # Shear exponent in wind power law -prob["cm"] = 2.0 # Added mass coefficient -prob["Uc"] = 0.0 # Mean current speed -prob["main.wind_z0"] = 0.0 # Water line -prob["yaw"] = 0.0 # Turbine yaw angle -prob["beta_wind"] = prob["beta_wave"] = 0.0 # Wind/water beta angle -prob["cd_usr"] = -1.0 # Compute drag coefficient - -# Wind and water properties -prob["rho_air"] = 1.226 # Density of air [kg/m^3] -prob["mu_air"] = 1.78e-5 # Viscosity of air [kg/m/s] -prob["rho_water"] = 1025.0 # Density of water [kg/m^3] -prob["mu_water"] = 1.08e-3 # Viscosity of water [kg/m/s] - # Material properties -prob["rho_mat"] = np.array([7850.0]) # Steel [kg/m^3] -prob["E_mat"] = 200e9 * np.ones((1, 3)) # Young's modulus [N/m^2] -prob["G_mat"] = 79.3e9 * np.ones((1, 3)) # Shear modulus [N/m^2] -prob["sigma_y_mat"] = np.array([3.45e8]) # Elastic yield stress [N/m^2] -prob["permanent_ballast_density"] = 4492.0 # [kg/m^3] +prob["rho_mat"] = np.array([7850.0, 5000.0]) # Steel, ballast slurry [kg/m^3] +prob["E_mat"] = 200e9 * np.ones((2, 3)) # Young's modulus [N/m^2] +prob["G_mat"] = 79.3e9 * np.ones((2, 3)) # Shear modulus [N/m^2] +prob["sigma_y_mat"] = 3.45e8 * np.ones(2) # Elastic yield stress [N/m^2] +prob["unit_cost_mat"] = np.array([2.0, 1.0]) +prob["material_names"] = ["steel", "slurry"] # Mass and cost scaling factors -prob["outfitting_factor"] = 0.06 # Fraction of additional outfitting mass for each column -prob["ballast_cost_rate"] = 0.1 # Cost factor for ballast mass [$/kg] -prob["unit_cost_mat"] = np.array([1.1]) # Cost factor for column mass [$/kg] prob["labor_cost_rate"] = 1.0 # Cost factor for labor time [$/min] prob["painting_cost_rate"] = 14.4 # Cost factor for column surface finishing [$/m^2] -prob["outfitting_cost_rate"] = 1.5 * 1.1 # Cost factor for outfitting mass [$/kg] -prob["mooring_cost_factor"] = 1.1 # Cost factor for mooring mass [$/kg] -# Mooring parameters -prob["number_of_mooring_connections"] = 3 # Evenly spaced around structure -prob["mooring_lines_per_connection"] = 1 # Evenly spaced around structure +# Column geometry +h = np.array([49.0, 59.0, 8.0, 14.0]) # Length of each section [m] +prob["member0.outfitting_factor_in"] = 1.05 # Fraction of additional outfitting mass for each column +prob["member0.grid_axial_joints"] = [0.384615] # Fairlead at 70m +prob["member0.ballast_grid"] = np.array([[0, 0.37692308], [0, 0.89230769]]) +prob["member0.ballast_volume"] = [np.pi * 4.7 ** 2 * 10, 0.0] +prob["member0.s"] = np.cumsum(np.r_[0, h]) / h.sum() +prob["member0.outer_diameter_in"] = np.array([9.4, 9.4, 9.4, 6.5, 6.5]) +prob["member0.layer_thickness"] = 0.05 * np.ones((1, npts)) +prob["member0.layer_materials"] = ["steel"] +prob["member0.ballast_materials"] = ["slurry", "seawater"] +prob["member0.joint1"] = np.array([0.0, 0.0, 10.0 - h.sum()]) +prob["member0.joint2"] = np.array([0.0, 0.0, 10.0]) # Freeboard=10 +prob["member0.transition_flag"] = [False, True] +prob["member0.bulkhead_thickness"] = 0.05 * np.ones(4) +prob["member0.bulkhead_grid"] = np.array([0.0, 0.37692308, 0.89230769, 1.0]) +prob["member0.ring_stiffener_web_height"] = 0.10 +prob["member0.ring_stiffener_web_thickness"] = 0.04 +prob["member0.ring_stiffener_flange_width"] = 0.10 +prob["member0.ring_stiffener_flange_thickness"] = 0.02 +prob["member0.ring_stiffener_spacing"] = 2.15 + +# Mooring parameters: Chain +prob["line_diameter"] = 0.09 # Diameter of mooring line/chain [m] +prob["line_length"] = 300 + 902.2 # Unstretched mooring line length +prob["line_mass_density_coeff"] = 19.9e3 +prob["line_stiffness_coeff"] = 8.54e10 +prob["line_breaking_load_coeff"] = 818125253.0 +prob["line_cost_rate_coeff"] = 3.415e4 +prob["fairlead_radius"] = 10.0 +prob["fairlead"] = 70.0 +prob["anchor_radius"] = 853.87 +prob["anchor_cost"] = 1e5 -# Porperties of turbine tower -nTower = prob.model.options["modeling_options"]["TowerSE"]["n_height_tower"] - 1 -prob["tower_height"] = prob["hub_height"] = 77.6 -prob["tower_s"] = np.linspace(0.0, 1.0, nTower + 1) -prob["tower_outer_diameter_in"] = np.linspace(8.0, 3.87, nTower + 1) -prob["tower_layer_thickness"] = np.linspace(0.04, 0.02, nTower + 1).reshape((1, nTower + 1)) -prob["tower_outfitting_factor"] = 1.07 +# Mooring constraints +prob["max_surge_fraction"] = 0.1 # Max surge/sway offset [m] +prob["survival_heel"] = 10.0 # Max heel (pitching) angle [deg] +prob["operational_heel"] = 5.0 # Max heel (pitching) angle [deg] -# Materials -prob["material_names"] = ["steel"] -prob["main.layer_materials"] = prob["off.layer_materials"] = prob["tow.tower_layer_materials"] = ["steel"] +# Set environment to that used in OC3 testing campaign +# prob["rho_air"] = 1.226 # Density of air [kg/m^3] +# prob["mu_air"] = 1.78e-5 # Viscosity of air [kg/m/s] +prob["rho_water"] = 1025.0 # Density of water [kg/m^3] +# prob["mu_water"] = 1.08e-3 # Viscosity of water [kg/m/s] +prob["water_depth"] = 320.0 # Distance to sea floor [m] +# prob["Hsig_wave"] = 10.8 # Significant wave height [m] +# prob["Tsig_wave"] = 9.8 # Wave period [s] +# prob["shearExp"] = 0.11 # Shear exponent in wind power law +# prob["cm"] = 2.0 # Added mass coefficient +# prob["Uc"] = 0.0 # Mean current speed +# prob["yaw"] = 0.0 # Turbine yaw angle +# prob["beta_wind"] = prob["beta_wave"] = 0.0 +# prob["cd_usr"] = -1.0 # Compute drag coefficient +# prob["Uref"] = 11.0 +# prob["zref"] = 119.0 + +# Porperties of turbine tower +nTower = prob.model.options["modeling_options"]["floating"]["tower"]["n_height"][0] +prob["hub_height"] = 85.0 +prob["tower.s"] = np.linspace(0.0, 1.0, nTower) +prob["tower.outer_diameter_in"] = np.linspace(6.5, 3.87, nTower) +prob["tower.layer_thickness"] = np.linspace(0.027, 0.019, nTower).reshape((1, nTower)) +prob["tower.layer_materials"] = ["steel"] +prob["tower.outfitting_factor"] = 1.07 # Properties of rotor-nacelle-assembly (RNA) prob["rna_mass"] = 350e3 prob["rna_I"] = 1e5 * np.array([1149.307, 220.354, 187.597, 0, 5.037, 0]) prob["rna_cg"] = np.array([-1.132, 0, 0.509]) -prob["rna_force"] = np.array([1284744.196, 0, -112400.5527]) -prob["rna_moment"] = np.array([3963732.762, 896380.8464, -346781.682]) - -# Mooring constraints -prob["max_draft"] = 150.0 # Max surge/sway offset [m] -prob["max_offset"] = 100.0 # Max surge/sway offset [m] -prob["operational_heel"] = 10.0 # Max heel (pitching) angle [deg] - -# Design constraints -prob["connection_ratio_max"] = 0.25 # For welding pontoons to columns - -# API 2U flag -prob["loading"] = "hydrostatic" - +prob["rna_F"] = np.array([1284744.196, 0, -112400.5527]) +prob["rna_M"] = np.array([3963732.762, 896380.8464, -346781.682]) # Use FD and run optimization prob.run_model() diff --git a/examples/09_floating/spar_opt.py b/examples/09_floating/spar_opt.py index 96be5da31..342d5ec8d 100644 --- a/examples/09_floating/spar_opt.py +++ b/examples/09_floating/spar_opt.py @@ -9,50 +9,44 @@ plot_flag = False opt_flag = False -npts = 20 -nsection = npts - 1 +npts = 10 min_diam = 7.0 max_diam = 20.0 opt = {} -opt["platform"] = {} -opt["platform"]["columns"] = {} -opt["platform"]["columns"]["main"] = {} -opt["platform"]["columns"]["offset"] = {} -opt["platform"]["columns"]["main"]["n_height"] = npts -opt["platform"]["columns"]["main"]["n_layers"] = 1 -opt["platform"]["columns"]["main"]["n_bulkhead"] = 4 -opt["platform"]["columns"]["main"]["buckling_length"] = 30.0 -opt["platform"]["columns"]["offset"]["n_height"] = npts -opt["platform"]["columns"]["offset"]["n_layers"] = 1 -opt["platform"]["columns"]["offset"]["n_bulkhead"] = 4 -opt["platform"]["columns"]["offset"]["buckling_length"] = 30.0 -opt["platform"]["tower"] = {} -opt["platform"]["tower"]["buckling_length"] = 30.0 -opt["platform"]["frame3dd"] = {} -opt["platform"]["frame3dd"]["shear"] = True -opt["platform"]["frame3dd"]["geom"] = False -opt["platform"]["frame3dd"]["dx"] = -1 -opt["platform"]["frame3dd"]["Mmethod"] = 1 -opt["platform"]["frame3dd"]["lump"] = 0 -opt["platform"]["frame3dd"]["tol"] = 1e-6 -opt["platform"]["gamma_f"] = 1.35 # Safety factor on loads -opt["platform"]["gamma_m"] = 1.3 # Safety factor on materials -opt["platform"]["gamma_n"] = 1.0 # Safety factor on consequence of failure -opt["platform"]["gamma_b"] = 1.1 # Safety factor on buckling -opt["platform"]["gamma_fatigue"] = 1.755 # Not used -opt["platform"]["run_modal"] = False - -opt["flags"] = {} -opt["flags"]["monopile"] = False - -opt["TowerSE"] = {} -opt["TowerSE"]["n_height_tower"] = npts -opt["TowerSE"]["n_layers_tower"] = 1 +opt["floating"] = {} +opt["WISDEM"] = {} +opt["WISDEM"]["FloatingSE"] = {} +opt["floating"]["members"] = {} +opt["floating"]["members"]["n_members"] = 1 +opt["floating"]["members"]["n_height"] = [npts] +opt["floating"]["members"]["n_bulkheads"] = [4] +opt["floating"]["members"]["n_layers"] = [1] +opt["floating"]["members"]["n_ballasts"] = [2] +opt["floating"]["members"]["n_axial_joints"] = [1] +opt["floating"]["tower"] = {} +opt["floating"]["tower"]["n_height"] = [npts] +opt["floating"]["tower"]["n_bulkheads"] = [0] +opt["floating"]["tower"]["n_layers"] = [1] +opt["floating"]["tower"]["n_ballasts"] = [0] +opt["floating"]["tower"]["n_axial_joints"] = [0] +opt["WISDEM"]["FloatingSE"]["frame3dd"] = {} +opt["WISDEM"]["FloatingSE"]["frame3dd"]["shear"] = True +opt["WISDEM"]["FloatingSE"]["frame3dd"]["geom"] = True +opt["WISDEM"]["FloatingSE"]["frame3dd"]["modal"] = True +opt["WISDEM"]["FloatingSE"]["frame3dd"]["tol"] = 1e-6 +opt["WISDEM"]["FloatingSE"]["gamma_f"] = 1.35 # Safety factor on loads +opt["WISDEM"]["FloatingSE"]["gamma_m"] = 1.3 # Safety factor on materials +opt["WISDEM"]["FloatingSE"]["gamma_n"] = 1.0 # Safety factor on consequence of failure +opt["WISDEM"]["FloatingSE"]["gamma_b"] = 1.1 # Safety factor on buckling +opt["WISDEM"]["FloatingSE"]["gamma_fatigue"] = 1.755 # Not used +opt["WISDEM"]["FloatingSE"]["run_modal"] = True # Not used +opt["mooring"] = {} +opt["mooring"]["n_attach"] = 3 +opt["mooring"]["n_anchors"] = 3 opt["materials"] = {} -opt["materials"]["n_mat"] = 1 +opt["materials"]["n_mat"] = 2 -# Initialize OpenMDAO problem and FloatingSE Group prob = om.Problem() prob.model = FloatingSE(modeling_options=opt) @@ -70,40 +64,32 @@ # --- Design Variables --- # Mooring system - # prob.model.add_design_var('mooring_diameter', lower=1e-3, upper=0.35) - # prob.model.add_design_var('mooring_line_length', lower=1.0, upper=10e4) + # prob.model.add_design_var('line_diameter', lower=1e-3, upper=0.35) + # prob.model.add_design_var('line_length', lower=1.0, upper=10e4) # prob.model.add_design_var('anchor_radius', lower=1.0, upper=10e4) # Column geometry - prob.model.add_design_var("main.permanent_ballast_height", lower=1.0, upper=15.0) - prob.model.add_design_var("main_freeboard", lower=10.0, upper=25.0) - prob.model.add_design_var("main.height", lower=40.0, upper=200.0) - # prob.model.add_design_var('main.s', lower=0.0, upper=1.0) - prob.model.add_design_var("main.outer_diameter_in", lower=min_diam, upper=max_diam) - prob.model.add_design_var("main.layer_thickness", lower=2e-3, upper=8e-1) - - # prob.model.add_design_var('main.stiffener_web_height', lower=1e-2, upper=1.0) - # prob.model.add_design_var('main.stiffener_web_thickness', lower=1e-3, upper=1e-1) - # prob.model.add_design_var('main.stiffener_flange_width', lower=1e-2, upper=1.0) - # prob.model.add_design_var('main.stiffener_flange_thickness', lower=1e-3, upper=1e-1) - # prob.model.add_design_var('main.stiffener_spacing', lower=2.0, upper=10.0) - - # Tower geometry - # prob.model.add_design_var('tower_height', lower=40.0, upper=200.0) - # prob.model.add_design_var('tower_s', lower=0.0, upper=1.0) - # prob.model.add_design_var('tower_outer_diameter_in', lower=min_diam, upper=max_diam) - # prob.model.add_design_var('tower_layer_thickness', lower=2e-3, upper=8e-1) - # ---------------------- + prob.model.add_design_var("member0.ballast_volume", lower=10.0, upper=2000.0, indices=[0]) + prob.model.add_design_var("member0.joint1", lower=-200, upper=-25.0, indices=[-1]) + prob.model.add_design_var("member0.joint2", lower=1.0, upper=30.0, indices=[-1]) + prob.model.add_design_var("member0.outer_diameter_in", lower=min_diam, upper=max_diam) + prob.model.add_design_var("member0.layer_thickness", lower=2e-3, upper=8e-1) + + # prob.model.add_design_var('member0.stiffener_web_height', lower=1e-2, upper=1.0) + # prob.model.add_design_var('member0.stiffener_web_thickness', lower=1e-3, upper=1e-1) + # prob.model.add_design_var('member0.stiffener_flange_width', lower=1e-2, upper=1.0) + # prob.model.add_design_var('member0.stiffener_flange_thickness', lower=1e-3, upper=1e-1) + # prob.model.add_design_var('member0.stiffener_spacing', lower=2.0, upper=10.0) # --- Constraints --- # Ensure that draft is greater than 0 (spar length>0) and that less than water depth - # prob.model.add_constraint('main.draft_margin', upper=1.0) - # prob.model.add_constraint('main.wave_height_freeboard_ratio', upper=1.0) + # prob.model.add_constraint('member0.draft_margin', upper=1.0) + # prob.model.add_constraint('member0.wave_height_freeboard_ratio', upper=1.0) # prob.model.add_constraint('wave_height_fairlead_ratio', upper=1.0) # Ensure that the radius doesn't change dramatically over a section - prob.model.add_constraint("main.constr_taper", lower=0.2) - prob.model.add_constraint("main.constr_d_to_t", lower=80.0) + prob.model.add_constraint("member0.constr_taper", lower=0.2) + prob.model.add_constraint("member0.constr_d_to_t", lower=80.0) # Ensure max mooring line tension is less than X% of MBL: 60% for intact mooring, 80% for damanged # prob.model.add_constraint('axial_unity', lower=0.0, upper=1.0) @@ -112,14 +98,14 @@ # prob.model.add_constraint('mooring_length_max', upper=1.0) # API Bulletin 2U constraints - # prob.model.add_constraint('main.flange_spacing_ratio', upper=1.0) - # prob.model.add_constraint('main.stiffener_radius_ratio', upper=0.5) - # prob.model.add_constraint('main.flange_compactness', lower=1.0) - # prob.model.add_constraint('main.web_compactness', lower=1.0) - # prob.model.add_constraint('main.axial_local_api', upper=1.0) - # prob.model.add_constraint('main.axial_general_api', upper=1.0) - # prob.model.add_constraint('main.external_local_api', upper=1.0) - # prob.model.add_constraint('main.external_general_api', upper=1.0) + # prob.model.add_constraint('member0.flange_spacing_ratio', upper=1.0) + # prob.model.add_constraint('member0.stiffener_radius_ratio', upper=0.5) + # prob.model.add_constraint('member0.flange_compactness', lower=1.0) + # prob.model.add_constraint('member0.web_compactness', lower=1.0) + # prob.model.add_constraint('member0.axial_local_api', upper=1.0) + # prob.model.add_constraint('member0.axial_general_api', upper=1.0) + # prob.model.add_constraint('member0.external_local_api', upper=1.0) + # prob.model.add_constraint('member0.external_general_api', upper=1.0) # Eurocode constraints prob.model.add_constraint("main_stress", upper=1.0) @@ -144,137 +130,98 @@ prob.setup() - -# Set environment -prob["shearExp"] = 0.11 # Shear exponent in wind power law -prob["cm"] = 2.0 # Added mass coefficient -prob["Uc"] = 0.0 # Mean current speed -prob["wind_z0"] = 0.0 # Water line -prob["yaw"] = 0.0 # Turbine yaw angle -prob["beta_wind"] = prob["beta_wave"] = 0.0 # Wind/water beta angle -prob["cd_usr"] = -1.0 # Compute drag coefficient - -# Wind and water properties -prob["rho_air"] = 1.226 # Density of air [kg/m^3] -prob["mu_air"] = 1.78e-5 # Viscosity of air [kg/m/s] -prob["rho_water"] = 1025.0 # Density of water [kg/m^3] -prob["mu_water"] = 1.08e-3 # Viscosity of water [kg/m/s] - # Material properties -prob["rho_mat"] = np.array([7850.0]) # Steel [kg/m^3] -prob["E_mat"] = 200e9 * np.ones((1, 3)) # Young's modulus [N/m^2] -prob["G_mat"] = 79.3e9 * np.ones((1, 3)) # Shear modulus [N/m^2] -prob["sigma_y_mat"] = np.array([3.70e8]) # Elastic yield stress [N/m^2] -prob["permanent_ballast_density"] = 4492.0 # [kg/m^3] +prob["rho_mat"] = np.array([7850.0, 5000.0]) # Steel, ballast slurry [kg/m^3] +prob["E_mat"] = 200e9 * np.ones((2, 3)) # Young's modulus [N/m^2] +prob["G_mat"] = 79.3e9 * np.ones((2, 3)) # Shear modulus [N/m^2] +prob["sigma_y_mat"] = 3.45e8 * np.ones(2) # Elastic yield stress [N/m^2] +prob["unit_cost_mat"] = np.array([2.0, 1.0]) +prob["material_names"] = ["steel", "slurry"] # Mass and cost scaling factors -prob["outfitting_factor"] = 0.06 # Fraction of additional outfitting mass for each column -prob["ballast_cost_rate"] = 0.1 # Cost factor for ballast mass [$/kg] -prob["unit_cost_mat"] = np.array([1.1]) # Cost factor for column mass [$/kg] prob["labor_cost_rate"] = 1.0 # Cost factor for labor time [$/min] prob["painting_cost_rate"] = 14.4 # Cost factor for column surface finishing [$/m^2] -prob["outfitting_cost_rate"] = 1.5 * 1.1 # Cost factor for outfitting mass [$/kg] -prob["mooring_cost_factor"] = 1.1 # Cost factor for mooring mass [$/kg] + +# Column geometry +h = np.array([49.0, 59.0, 8.0, 14.0]) # Length of each section [m] +prob["member0.outfitting_factor_in"] = 1.05 # Fraction of additional outfitting mass for each column +prob["member0.grid_axial_joints"] = [0.384615] # Fairlead at 70m +prob["member0.ballast_grid"] = np.array([[0, 0.37692308], [0, 0.89230769]]) +prob["member0.ballast_volume"] = [np.pi * 4.7 ** 2 * 10, 0.0] +prob["member0.s"] = np.linspace(0, 1, npts) +prob["member0.outer_diameter_in"] = 10 * np.ones(npts) +prob["member0.layer_thickness"] = 0.05 * np.ones((1, npts)) +prob["member0.layer_materials"] = ["steel"] +prob["member0.ballast_materials"] = ["slurry", "seawater"] +prob["member0.joint1"] = np.array([0.0, 0.0, 10.0 - h.sum()]) +prob["member0.joint2"] = np.array([0.0, 0.0, 10.0]) # Freeboard=10 +prob["member0.transition_flag"] = [False, True] +prob["member0.bulkhead_thickness"] = 0.05 * np.ones(4) +prob["member0.bulkhead_grid"] = np.array([0.0, 0.37692308, 0.89230769, 1.0]) +prob["member0.ring_stiffener_web_height"] = 0.10 +prob["member0.ring_stiffener_web_thickness"] = 0.04 +prob["member0.ring_stiffener_flange_width"] = 0.10 +prob["member0.ring_stiffener_flange_thickness"] = 0.02 +prob["member0.ring_stiffener_spacing"] = 2.15 # Mooring parameters -prob["number_of_mooring_connections"] = 3 # Evenly spaced around structure -prob["mooring_lines_per_connection"] = 1 # Evenly spaced around structure -prob["mooring_type"] = "chain" # Options are chain, nylon, polyester, fiber, or iwrc -prob["anchor_type"] = "DRAGEMBEDMENT" # Options are SUCTIONPILE or DRAGEMBEDMENT +prob["line_diameter"] = 0.09 # Diameter of mooring line/chain [m] +prob["line_length"] = 300 + 902.2 # Unstretched mooring line length +prob["line_mass_density_coeff"] = 19.9e3 +prob["line_stiffness_coeff"] = 8.54e10 +prob["line_breaking_load_coeff"] = 818125253.0 +prob["line_cost_rate_coeff"] = 3.415e4 +prob["fairlead_radius"] = 10.0 +prob["fairlead"] = 70.0 +prob["anchor_radius"] = 853.87 +prob["anchor_cost"] = 1e5 + +# Mooring constraints +prob["max_surge_fraction"] = 0.1 # Max surge/sway offset [m] +prob["survival_heel"] = 10.0 # Max heel (pitching) angle [deg] +prob["operational_heel"] = 5.0 # Max heel (pitching) angle [deg] + +# Set environment to that used in OC3 testing campaign +# prob["rho_air"] = 1.226 # Density of air [kg/m^3] +# prob["mu_air"] = 1.78e-5 # Viscosity of air [kg/m/s] +prob["rho_water"] = 1025.0 # Density of water [kg/m^3] +# prob["mu_water"] = 1.08e-3 # Viscosity of water [kg/m/s] +prob["water_depth"] = 320.0 # Distance to sea floor [m] +# prob["Hsig_wave"] = 10.8 # Significant wave height [m] +# prob["Tsig_wave"] = 9.8 # Wave period [s] +# prob["shearExp"] = 0.11 # Shear exponent in wind power law +# prob["cm"] = 2.0 # Added mass coefficient +# prob["Uc"] = 0.0 # Mean current speed +# prob["yaw"] = 0.0 # Turbine yaw angle +# prob["beta_wind"] = prob["beta_wave"] = 0.0 +# prob["cd_usr"] = -1.0 # Compute drag coefficient +# prob["Uref"] = 11.0 +# prob["zref"] = 119.0 # Porperties of turbine tower -nTower = prob.model.options["modeling_options"]["TowerSE"]["n_height_tower"] - 1 -prob["tower_height"] = prob["hub_height"] = 77.6 -prob["tower_s"] = np.linspace(0.0, 1.0, nTower + 1) -prob["tower_outer_diameter_in"] = np.linspace(min_diam, 3.87, nTower + 1) -prob["tower_layer_thickness"] = np.linspace(0.04, 0.02, nTower + 1).reshape((1, nTower + 1)) -prob["tower_outfitting_factor"] = 1.07 +nTower = prob.model.options["modeling_options"]["floating"]["tower"]["n_height"][0] +prob["hub_height"] = 85.0 +prob["tower.height"] = 85.0 - prob["member0.joint2"][-1] +prob["tower.s"] = np.linspace(0.0, 1.0, nTower) +prob["tower.outer_diameter_in"] = np.linspace(6.5, 3.87, nTower) +prob["tower.layer_thickness"] = np.linspace(0.027, 0.019, nTower).reshape((1, nTower)) +prob["tower.layer_materials"] = ["steel"] +prob["tower.outfitting_factor"] = 1.07 -# Materials -prob["material_names"] = ["steel"] -prob["main.layer_materials"] = prob["off.layer_materials"] = prob["tow.tower_layer_materials"] = ["steel"] +prob["transition_node"] = prob["member0.joint2"] # Properties of rotor-nacelle-assembly (RNA) prob["rna_mass"] = 350e3 prob["rna_I"] = 1e5 * np.array([1149.307, 220.354, 187.597, 0, 5.037, 0]) prob["rna_cg"] = np.array([-1.132, 0, 0.509]) -prob["rna_force"] = np.array([1284744.196, 0, -112400.5527]) -prob["rna_moment"] = np.array([3963732.762, 896380.8464, -346781.682]) - -# Mooring constraints -prob["max_draft"] = 150.0 # Max surge/sway offset [m] -prob["max_offset"] = 100.0 # Max surge/sway offset [m] -prob["operational_heel"] = 10.0 # Max heel (pitching) angle [deg] - -# Design constraints -prob["connection_ratio_max"] = 0.25 # For welding pontoons to columns - -# API 2U flag -prob["loading"] = "hydrostatic" - -# Remove all offset columns and pontoons -prob["number_of_offset_columns"] = 0 -prob["cross_attachment_pontoons_int"] = 0 -prob["lower_attachment_pontoons_int"] = 0 -prob["upper_attachment_pontoons_int"] = 0 -prob["lower_ring_pontoons_int"] = 0 -prob["upper_ring_pontoons_int"] = 0 -prob["outer_cross_pontoons_int"] = 0 - -# Set environment to that used in OC3 testing campaign -prob["water_depth"] = 320.0 # Distance to sea floor [m] -prob["hsig_wave"] = 10.8 # Significant wave height [m] -prob["Tsig_wave"] = 9.8 # Wave period [s] -prob["Uref"] = 11.0 # Wind reference speed [m/s] -prob["zref"] = 119.0 # Wind reference height [m] - -# Column geometry -prob["main.permanent_ballast_height"] = 5.0 # Height above keel for permanent ballast [m] -prob["main_freeboard"] = 10.0 # Height extension above waterline [m] -prob["main.height"] = 100.0 # m -prob["main.s"] = np.linspace(0, 1, npts) # Non dimensional section pointns -prob["main.outer_diameter_in"] = np.linspace(max_diam, min_diam, npts) # m -prob["main.layer_thickness"] = prob["main.outer_diameter_in"][:-1].reshape((1, nsection)) / 100.0 # m -prob["main.bulkhead_thickness"] = 0.05 * np.ones(4) # Thickness of internal bulkheads [m] -prob["main.bulkhead_locations"] = np.array([0.0, 0.25, 0.9, 1.0]) # Locations of internal bulkheads - -# Column ring stiffener parameters -prob["main.stiffener_web_height"] = 0.10 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_web_thickness"] = 0.04 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_flange_width"] = 0.10 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_flange_thickness"] = 0.02 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_spacing"] = 0.40 * np.ones(nsection) # (by section) [m] - -# Mooring parameters -prob["mooring_diameter"] = 0.09 # Diameter of mooring line/chain [m] -prob["fairlead"] = 70.0 # Distance below waterline for attachment [m] -prob["fairlead_offset_from_shell"] = 0.5 # Offset from shell surface for mooring attachment [m] -prob["mooring_line_length"] = 902.2 # Unstretched mooring line length -prob["anchor_radius"] = 853.87 # Distance from centerline to sea floor landing [m] -prob["fairlead_support_outer_diameter"] = 3.2 # Diameter of all fairlead support elements [m] -prob["fairlead_support_wall_thickness"] = 0.0175 # Thickness of all fairlead support elements [m] - -# Other variables to avoid divide by zeros, even though it won't matter -prob["radius_to_offset_column"] = 15.0 -prob["offset_freeboard"] = 0.1 -prob["off.height"] = 1.0 -prob["off.s"] = np.linspace(0, 1, nsection + 1) -prob["off.outer_diameter_in"] = 5.0 * np.ones(nsection + 1) -prob["off.layer_thickness"] = 0.1 * np.ones((1, nsection)) -prob["off.permanent_ballast_height"] = 0.1 -prob["off.stiffener_web_height"] = 0.1 * np.ones(nsection) -prob["off.stiffener_web_thickness"] = 0.1 * np.ones(nsection) -prob["off.stiffener_flange_width"] = 0.1 * np.ones(nsection) -prob["off.stiffener_flange_thickness"] = 0.1 * np.ones(nsection) -prob["off.stiffener_spacing"] = 0.1 * np.ones(nsection) -prob["pontoon_outer_diameter"] = 1.0 -prob["pontoon_wall_thickness"] = 0.1 +prob["rna_F"] = np.array([1284744.196, 0, -112400.5527]) +prob["rna_M"] = np.array([3963732.762, 896380.8464, -346781.682]) # Use FD and run optimization if opt_flag: prob.model.approx_totals(form="central", step=1e-4) prob.run_driver() - fileIO.save_data("spar.pickle", prob) + fileIO.save_data("spar_out", prob) else: prob.run_model() diff --git a/examples/09_floating/tlp_example.py b/examples/09_floating/tlp_example.py index 4846e50f4..9574a86c5 100644 --- a/examples/09_floating/tlp_example.py +++ b/examples/09_floating/tlp_example.py @@ -9,184 +9,149 @@ opt_flag = False npts = 5 -nsection = npts - 1 opt = {} -opt["platform"] = {} -opt["platform"]["columns"] = {} -opt["platform"]["columns"]["main"] = {} -opt["platform"]["columns"]["offset"] = {} -opt["platform"]["columns"]["main"]["n_height"] = npts -opt["platform"]["columns"]["main"]["n_layers"] = 1 -opt["platform"]["columns"]["main"]["n_bulkhead"] = 4 -opt["platform"]["columns"]["main"]["buckling_length"] = 30.0 -opt["platform"]["columns"]["offset"]["n_height"] = npts -opt["platform"]["columns"]["offset"]["n_layers"] = 1 -opt["platform"]["columns"]["offset"]["n_bulkhead"] = 4 -opt["platform"]["columns"]["offset"]["buckling_length"] = 30.0 -opt["platform"]["tower"] = {} -opt["platform"]["tower"]["buckling_length"] = 30.0 -opt["platform"]["frame3dd"] = {} -opt["platform"]["frame3dd"]["shear"] = True -opt["platform"]["frame3dd"]["geom"] = False -opt["platform"]["frame3dd"]["dx"] = -1 -# opt['platform']['frame3dd']['nM'] = 2 -opt["platform"]["frame3dd"]["Mmethod"] = 1 -opt["platform"]["frame3dd"]["lump"] = 0 -opt["platform"]["frame3dd"]["tol"] = 1e-6 -# opt['platform']['frame3dd']['shift'] = 0.0 -opt["platform"]["gamma_f"] = 1.35 # Safety factor on loads -opt["platform"]["gamma_m"] = 1.3 # Safety factor on materials -opt["platform"]["gamma_n"] = 1.0 # Safety factor on consequence of failure -opt["platform"]["gamma_b"] = 1.1 # Safety factor on buckling -opt["platform"]["gamma_fatigue"] = 1.755 # Not used -opt["platform"]["run_modal"] = True # Not used - -opt["flags"] = {} -opt["flags"]["monopile"] = False - -opt["TowerSE"] = {} -opt["TowerSE"]["n_height_tower"] = npts -opt["TowerSE"]["n_layers_tower"] = 1 +opt["floating"] = {} +opt["WISDEM"] = {} +opt["WISDEM"]["FloatingSE"] = {} +opt["floating"]["members"] = {} +opt["floating"]["members"]["n_members"] = 4 +opt["floating"]["members"]["n_height"] = [npts, 3, 3, 3] +opt["floating"]["members"]["n_bulkheads"] = [4, 2, 2, 2] +opt["floating"]["members"]["n_layers"] = [1, 1, 1, 1] +opt["floating"]["members"]["n_ballasts"] = [2, 0, 0, 0] +opt["floating"]["members"]["n_axial_joints"] = [0, 0, 0, 0] +opt["floating"]["tower"] = {} +opt["floating"]["tower"]["n_height"] = [npts] +opt["floating"]["tower"]["n_bulkheads"] = [0] +opt["floating"]["tower"]["n_layers"] = [1] +opt["floating"]["tower"]["n_ballasts"] = [0] +opt["floating"]["tower"]["n_axial_joints"] = [0] +opt["WISDEM"]["FloatingSE"]["frame3dd"] = {} +opt["WISDEM"]["FloatingSE"]["frame3dd"]["shear"] = True +opt["WISDEM"]["FloatingSE"]["frame3dd"]["geom"] = True +opt["WISDEM"]["FloatingSE"]["frame3dd"]["modal"] = True +opt["WISDEM"]["FloatingSE"]["frame3dd"]["tol"] = 1e-6 +opt["WISDEM"]["FloatingSE"]["gamma_f"] = 1.35 # Safety factor on loads +opt["WISDEM"]["FloatingSE"]["gamma_m"] = 1.3 # Safety factor on materials +opt["WISDEM"]["FloatingSE"]["gamma_n"] = 1.0 # Safety factor on consequence of failure +opt["WISDEM"]["FloatingSE"]["gamma_b"] = 1.1 # Safety factor on buckling +opt["WISDEM"]["FloatingSE"]["gamma_fatigue"] = 1.755 # Not used +opt["WISDEM"]["FloatingSE"]["run_modal"] = True # Not used +opt["mooring"] = {} +opt["mooring"]["n_attach"] = 3 +opt["mooring"]["n_anchors"] = 3 opt["materials"] = {} -opt["materials"]["n_mat"] = 1 +opt["materials"]["n_mat"] = 2 -# Initialize OpenMDAO problem and FloatingSE Group prob = om.Problem() prob.model = FloatingSE(modeling_options=opt) prob.setup() -# Mooring parameters -prob["number_of_mooring_connections"] = 3 # Evenly spaced around structure -prob["mooring_lines_per_connection"] = 1 # Evenly spaced around structure -prob["mooring_type"] = "nylon" # Options are chain, nylon, polyester, fiber, or iwrc -prob["anchor_type"] = "suctionpile" # Options are SUCTIONPILE or DRAGEMBEDMENT - -# Remove all offset columns -prob["number_of_offset_columns"] = 0 -prob["cross_attachment_pontoons_int"] = 0 -prob["lower_attachment_pontoons_int"] = 0 -prob["upper_attachment_pontoons_int"] = 0 -prob["lower_ring_pontoons_int"] = 0 -prob["upper_ring_pontoons_int"] = 0 -prob["outer_cross_pontoons_int"] = 0 - -# Set environment to that used in OC3 testing campaign -prob["water_depth"] = 320.0 # Distance to sea floor [m] -prob["hsig_wave"] = 10.8 # Significant wave height [m] -prob["Tsig_wave"] = 9.8 # Wave period [s] -prob["Uref"] = 11.0 # Wind reference speed [m/s] -prob["zref"] = 119.0 # Wind reference height [m] - -# Column geometry -prob["main.permanent_ballast_height"] = 5.0 # Height above keel for permanent ballast [m] -prob["main_freeboard"] = 8.0 # Height extension above waterline [m] -prob["main.height"] = np.sum([10.0, 20.0, 10.0, 8.0]) -prob["main.s"] = np.cumsum([0.0, 10.0, 20.0, 10.0, 5.0]) / prob["main.height"] -prob["main.outer_diameter_in"] = 14.0 * np.ones(nsection + 1) -prob["main.layer_thickness"] = 0.04 * np.ones((1, nsection)) -prob["main.bulkhead_thickness"] = 0.05 * np.ones(4) -prob["main.bulkhead_locations"] = np.array([0.0, 0.25, 0.9, 1.0]) - -# Column ring stiffener parameters -prob["main.stiffener_web_height"] = 0.10 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_web_thickness"] = 0.04 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_flange_width"] = 0.10 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_flange_thickness"] = 0.02 * np.ones(nsection) # (by section) [m] -prob["main.stiffener_spacing"] = 0.40 * np.ones(nsection) # (by section) [m] - -# Mooring parameters -prob["mooring_diameter"] = 0.5 # Diameter of mooring line/chain [m] -prob["fairlead"] = 40.0 # Distance below waterline for attachment [m] -prob["fairlead_offset_from_shell"] = 30.0 # Offset from shell surface for mooring attachment [m] -prob["mooring_line_length"] = 250.0 # Unstretched mooring line length -prob["anchor_radius"] = 50.0 # Distance from centerline to sea floor landing [m] -prob["fairlead_support_outer_diameter"] = 5.0 # Diameter of all fairlead support elements [m] -prob["fairlead_support_wall_thickness"] = 0.05 # Thickness of all fairlead support elements [m] - -# Other variables to avoid divide by zeros, even though it won't matter -prob["radius_to_offset_column"] = 15.0 -prob["offset_freeboard"] = 0.1 -prob["off.height"] = 1.0 -prob["off.s"] = np.linspace(0, 1, nsection + 1) -prob["off.outer_diameter_in"] = 5.0 * np.ones(nsection + 1) -prob["off.layer_thickness"] = 0.1 * np.ones((1, nsection)) -prob["off.permanent_ballast_height"] = 0.1 -prob["off.stiffener_web_height"] = 0.1 * np.ones(nsection) -prob["off.stiffener_web_thickness"] = 0.1 * np.ones(nsection) -prob["off.stiffener_flange_width"] = 0.1 * np.ones(nsection) -prob["off.stiffener_flange_thickness"] = 0.1 * np.ones(nsection) -prob["off.stiffener_spacing"] = 0.1 * np.ones(nsection) -prob["pontoon_outer_diameter"] = 1.0 -prob["pontoon_wall_thickness"] = 0.1 - - -### Variables common to these spar, semi, TLP examples ### -# Set environment to that used in OC4 testing campaign -prob["shearExp"] = 0.11 # Shear exponent in wind power law -prob["cm"] = 2.0 # Added mass coefficient -prob["Uc"] = 0.0 # Mean current speed -prob["wind_z0"] = 0.0 # Water line -prob["yaw"] = 0.0 # Turbine yaw angle -prob["beta_wind"] = prob["beta_wave"] = 0.0 # Wind/water beta angle -prob["cd_usr"] = -1.0 # Compute drag coefficient - -# Wind and water properties -prob["rho_air"] = 1.226 # Density of air [kg/m^3] -prob["mu_air"] = 1.78e-5 # Viscosity of air [kg/m/s] -prob["rho_water"] = 1025.0 # Density of water [kg/m^3] -prob["mu_water"] = 1.08e-3 # Viscosity of water [kg/m/s] - # Material properties -prob["rho_mat"] = np.array([7850.0]) # Steel [kg/m^3] -prob["E_mat"] = 200e9 * np.ones((1, 3)) # Young's modulus [N/m^2] -prob["G_mat"] = 79.3e9 * np.ones((1, 3)) # Shear modulus [N/m^2] -prob["sigma_y_mat"] = np.array([3.45e8]) # Elastic yield stress [N/m^2] -prob["permanent_ballast_density"] = 4492.0 # [kg/m^3] +prob["rho_mat"] = np.array([7850.0, 5000.0]) # Steel, ballast slurry [kg/m^3] +prob["E_mat"] = 200e9 * np.ones((2, 3)) # Young's modulus [N/m^2] +prob["G_mat"] = 79.3e9 * np.ones((2, 3)) # Shear modulus [N/m^2] +prob["sigma_y_mat"] = 3.45e8 * np.ones(2) # Elastic yield stress [N/m^2] +prob["unit_cost_mat"] = np.array([2.0, 1.0]) +prob["material_names"] = ["steel", "slurry"] # Mass and cost scaling factors -prob["outfitting_factor"] = 0.06 # Fraction of additional outfitting mass for each column -prob["ballast_cost_rate"] = 0.1 # Cost factor for ballast mass [$/kg] -prob["unit_cost_mat"] = np.array([1.1]) # Cost factor for column mass [$/kg] prob["labor_cost_rate"] = 1.0 # Cost factor for labor time [$/min] prob["painting_cost_rate"] = 14.4 # Cost factor for column surface finishing [$/m^2] -prob["outfitting_cost_rate"] = 1.5 * 1.1 # Cost factor for outfitting mass [$/kg] -prob["mooring_cost_factor"] = 1.1 # Cost factor for mooring mass [$/kg] -# Mooring parameters -prob["number_of_mooring_connections"] = 3 # Evenly spaced around structure -prob["mooring_lines_per_connection"] = 1 # Evenly spaced around structure +# Main geometry +h = np.array([10.0, 20.0, 10.0, 8.0]) +prob["member0.outfitting_factor_in"] = 1.05 # Fraction of additional outfitting mass for each column +prob["member0.grid_axial_joints"] = [] +prob["member0.ballast_grid"] = np.array([[0, 0.25], [0, 0.5]]) +prob["member0.ballast_volume"] = [np.pi * 7 ** 2 * 5, 0.0] +prob["member0.s"] = np.cumsum(np.r_[0, h]) / h.sum() +prob["member0.outer_diameter_in"] = 14 * np.ones(npts) +prob["member0.layer_thickness"] = 0.05 * np.ones((1, npts)) +prob["member0.layer_materials"] = ["steel"] +prob["member0.ballast_materials"] = ["slurry", "seawater"] +prob["member0.joint1"] = np.array([0.0, 0.0, 8.0 - h.sum()]) +prob["member0.joint2"] = np.array([0.0, 0.0, 8.0]) # Freeboard=10 +prob["member0.transition_flag"] = [False, True] +prob["member0.bulkhead_thickness"] = 0.05 * np.ones(4) # Locations of internal bulkheads +prob["member0.bulkhead_grid"] = np.array([0.0, 0.25, 0.5, 1.0]) +prob["member0.ring_stiffener_web_height"] = 0.10 +prob["member0.ring_stiffener_web_thickness"] = 0.04 +prob["member0.ring_stiffener_flange_width"] = 0.10 +prob["member0.ring_stiffener_flange_thickness"] = 0.02 +prob["member0.ring_stiffener_spacing"] = 2.15 + +# Now do the legs +angs = np.linspace(0, 2 * np.pi, 1 + opt["mooring"]["n_attach"]) +for k in range(1, 4): + prob["member" + str(k) + ".outfitting_factor_in"] = 1.05 # Fraction of additional outfitting mass for each column + prob["member" + str(k) + ".grid_axial_joints"] = [] + prob["member" + str(k) + ".s"] = np.array([0.0, 0.5, 1.0]) + prob["member" + str(k) + ".outer_diameter_in"] = 5 * np.ones(3) + prob["member" + str(k) + ".layer_thickness"] = 0.05 * np.ones((1, 3)) + prob["member" + str(k) + ".layer_materials"] = ["steel"] + prob["member" + str(k) + ".ballast_materials"] = [] + prob["member" + str(k) + ".joint1"] = np.array([30.0 * np.cos(angs[k - 1]), 30.0 * np.sin(angs[k - 1]), -40.0]) + prob["member" + str(k) + ".joint2"] = np.array([0.0, 0.0, -40.0]) # Freeboard=10 + prob["member" + str(k) + ".transition_flag"] = [False, False] + prob["member" + str(k) + ".bulkhead_thickness"] = 0.05 * np.ones(2) # Locations of internal bulkheads + prob["member" + str(k) + ".bulkhead_grid"] = np.array([0.0, 1.0]) + prob["member" + str(k) + ".ring_stiffener_web_height"] = 0.10 + prob["member" + str(k) + ".ring_stiffener_web_thickness"] = 0.04 + prob["member" + str(k) + ".ring_stiffener_flange_width"] = 0.10 + prob["member" + str(k) + ".ring_stiffener_flange_thickness"] = 0.02 + prob["member" + str(k) + ".ring_stiffener_spacing"] = 2.0 + +# Mooring parameters: Nylon +prob["line_diameter"] = 0.5 # Diameter of mooring line/chain [m] +prob["line_length"] = 250.0 # Unstretched mooring line length +prob["line_mass_density_coeff"] = 0.6476e3 +prob["line_stiffness_coeff"] = 1.18e8 +prob["line_breaking_load_coeff"] = 139357e3 +prob["line_cost_rate_coeff"] = 3.415e4 +prob["fairlead_radius"] = 30.0 +prob["fairlead"] = 40.0 +prob["anchor_radius"] = 50.0 +prob["anchor_cost"] = 1e5 -# Porperties of turbine tower -nTower = prob.model.options["modeling_options"]["TowerSE"]["n_height_tower"] - 1 -prob["tower_height"] = prob["hub_height"] = 77.6 -prob["tower_s"] = np.linspace(0.0, 1.0, nTower + 1) -prob["tower_outer_diameter_in"] = np.linspace(8.0, 3.87, nTower + 1) -prob["tower_layer_thickness"] = np.linspace(0.04, 0.02, nTower + 1).reshape((1, nTower + 1)) -prob["tower_outfitting_factor"] = 1.07 +# Mooring constraints +prob["max_surge_fraction"] = 0.1 # Max surge/sway offset [m] +prob["survival_heel"] = 10.0 # Max heel (pitching) angle [deg] +prob["operational_heel"] = 5.0 # Max heel (pitching) angle [deg] -# Materials -prob["material_names"] = ["steel"] -prob["main.layer_materials"] = prob["off.layer_materials"] = prob["tow.tower_layer_materials"] = ["steel"] +# Set environment to that used in OC3 testing campaign +# prob["rho_air"] = 1.226 # Density of air [kg/m^3] +# prob["mu_air"] = 1.78e-5 # Viscosity of air [kg/m/s] +prob["rho_water"] = 1025.0 # Density of water [kg/m^3] +# prob["mu_water"] = 1.08e-3 # Viscosity of water [kg/m/s] +prob["water_depth"] = 320.0 # Distance to sea floor [m] +# prob["Hsig_wave"] = 10.8 # Significant wave height [m] +# prob["Tsig_wave"] = 9.8 # Wave period [s] +# prob["shearExp"] = 0.11 # Shear exponent in wind power law +# prob["cm"] = 2.0 # Added mass coefficient +# prob["Uc"] = 0.0 # Mean current speed +# prob["yaw"] = 0.0 # Turbine yaw angle +# prob["beta_wind"] = prob["beta_wave"] = 0.0 +# prob["cd_usr"] = -1.0 # Compute drag coefficient +# prob["Uref"] = 11.0 +# prob["zref"] = 119.0 + +# Porperties of turbine tower +nTower = prob.model.options["modeling_options"]["floating"]["tower"]["n_height"][0] +prob["hub_height"] = 85.0 +prob["tower.s"] = np.linspace(0.0, 1.0, nTower) +prob["tower.outer_diameter_in"] = np.linspace(6.5, 3.87, nTower) +prob["tower.layer_thickness"] = np.linspace(0.027, 0.019, nTower).reshape((1, nTower)) +prob["tower.layer_materials"] = ["steel"] +prob["tower.outfitting_factor"] = 1.07 # Properties of rotor-nacelle-assembly (RNA) prob["rna_mass"] = 350e3 prob["rna_I"] = 1e5 * np.array([1149.307, 220.354, 187.597, 0, 5.037, 0]) prob["rna_cg"] = np.array([-1.132, 0, 0.509]) -prob["rna_force"] = np.array([1284744.196, 0, -112400.5527]) -prob["rna_moment"] = np.array([3963732.762, 896380.8464, -346781.682]) - -# Mooring constraints -prob["max_draft"] = 150.0 # Max surge/sway offset [m] -prob["max_offset"] = 100.0 # Max surge/sway offset [m] -prob["operational_heel"] = 10.0 # Max heel (pitching) angle [deg] - -# Design constraints -prob["connection_ratio_max"] = 0.25 # For welding pontoons to columns - -# API 2U flag -prob["loading"] = "hydrostatic" - +prob["rna_F"] = np.array([1284744.196, 0, -112400.5527]) +prob["rna_M"] = np.array([3963732.762, 896380.8464, -346781.682]) # Use FD and run optimization prob.run_model() diff --git a/examples/09_weis_floating/modeling_options.yaml b/examples/09_weis_floating/modeling_options.yaml deleted file mode 100644 index cbea66397..000000000 --- a/examples/09_weis_floating/modeling_options.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Generic modeling options file to run standard WISDEM case -General: - verbosity: False # When set to True, the code prints to screen many infos -RotorSE: - flag: True - spar_cap_ss: Spar_Cap_SS - spar_cap_ps: Spar_Cap_PS -DriveSE: - flag: True -TowerSE: # Options of TowerSE module - flag: True -BOS: - flag: True \ No newline at end of file diff --git a/examples/13_design_of_experiments/IEA-15-240-RWT.yaml b/examples/13_design_of_experiments/IEA-15-240-RWT.yaml new file mode 100644 index 000000000..8dc25446c --- /dev/null +++ b/examples/13_design_of_experiments/IEA-15-240-RWT.yaml @@ -0,0 +1,970 @@ +name: IEA 15MW Offshore Reference Turbine, with taped chord tip design + +assembly: + turbine_class: I + turbulence_class: B + drivetrain: direct_drive + rotor_orientation: Upwind + number_of_blades: 3 + hub_height: 150. + rotor_diameter: 242.23775645 + rated_power: 15.e+6 + +components: + blade: + outer_shape_bem: + airfoil_position: + grid: [0.0, 0.02, 0.15, 0.24517031675566095, 0.3288439506472435, 0.4391793464459161, 0.5376714071084352, 0.6382076569163737, 0.7717438522715817, 1.0] + labels: [circular, circular, SNL-FFA-W3-500, FFA-W3-360, FFA-W3-330blend, FFA-W3-301, FFA-W3-270blend, FFA-W3-241, FFA-W3-211, FFA-W3-211] + chord: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 0.9786324786324786, 0.9807692307692307, 0.9829059829059829, 0.985042735042735, 0.9871794871794871, 0.9893162393162394, 0.9914529914529915, 0.9935897435897436, 0.9957264957264957, 0.9978632478632479, 1.0] + values: [5.2, 5.208280051053228, 5.235714686841762, 5.288938619667076, 5.360587950371896, 5.4432987797989485, 5.551024109038494, 5.650144322056736, 5.710964180985916, 5.75211103329394, 5.767237237684651, 5.755066778057295, 5.702344106369591, 5.600880031723281, 5.4630400185346435, 5.309547499170078, 5.148693682942882, 4.991042900107676, 4.849752915435638, 4.727607774019834, 4.604737872426362, 4.482056690032084, 4.373756099992555, 4.267303627608841, 4.162975379862409, 4.061706438261234, 3.9635294254274465, 3.866946023408861, 3.7730033609042173, 3.681316991817714, 3.590732198099713, 3.5007879683424514, 3.415117624636448, 3.3288587248956025, 3.2415382010222533, 3.1550747436965874, 3.0695283044857655, 2.9839638733880505, 2.8980041517726187, 2.7682222306856965, 2.6795728562121024, 2.588237585754934, 2.4939219808866904, 2.3973569798757457, 2.2985748263544723, 2.197674872061173, 2.0947488594285835, 1.989796398413245, 1.8481235924071342, 1.8258389124179322, 1.812083398372174, 1.7969953985141929, 1.779313478686, 1.7565605031190397, 1.7238716579926647, 1.6716926510559211, 1.5812588997806323, 1.4157165078158995, 1.1026835699503355, 0.5] + # grid: [0.0, 0.02, 0.06, 0.1, 0.15, 0.18172343891855364, 0.2134468778371073, 0.24517031675566095, 0.2870071337014522, 0.3288439506472435, 0.36562241591346767, 0.4024008811796919, 0.4391793464459161, 0.47201003333342245, 0.5048407202209289, 0.5376714071084352, 0.5711834903777481, 0.6046955736470608, 0.6382076569163737, 0.6715917057551757, 0.7049757545939777, 0.7383598034327797, 0.7717438522715817, 0.8, 0.8300000000000001, 0.86, 0.89, 0.9199999999999999, 0.95, 1.0] # Chord distribution prior to tip design + # values: [5.2, 5.208280051053228, 5.285492068391177, 5.433382174440381, 5.636356032301065, 5.731725074683288, 5.769429098024944, 5.7023609061472, 5.463008795114139, 5.148711469509542, 4.893885502207279, 4.687066719531832, 4.482055410102649, 4.302541125762527, 4.128880685683523, 3.9635291625049662, 3.8040647972939348, 3.65098634486243, 3.500787157643111, 3.350542461903591, 3.1981002576045348, 3.048118061847359, 2.8980023234653363, 2.7682221671772655, 2.625166734511243, 2.4747966494730496, 2.3184927933828097, 2.1567504381003917, 1.989814349481612, 1.7007107016322278] + twist: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.27217629557079365, 0.2721151609261069, 0.2692829996121141, 0.26160974693508077, 0.25004194631128646, 0.23552614115701065, 0.21467775307891476, 0.19255079961979954, 0.1759878586013097, 0.16057133469540177, 0.14711714321398614, 0.1357292521960968, 0.12536451335231813, 0.11391930868991773, 0.10288584723243112, 0.09240330785216998, 0.08263112720589656, 0.07331222604686127, 0.06502804112954641, 0.05771742601208772, 0.05096319090575163, 0.04469782752454605, 0.03947910486050121, 0.03456265676425803, 0.0299081112103219, 0.025446129064633893, 0.02121583510652544, 0.01724217523108515, 0.013528184408954813, 0.01004307189737569, 0.006635163107666419, 0.0032467588547257582, -3.369564839038511e-05, -0.0035492823924747176, -0.007421594235148177, -0.011928778895197866, -0.017136732150624685, -0.0223974077244561, -0.027490996670335385, -0.03379973253909251, -0.036799280794358936, -0.037810153747541306, -0.037988462925127336, -0.03771818081838972, -0.036975584119978255, -0.03575998726335209, -0.03405925501568549, -0.03168024471993639, -0.027365297139775716, -0.021683756060763528] + pitch_axis: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.5045454545454545, 0.4903639488433998, 0.47344581477669084, 0.4551778324621211, 0.43618119557775503, 0.4173968865018273, 0.39572662049393514, 0.37552880224106955, 0.3611273141524491, 0.3487501127713043, 0.33844986179274716, 0.3303679801622404, 0.32401218948827915, 0.3186465534437526, 0.314428907534966, 0.31121157527709187, 0.30854840563696906, 0.30617886678441086, 0.3037386214393621, 0.3009565896461483, 0.2980046447599979, 0.2950798114486581, 0.2928514995454046, 0.2909879050256579, 0.2894463822857346, 0.2883899507195189, 0.2878232655577635, 0.28785074735445465, 0.2879859584711403, 0.28875948812490126, 0.28991267457158154, 0.2915681182477685, 0.29342497097292985, 0.29556568178801323, 0.29798169875839364, 0.3005965859767856, 0.3034066669145652, 0.30632395890335945, 0.3093052266122308, 0.3136363636363636, 0.3167992340460062, 0.3203562773387713, 0.32441360302489675, 0.32881339964516054, 0.333523199822775, 0.3384671408287767, 0.3437460366916561, 0.34950036634135173, 0.3582681522176359, 0.36818181818181805] + reference_axis: + x: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0, 0.017927254421009235, 0.04109498585036197, 0.0694133295001114, 0.10093956284485817, 0.13373096335920276, 0.1735397389870922, 0.20849545443912093, 0.22951354509947788, 0.24384433268043473, 0.2498132008590583, 0.25028811784244964, 0.24939986751287166, 0.2474948825479514, 0.24464044933568185, 0.24098808462171836, 0.2364924891508088, 0.2315294661480854, 0.22318132011225872, 0.20501055669934568, 0.17342012363636425, 0.12433480584123438, 0.07234940243374963, 0.013808931623088154, -0.04965660218175578, -0.11436010751525384, -0.18380667727600022, -0.2695700493574572, -0.36837312601691447, -0.4816050193474183, -0.6035893261970865, -0.7333735567347408, -0.8602026821607676, -0.9895743950626118, -1.1197364725526204, -1.2567849268716618, -1.401602523724949, -1.5531511571253716, -1.7110231316969464, -1.9546458376616889, -2.1216802152556147, -2.2924730996379994, -2.4670749165198758, -2.647980282848751, -2.8349366171144617, -3.0279675976038907, -3.2264755653995576, -3.4307396940691453, -3.7110555584534786, -3.9999999999999964] + y: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + z: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0, 2.34, 4.68, 7.02, 9.36, 11.700000000000001, 14.625, 17.55, 19.776985412082468, 22.00397082416493, 24.230956236247398, 26.457941648329864, 28.68492706041233, 31.13238085174112, 33.57983464306991, 36.0272884343987, 38.47474222572749, 41.05659048741643, 43.63843874910536, 46.22028701079431, 48.80213527248324, 51.38398353417219, 53.688697753675136, 55.99341197317808, 58.298126192681025, 60.60284041218397, 62.90755463168692, 65.26010287719268, 67.61265112269844, 69.96519936820421, 72.31774761370995, 74.67029585921573, 76.90225798158134, 79.13422010394696, 81.36618222631259, 83.5981443486782, 85.83010647104382, 88.06206859340944, 90.29403071577507, 93.60000000000001, 95.79375000000002, 97.9875, 100.18124999999999, 102.375, 104.56875000000001, 106.7625, 108.95624999999998, 111.14999999999999, 114.075, 117.0] + internal_structure_2d_fem: + reference_axis: + x: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0, 0.017927254421009235, 0.04109498585036197, 0.0694133295001114, 0.10093956284485817, 0.13373096335920276, 0.1735397389870922, 0.20849545443912093, 0.22951354509947788, 0.24384433268043473, 0.2498132008590583, 0.25028811784244964, 0.24939986751287166, 0.2474948825479514, 0.24464044933568185, 0.24098808462171836, 0.2364924891508088, 0.2315294661480854, 0.22318132011225872, 0.20501055669934568, 0.17342012363636425, 0.12433480584123438, 0.07234940243374963, 0.013808931623088154, -0.04965660218175578, -0.11436010751525384, -0.18380667727600022, -0.2695700493574572, -0.36837312601691447, -0.4816050193474183, -0.6035893261970865, -0.7333735567347408, -0.8602026821607676, -0.9895743950626118, -1.1197364725526204, -1.2567849268716618, -1.401602523724949, -1.5531511571253716, -1.7110231316969464, -1.9546458376616889, -2.1216802152556147, -2.2924730996379994, -2.4670749165198758, -2.647980282848751, -2.8349366171144617, -3.0279675976038907, -3.2264755653995576, -3.4307396940691453, -3.7110555584534786, -3.9999999999999964] + y: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + z: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0, 2.34, 4.68, 7.02, 9.36, 11.700000000000001, 14.625, 17.55, 19.776985412082468, 22.00397082416493, 24.230956236247398, 26.457941648329864, 28.68492706041233, 31.13238085174112, 33.57983464306991, 36.0272884343987, 38.47474222572749, 41.05659048741643, 43.63843874910536, 46.22028701079431, 48.80213527248324, 51.38398353417219, 53.688697753675136, 55.99341197317808, 58.298126192681025, 60.60284041218397, 62.90755463168692, 65.26010287719268, 67.61265112269844, 69.96519936820421, 72.31774761370995, 74.67029585921573, 76.90225798158134, 79.13422010394696, 81.36618222631259, 83.5981443486782, 85.83010647104382, 88.06206859340944, 90.29403071577507, 93.60000000000001, 95.79375000000002, 97.9875, 100.18124999999999, 102.375, 104.56875000000001, 106.7625, 108.95624999999998, 111.14999999999999, 114.075, 117.0] + webs: + - name: web0 + rotation: + fixed: twist + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [-0.27217629557079365, -0.2721151609261069, -0.2692829996121141, -0.26160974693508077, -0.25004194631128646, -0.23552614115701065, -0.21467775307891476, -0.19255079961979954, -0.1759878586013097, -0.16057133469540177, -0.14711714321398614, -0.1357292521960968, -0.12536451335231813, -0.11391930868991773, -0.10288584723243112, -0.09240330785216998, -0.08263112720589656, -0.07331222604686127, -0.06502804112954641, -0.05771742601208772, -0.05096319090575163, -0.04469782752454605, -0.03947910486050121, -0.03456265676425803, -0.0299081112103219, -0.025446129064633893, -0.02121583510652544, -0.01724217523108515, -0.013528184408954813, -0.01004307189737569, -0.006635163107666419, -0.0032467588547257582, 3.369564839038511e-05, 0.0035492823924747176, 0.007421594235148177, 0.011928778895197866, 0.017136732150624685, 0.0223974077244561, 0.027490996670335385, 0.03379973253909251, 0.036799280794358936, 0.037810153747541306, 0.037988462925127336, 0.03771818081838972, 0.036975584119978255, 0.03575998726335209, 0.03405925501568549, 0.03168024471993639, 0.027365297139775716, 0.021683756060763528] + offset_y_pa: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975] + values: [-0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35, -0.35] + start_nd_arc: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.2052832866238972, 0.20964207801130544, 0.2149018865021589, 0.2211786870016678, 0.22903035857348808, 0.2632168495782914, 0.27831044914309483, 0.2930124745949063, 0.30261391549635497, 0.31130358335135594, 0.31904970539697797, 0.3256090681579934, 0.3310228858092881, 0.33626258590946945, 0.3410021225330599, 0.34509685716110255, 0.3488669831656817, 0.35252871253565177, 0.3560425836181651, 0.35953269434147644, 0.36310788745157013, 0.36671126887658234, 0.36979454677314966, 0.37275424684679154, 0.3755814765841752, 0.3781683955745834, 0.3804897412053756, 0.3825158515808853, 0.38446668864538414, 0.38611001330565603, 0.38760290744857984, 0.38890422752099757, 0.3901260320670572, 0.3913748668211914, 0.39261097608454115, 0.393791433992064, 0.39484956703302526, 0.3957404616114903, 0.396416104578199, 0.3974006481268957, 0.398057730369124, 0.39862540875484753, 0.39915197364275734, 0.3997504096013997, 0.4004755530100168, 0.4014112142437175, 0.40256531874178636, 0.4039350776826077, 0.4060203151084342, 0.30965107872575415] + end_nd_arc: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.7080814332146719, 0.7037423184648783, 0.7008720704020883, 0.7007663914444887, 0.7014592994444613, 0.6768679674127758, 0.6727146826521084, 0.6665855901258702, 0.6615117667853724, 0.6570238242449686, 0.6527854239454647, 0.6489450337329036, 0.6457010897759348, 0.6426373447872222, 0.6397304476851019, 0.6372172220396691, 0.6349562902863888, 0.6328310710695207, 0.6306883214049477, 0.6284198886110399, 0.625976290774459, 0.6234387088130607, 0.6212498617518806, 0.6191409856801369, 0.6171284439735927, 0.6153285411296807, 0.6137670769244926, 0.6124613204850784, 0.6111892493226305, 0.6102069121938857, 0.6093859212554675, 0.6087785128819696, 0.6082571237269621, 0.6077665190995171, 0.607327721214209, 0.6069739930175891, 0.6067360730529229, 0.6065570916290545, 0.6064314244574617, 0.6060239165038073, 0.605638948335295, 0.6051614931583904, 0.6046499467538473, 0.604025344365607, 0.6032306569974505, 0.6021794408845366, 0.6008659971061063, 0.5992775043717204, 0.5968041461122746, 0.692699164916447] + - name: web1 + rotation: + fixed: twist + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [-0.27217629557079365, -0.2721151609261069, -0.2692829996121141, -0.26160974693508077, -0.25004194631128646, -0.23552614115701065, -0.21467775307891476, -0.19255079961979954, -0.1759878586013097, -0.16057133469540177, -0.14711714321398614, -0.1357292521960968, -0.12536451335231813, -0.11391930868991773, -0.10288584723243112, -0.09240330785216998, -0.08263112720589656, -0.07331222604686127, -0.06502804112954641, -0.05771742601208772, -0.05096319090575163, -0.04469782752454605, -0.03947910486050121, -0.03456265676425803, -0.0299081112103219, -0.025446129064633893, -0.02121583510652544, -0.01724217523108515, -0.013528184408954813, -0.01004307189737569, -0.006635163107666419, -0.0032467588547257582, 3.369564839038511e-05, 0.0035492823924747176, 0.007421594235148177, 0.011928778895197866, 0.017136732150624685, 0.0223974077244561, 0.027490996670335385, 0.03379973253909251, 0.036799280794358936, 0.037810153747541306, 0.037988462925127336, 0.03771818081838972, 0.036975584119978255, 0.03575998726335209, 0.03405925501568549, 0.03168024471993639, 0.027365297139775716, 0.021683756060763528] + offset_y_pa: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975] + values: [0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35] + start_nd_arc: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.2052832866238972, 0.20964207801130544, 0.2149018865021589, 0.2211786870016678, 0.22903035857348808, 0.21432434252219454, 0.22721916960625643, 0.24091615782689985, 0.25024959398087304, 0.25855217659672053, 0.2657772762134616, 0.27170867241351976, 0.2762811405401291, 0.28034087811498737, 0.28356137556758043, 0.28588849421683543, 0.28765014773657943, 0.2891424803294219, 0.2905634928566717, 0.2921045778649552, 0.2936170793405667, 0.29504142747498485, 0.296096765660087, 0.2969483997712954, 0.29760044158053645, 0.2979717733931913, 0.298040921433441, 0.2977473092468652, 0.2973310091829169, 0.2965510123421598, 0.2955306665236286, 0.2942097191303531, 0.29278574635814536, 0.2912043768473332, 0.28941972485622375, 0.2874618856589628, 0.2852848282657877, 0.28283499128404316, 0.2800707469127728, 0.2755467145869285, 0.27213080194406253, 0.2682087011680181, 0.26375371883707327, 0.25883772005119166, 0.2534414260595001, 0.24755145464380687, 0.24106669116904295, 0.23383283884309086, 0.22274237902285932, 0.30965107872575415] + end_nd_arc: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.7080814332146719, 0.7037423184648783, 0.7008720704020883, 0.7007663914444887, 0.7014592994444613, 0.7259533873235942, 0.7244160359508459, 0.719542268196627, 0.7147460993855903, 0.7107032736927151, 0.7069857529396322, 0.7037098437317258, 0.7012133082971896, 0.6992141373119866, 0.6977275760629951, 0.6969007859022075, 0.6965816500644172, 0.6965624167571665, 0.6964551326409836, 0.6960796440803874, 0.695653364376506, 0.6952527583374107, 0.6950683549292908, 0.6950526452867478, 0.6952060054775661, 0.6956189537771706, 0.6963061461301319, 0.6973197569087212, 0.698415938670108, 0.6998563826114835, 0.7015494651502238, 0.7035611910126097, 0.7056751855391313, 0.7079970272037814, 0.710562414103539, 0.7133274024103817, 0.7163044118337935, 0.7194566100475068, 0.7227687143391472, 0.7278657435938146, 0.7315440181162757, 0.7355441956432848, 0.7400025779922411, 0.7448809650304351, 0.7502019203156253, 0.7559881053807376, 0.7623406936078805, 0.7694044080150518, 0.7802119228449619, 0.692699164916447] + layers: + - name: UV_protection + material: Gelcoat + thickness: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005] + start_nd_arc: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + end_nd_arc: + values: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + fiber_orientation: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + - name: Shell_skin + material: glass_triax + fiber_orientation: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + thickness: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.05, 0.044591322442333034, 0.03941021393803537, 0.03441462790857325, 0.029675608520261305, 0.025, 0.018746440238161553, 0.012935658206036059, 0.009215755156010518, 0.006529885804404432, 0.004719386872874692, 0.0036740071088946866, 0.0030103299903238034, 0.0024577650586213515, 0.002138757820887468, 0.0020411160305407556, 0.002001744944261837, 0.002000350046275909, 0.0020000346243765573, 0.0020000022429495484, 0.002000000000106529, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.0018889580666709893, 0.0016806403193428728, 0.0013888318819373973, 0.001170340950441993, 0.0010489579419444472, 0.001008605037770434, 0.001001324884824214, 0.001, 0.001, 0.001] + start_nd_arc: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + end_nd_arc: + values: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + - name: Spar_Cap_SS + material: CarbonUD + thickness: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0001, 0.0018481775565967556, 0.0056292722526006525, 0.011723847305571057, 0.019594784226386087, 0.028704964525923835, 0.0410171094534032, 0.053377264971817634, 0.06217255679907582, 0.06992457442919356, 0.07617032462588876, 0.08038052326853298, 0.08385343152685465, 0.08770720185923539, 0.09117522872167859, 0.09420279562329452, 0.09648670099195718, 0.09805824310543443, 0.09846752029205291, 0.09806788151575038, 0.09724141797579387, 0.09579771132961162, 0.09429333850733454, 0.09263041986511257, 0.09088095459802573, 0.08919218901957637, 0.08753523764006314, 0.08583969427275749, 0.08405524044868973, 0.08213237824356773, 0.08001280913405627, 0.07766056294487361, 0.07520222644191554, 0.07232533050986578, 0.06884782957973191, 0.06416031816637194, 0.0578329884057097, 0.05088707390724323, 0.04349789291675291, 0.033022909870011316, 0.027018764366165116, 0.022341670505153635, 0.01882057390364337, 0.015488026573407695, 0.012390370179697097, 0.009487939911486443, 0.006886703058694333, 0.004684019325428318, 0.0025120129030554894, 0.0010000000000001236] + fiber_orientation: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + width: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.8999999986667392, 0.899999489275702, 0.8999939509463861, 0.8998931476365687, 0.8994447807605057, 0.8959475304746178, 0.8821965355067473, 0.838237396119912, 0.6508977582590109, 0.294] + offset_y_pa: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + rotation: + fixed: twist + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [-0.27217629557079365, -0.2721151609261069, -0.2692829996121141, -0.26160974693508077, -0.25004194631128646, -0.23552614115701065, -0.21467775307891476, -0.19255079961979954, -0.1759878586013097, -0.16057133469540177, -0.14711714321398614, -0.1357292521960968, -0.12536451335231813, -0.11391930868991773, -0.10288584723243112, -0.09240330785216998, -0.08263112720589656, -0.07331222604686127, -0.06502804112954641, -0.05771742601208772, -0.05096319090575163, -0.04469782752454605, -0.03947910486050121, -0.03456265676425803, -0.0299081112103219, -0.025446129064633893, -0.02121583510652544, -0.01724217523108515, -0.013528184408954813, -0.01004307189737569, -0.006635163107666419, -0.0032467588547257582, 3.369564839038511e-05, 0.0035492823924747176, 0.007421594235148177, 0.011928778895197866, 0.017136732150624685, 0.0223974077244561, 0.027490996670335385, 0.03379973253909251, 0.036799280794358936, 0.037810153747541306, 0.037988462925127336, 0.03771818081838972, 0.036975584119978255, 0.03575998726335209, 0.03405925501568549, 0.03168024471993639, 0.027365297139775716, 0.021683756060763528] + side: suction + start_nd_arc: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.17773574415091226, 0.1821383302374979, 0.1871009332318214, 0.19249766577284227, 0.19909487385335925, 0.20737659863174399, 0.2199563361667385, 0.23350030064214203, 0.2427908278512117, 0.2510319817189903, 0.25817999326235086, 0.2640222447594565, 0.26847452041144376, 0.2723675397633476, 0.27537254893766394, 0.2774500732168921, 0.27892856252835135, 0.2801147338948552, 0.2812396202718402, 0.2825052719168474, 0.28372563674648144, 0.28484219419451967, 0.2856108338834729, 0.28616541775075416, 0.28651108512022494, 0.2865704975066126, 0.28632375439281, 0.2857054110482848, 0.28495802005810855, 0.28384007813924583, 0.28247070235731614, 0.2807878820118879, 0.2790021706772168, 0.2770373700835421, 0.2748470321944108, 0.27247329866326264, 0.26987814641494146, 0.26700229102005213, 0.26380367016835893, 0.2585962219214307, 0.2546725630541853, 0.2501832999132221, 0.24509579187321692, 0.2394959247202817, 0.23337437446593906, 0.2270055310048491, 0.22114542327869086, 0.2182172070860106, 0.2303158393899657, 0.1687033757034503] + end_nd_arc: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.23283082909688213, 0.23714582578511298, 0.24270283977249643, 0.2498597082304933, 0.25896584329361694, 0.27000269483004224, 0.2854347760314214, 0.30032302329985644, 0.3099798482861786, 0.3187574972802518, 0.32659579098178837, 0.33325185317686107, 0.33878684218138727, 0.3441915018402132, 0.3491447797198609, 0.35348722226045426, 0.3575399900923965, 0.36150232782620695, 0.3653044117612391, 0.36905455788355945, 0.37290635264139294, 0.3767955526039047, 0.3801508362312212, 0.38339266024185553, 0.3865129652584084, 0.38940305959635185, 0.3920303297107056, 0.394374898051104, 0.39665263900019554, 0.39863435510624, 0.40047841686523705, 0.40214407322390117, 0.40373137751611976, 0.4053705328593945, 0.40702377694728326, 0.4086404375840501, 0.4101533481409244, 0.41151876487676886, 0.41268977856418043, 0.41446252322104926, 0.4156954509495021, 0.41688836508912697, 0.41810427553004403, 0.4194529930165829, 0.42097165234280315, 0.4224528900690906, 0.4230490209476922, 0.4201789285679513, 0.399162484943435, 0.450598781748058] + - name: Spar_Cap_PS + material: CarbonUD + thickness: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0001, 0.0018481775565967556, 0.0056292722526006525, 0.011723847305571057, 0.019594784226386087, 0.028704964525923835, 0.0410171094534032, 0.053377264971817634, 0.06217255679907582, 0.06992457442919356, 0.07617032462588876, 0.08038052326853298, 0.08385343152685465, 0.08770720185923539, 0.09117522872167859, 0.09420279562329452, 0.09648670099195718, 0.09805824310543443, 0.09846752029205291, 0.09806788151575038, 0.09724141797579387, 0.09579771132961162, 0.09429333850733454, 0.09263041986511257, 0.09088095459802573, 0.08919218901957637, 0.08753523764006314, 0.08583969427275749, 0.08405524044868973, 0.08213237824356773, 0.08001280913405627, 0.07766056294487361, 0.07520222644191554, 0.07232533050986578, 0.06884782957973191, 0.06416031816637194, 0.0578329884057097, 0.05088707390724323, 0.04349789291675291, 0.033022909870011316, 0.027018764366165116, 0.022341670505153635, 0.01882057390364337, 0.015488026573407695, 0.012390370179697097, 0.009487939911486443, 0.006886703058694333, 0.004684019325428318, 0.0025120129030554894, 0.0010000000000001236] + fiber_orientation: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + width: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.8999999986667392, 0.899999489275702, 0.8999939509463861, 0.8998931476365687, 0.8994447807605057, 0.8959475304746178, 0.8821965355067473, 0.838237396119912, 0.6508977582590109, 0.294] + offset_y_pa: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + rotation: + fixed: twist + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [-0.27217629557079365, -0.2721151609261069, -0.2692829996121141, -0.26160974693508077, -0.25004194631128646, -0.23552614115701065, -0.21467775307891476, -0.19255079961979954, -0.1759878586013097, -0.16057133469540177, -0.14711714321398614, -0.1357292521960968, -0.12536451335231813, -0.11391930868991773, -0.10288584723243112, -0.09240330785216998, -0.08263112720589656, -0.07331222604686127, -0.06502804112954641, -0.05771742601208772, -0.05096319090575163, -0.04469782752454605, -0.03947910486050121, -0.03456265676425803, -0.0299081112103219, -0.025446129064633893, -0.02121583510652544, -0.01724217523108515, -0.013528184408954813, -0.01004307189737569, -0.006635163107666419, -0.0032467588547257582, 3.369564839038511e-05, 0.0035492823924747176, 0.007421594235148177, 0.011928778895197866, 0.017136732150624685, 0.0223974077244561, 0.027490996670335385, 0.03379973253909251, 0.036799280794358936, 0.037810153747541306, 0.037988462925127336, 0.03771818081838972, 0.036975584119978255, 0.03575998726335209, 0.03405925501568549, 0.03168024471993639, 0.027365297139775716, 0.021683756060763528] + side: pressure + start_nd_arc: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.6805338907416869, 0.6762385706910707, 0.6730711171317508, 0.6720853702156632, 0.6715238147243324, 0.669935389335444, 0.6655671806707737, 0.6593685054871514, 0.6542602412429819, 0.6497245342820102, 0.6454069412689026, 0.6414525764647957, 0.638054167874096, 0.6347842632835325, 0.6316279880452333, 0.6288379737893852, 0.6262727455681317, 0.6238260766815458, 0.6213782858463365, 0.6188336115785243, 0.6161050675653428, 0.6132734389653898, 0.6108151075104965, 0.6084315592635592, 0.6061360135894776, 0.6040465000196825, 0.6021893219907264, 0.6005759965995441, 0.5989893575552869, 0.597678693729204, 0.5965148532340123, 0.5955479758414505, 0.5946638354193096, 0.5937850706722259, 0.5929333561343686, 0.5921546654447176, 0.5914841613577797, 0.5908616137911897, 0.5902818381435507, 0.5891481668469944, 0.5882190142913776, 0.587127116089011, 0.5859273639036329, 0.5845492608597633, 0.5829528707258185, 0.5813428646646273, 0.5805691826718496, 0.5831933888206003, 0.6037661157825593, 0.5517514618941431] + end_nd_arc: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.7356289756876568, 0.7312460662386859, 0.7286730236724258, 0.7294474126733143, 0.7313947841645901, 0.7325614855337423, 0.7310456205354565, 0.7261912281448659, 0.7214492616779489, 0.7174500498432717, 0.7138227389883401, 0.7106821848822004, 0.7083664896440395, 0.706608225360398, 0.7054002188274302, 0.7048751228329473, 0.704884173132177, 0.7052136706128975, 0.7054430773357353, 0.7053828975452364, 0.7052857834602544, 0.7052267973747748, 0.7053551098582448, 0.7056588017546606, 0.706137893727661, 0.7068790621094218, 0.707895897308622, 0.7092454836023632, 0.710683976497374, 0.7124729706961983, 0.7145225677419331, 0.7169041670534638, 0.7193930422582125, 0.7221182334480782, 0.7251101008872411, 0.7283218043655051, 0.7317593630837627, 0.7353780876479066, 0.7391679465393722, 0.7450144681466131, 0.7492419021866944, 0.7538321812649158, 0.75893584756046, 0.7645063291560645, 0.7705501486026827, 0.7767902237288686, 0.782472780340851, 0.785155110302541, 0.7726127613360285, 0.8336468679387509] + - name: LE_reinforcement + material: glass_uni + thickness: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8] + values: [0.003, 0.002964285714285715, 0.0029285714285714293, 0.002901379909498383, 0.002874188390425336, 0.0028469968713522894, 0.0028198053522792434, 0.002792613833206198, 0.0027627303925306336, 0.0027328469518550684, 0.0027029635111795037, 0.0026730800705039386, 0.002641555671704317, 0.002610031272904696, 0.002578506874105075, 0.0025469824753054553, 0.002515458076505835, 0.0024873174877451153, 0.002459176898984395, 0.002431036310223674, 0.002402895721462955, 0.0023747551327022364, 0.0023460304898999685, 0.0023173058470977006, 0.0022885812042954317, 0.002259856561493163, 0.002231131918690894, 0.0022038796339245255, 0.0021766273491581572, 0.0021493750643917886, 0.0021221227796254195, 0.0020948704948590495, 0.0020676182100926805, 0.002040365925326312, 0.002] + fiber_orientation: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8] + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + midpoint_nd_arc: + fixed: LE + width: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8] + values: [0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.7999660022114599, 0.799808764223006, 0.7983592796500668, 0.7942584952386328, 0.7823447701815651, 0.7628015030477565, 0.7360619547805919, 0.7000000000000001] + start_nd_arc: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.5000006654037845, 0.5000006654037842, 0.5002060277935696, 0.5006729174479077, 0.5011391259091965, 0.47346462151331714, 0.4718093545618548, 0.47076145665348734, 0.4701871709067392, 0.46981163253812597, 0.46943797907983936, 0.46898315286244396, 0.46840921108356154, 0.46756151597240136, 0.46639944253599985, 0.4650983204499207, 0.4637474479462891, 0.4623814213061447, 0.4610807730844251, 0.4598984578330925, 0.4586957997951826, 0.45750128840566523, 0.4564739644234709, 0.4554830835663568, 0.45451383175095833, 0.4535487072231865, 0.45255836592716747, 0.4515170177124577, 0.45044998957667665, 0.4493632184525854, 0.4482468008288618, 0.4470925325450485, 0.44595052036636745, 0.44475327494726175, 0.4435528144192485, 0.4424682269600974, 0.4418972477649065, 0.44183748532653727, 0.4422768823612798, 0.44254520962559685, 0.5031598823532262, 0.5031598823532264, 0.5031598823532264, 0.5031598823532264, 0.503159882353226, 0.5031598823532264, 0.5031598823532263, 0.5031598823532261, 0.5031598823532264, 0.5031598823532263] + end_nd_arc: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.5000006654037845, 0.5000006654037842, 0.5002060277935696, 0.5006729174479077, 0.5011391259091965, 0.5291322625784711, 0.5300124122193507, 0.5301594323492335, 0.5299107446267098, 0.5300120908148028, 0.5302520214971173, 0.5305205825668036, 0.5309090526568446, 0.5314050378185041, 0.5319747587868415, 0.5326868973775315, 0.5336242724476626, 0.5347259492451241, 0.535805032186113, 0.53683115647017, 0.5379675472573262, 0.5392376069917852, 0.5405095220659138, 0.5419072991140025, 0.5434043918737881, 0.5449554290807325, 0.5465197662097414, 0.5481121172705192, 0.5497340953029762, 0.5514025757565802, 0.5531425470581248, 0.5549647025112825, 0.5568162147596158, 0.5587999286456888, 0.5608022930052419, 0.5626370123602913, 0.5638345482721141, 0.5643234669632766, 0.5640428823451725, 0.5637745550808557, 0.5031598823532262, 0.5031598823532264, 0.5031598823532264, 0.5031598823532264, 0.503159882353226, 0.5031598823532264, 0.5031598823532263, 0.5031598823532261, 0.5031598823532264, 0.5031598823532263] + - name: TE_reinforcement + material: glass_uni + thickness: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8] + values: [0.014070829560136859, 0.022160430655669527, 0.030782769539623476, 0.03726831832745499, 0.043340757160609436, 0.04868802898982519, 0.053420517202720286, 0.05799770760351344, 0.0634520411459693, 0.06869127985025639, 0.07351107288889727, 0.0772925251716927, 0.0799890785380285, 0.08059191749850508, 0.0786524779131348, 0.07482392045113133, 0.06840200991343892, 0.061943604714406436, 0.05516030075943382, 0.048439556662600414, 0.042695699138683374, 0.03746845247962702, 0.03188144091497945, 0.02618334012805381, 0.02035123618867921, 0.014980729143164732, 0.010329533513665235, 0.006853678213629035, 0.004827463124691154, 0.003932953371568727, 0.0032868057459755104, 0.002775391173787103, 0.0023260236603008825, 0.001947794263911153, 0.0015403609743885973] + # values: [0.00469027652004562, 0.0073868102185565086, 0.010260923179874492, 0.012422772775818328, 0.01444691905353648, 0.016229342996608397, 0.017806839067573427, 0.019332569201171147, 0.021150680381989768, 0.022897093283418796, 0.024503690962965757, 0.0257641750572309, 0.02666302617934283, 0.026863972499501697, 0.0262174926377116, 0.024941306817043776, 0.022800669971146305, 0.02064786823813548, 0.01838676691981127, 0.016146518887533472, 0.014231899712894458, 0.012489484159875674, 0.010627146971659817, 0.008727780042684603, 0.006783745396226403, 0.00499357638105491, 0.003443177837888412, 0.0022845594045430117, 0.0016091543748970511, 0.0013109844571895755, 0.0010956019153251701, 0.0009251303912623677, 0.0007753412201002942, 0.000649264754637051, 0.0005134536581295324] + fiber_orientation: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8] + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + midpoint_nd_arc: + fixed: TE + width: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8] + values: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + start_nd_arc: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [1.0, 1.0, 1.0, 1.0, 1.0, 0.9652077243342787, 0.963623088964065, 0.9628762651901587, 0.9626727664250184, 0.9623747135770769, 0.9619912234892014, 0.9615391064347752, 0.9609375990166981, 0.9600977988461858, 0.9590154273432239, 0.9577571394202432, 0.9563269846866416, 0.9547846700381379, 0.953297338061445, 0.9519170633518266, 0.9504551578361603, 0.948914800883675, 0.9474777764734732, 0.9459848652827214, 0.9444433999232313, 0.9428707988390338, 0.9412741248233913, 0.9396280627762116, 0.9379474339210627, 0.9362254016850032, 0.9344401586067106, 0.9325798937711037, 0.9307059962006095, 0.9287037984578598, 0.9265684751372931, 0.9243515894884514, 0.9220693323744539, 0.9197130700796018, 0.9172854953356547, 0.9134076103891008, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + end_nd_arc: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [1.0, 1.0, 1.0, 1.0, 1.0, 0.034792275665721295, 0.03637691103593488, 0.037123734809841435, 0.03732723357498169, 0.037625286422922954, 0.03800877651079859, 0.03846089356522486, 0.039062400983301826, 0.039902201153814154, 0.04098457265677613, 0.04224286057975668, 0.04367301531335843, 0.04521532996186206, 0.04670266193855488, 0.048082936648173424, 0.04954484216383981, 0.05108519911632503, 0.0525222235265268, 0.05401513471727859, 0.05555660007676866, 0.057129201160966314, 0.058725875176608655, 0.06037193722378853, 0.06205256607893728, 0.06377459831499666, 0.06555984139328941, 0.06742010622889616, 0.06929400379939055, 0.07129620154214034, 0.07343152486270688, 0.07564841051154847, 0.07793066762554623, 0.0802869299203981, 0.08271450466434516, 0.0865923896108991, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + - name: TE_SS_filler + material: medium_density_foam + thickness: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8] + values: [0.06, 0.058035714285714295, 0.05607142857142858, 0.054575895022411045, 0.05308036147339351, 0.05158482792437597, 0.050089294375358445, 0.048593760826340926, 0.04695017158918484, 0.04530658235202875, 0.04366299311487268, 0.042019403877716605, 0.04028556194373745, 0.038551720009758296, 0.03681787807577915, 0.03508403614180001, 0.03335019420782087, 0.03180246182598129, 0.030254729444141714, 0.028706997062302113, 0.027159264680462522, 0.02561153229862294, 0.024031676944498204, 0.022451821590373466, 0.020871966236248707, 0.01929211088212396, 0.017712255527999203, 0.01621337986584891, 0.014714504203698625, 0.013215628541548327, 0.01171675287939804, 0.010217877217247743, 0.008719001555097456, 0.00722012589294716, 0.005] + start_nd_arc: + fixed: TE_reinforcement + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [1.0, 1.0, 1.0, 1.0, 1.0, 0.034792275665721295, 0.03637691103593488, 0.037123734809841435, 0.03732723357498169, 0.037625286422922954, 0.03800877651079859, 0.03846089356522486, 0.039062400983301826, 0.039902201153814154, 0.04098457265677613, 0.04224286057975668, 0.04367301531335843, 0.04521532996186206, 0.04670266193855488, 0.048082936648173424, 0.04954484216383981, 0.05108519911632503, 0.0525222235265268, 0.05401513471727859, 0.05555660007676866, 0.057129201160966314, 0.058725875176608655, 0.06037193722378853, 0.06205256607893728, 0.06377459831499666, 0.06555984139328941, 0.06742010622889616, 0.06929400379939055, 0.07129620154214034, 0.07343152486270688, 0.07564841051154847, 0.07793066762554623, 0.0802869299203981, 0.08271450466434516, 0.0865923896108991, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + end_nd_arc: + fixed: Spar_Cap_SS + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.17773574415091226, 0.1821383302374979, 0.1871009332318214, 0.19249766577284227, 0.19909487385335925, 0.20737659863174399, 0.2199563361667385, 0.23350030064214203, 0.2427908278512117, 0.2510319817189903, 0.25817999326235086, 0.2640222447594565, 0.26847452041144376, 0.2723675397633476, 0.27537254893766394, 0.2774500732168921, 0.27892856252835135, 0.2801147338948552, 0.2812396202718402, 0.2825052719168474, 0.28372563674648144, 0.28484219419451967, 0.2856108338834729, 0.28616541775075416, 0.28651108512022494, 0.2865704975066126, 0.28632375439281, 0.2857054110482848, 0.28495802005810855, 0.28384007813924583, 0.28247070235731614, 0.2807878820118879, 0.2790021706772168, 0.2770373700835421, 0.2748470321944108, 0.27247329866326264, 0.26987814641494146, 0.26700229102005213, 0.26380367016835893, 0.2585962219214307, 0.2546725630541853, 0.2501832999132221, 0.24509579187321692, 0.2394959247202817, 0.23337437446593906, 0.2270055310048491, 0.22114542327869086, 0.2182172070860106, 0.2303158393899657, 0.1687033757034503] + fiber_orientation: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975] + - name: LE_SS_filler + material: medium_density_foam + thickness: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8] + values: [0.06, 0.058035714285714295, 0.05607142857142858, 0.054575895022411045, 0.05308036147339351, 0.05158482792437597, 0.050089294375358445, 0.048593760826340926, 0.04695017158918484, 0.04530658235202875, 0.04366299311487268, 0.042019403877716605, 0.04028556194373745, 0.038551720009758296, 0.03681787807577915, 0.03508403614180001, 0.03335019420782087, 0.03180246182598129, 0.030254729444141714, 0.028706997062302113, 0.027159264680462522, 0.02561153229862294, 0.024031676944498204, 0.022451821590373466, 0.020871966236248707, 0.01929211088212396, 0.017712255527999203, 0.01621337986584891, 0.014714504203698625, 0.013215628541548327, 0.01171675287939804, 0.010217877217247743, 0.008719001555097456, 0.00722012589294716, 0.005] + start_nd_arc: + fixed: Spar_Cap_SS + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.23283082909688213, 0.23714582578511298, 0.24270283977249643, 0.2498597082304933, 0.25896584329361694, 0.27000269483004224, 0.2854347760314214, 0.30032302329985644, 0.3099798482861786, 0.3187574972802518, 0.32659579098178837, 0.33325185317686107, 0.33878684218138727, 0.3441915018402132, 0.3491447797198609, 0.35348722226045426, 0.3575399900923965, 0.36150232782620695, 0.3653044117612391, 0.36905455788355945, 0.37290635264139294, 0.3767955526039047, 0.3801508362312212, 0.38339266024185553, 0.3865129652584084, 0.38940305959635185, 0.3920303297107056, 0.394374898051104, 0.39665263900019554, 0.39863435510624, 0.40047841686523705, 0.40214407322390117, 0.40373137751611976, 0.4053705328593945, 0.40702377694728326, 0.4086404375840501, 0.4101533481409244, 0.41151876487676886, 0.41268977856418043, 0.41446252322104926, 0.4156954509495021, 0.41688836508912697, 0.41810427553004403, 0.4194529930165829, 0.42097165234280315, 0.4224528900690906, 0.4230490209476922, 0.4201789285679513, 0.399162484943435, 0.450598781748058] + end_nd_arc: + fixed: LE_reinforcement + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.5000006654037845, 0.5000006654037842, 0.5002060277935696, 0.5006729174479077, 0.5011391259091965, 0.47346462151331714, 0.4718093545618548, 0.47076145665348734, 0.4701871709067392, 0.46981163253812597, 0.46943797907983936, 0.46898315286244396, 0.46840921108356154, 0.46756151597240136, 0.46639944253599985, 0.4650983204499207, 0.4637474479462891, 0.4623814213061447, 0.4610807730844251, 0.4598984578330925, 0.4586957997951826, 0.45750128840566523, 0.4564739644234709, 0.4554830835663568, 0.45451383175095833, 0.4535487072231865, 0.45255836592716747, 0.4515170177124577, 0.45044998957667665, 0.4493632184525854, 0.4482468008288618, 0.4470925325450485, 0.44595052036636745, 0.44475327494726175, 0.4435528144192485, 0.4424682269600974, 0.4418972477649065, 0.44183748532653727, 0.4422768823612798, 0.44254520962559685, 0.5031598823532262, 0.5031598823532264, 0.5031598823532264, 0.5031598823532264, 0.503159882353226, 0.5031598823532264, 0.5031598823532263, 0.5031598823532261, 0.5031598823532264, 0.5031598823532263] + fiber_orientation: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975] + - name: LE_PS_filler + material: medium_density_foam + thickness: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8] + values: [0.06, 0.058035714285714295, 0.05607142857142858, 0.054575895022411045, 0.05308036147339351, 0.05158482792437597, 0.050089294375358445, 0.048593760826340926, 0.04695017158918484, 0.04530658235202875, 0.04366299311487268, 0.042019403877716605, 0.04028556194373745, 0.038551720009758296, 0.03681787807577915, 0.03508403614180001, 0.03335019420782087, 0.03180246182598129, 0.030254729444141714, 0.028706997062302113, 0.027159264680462522, 0.02561153229862294, 0.024031676944498204, 0.022451821590373466, 0.020871966236248707, 0.01929211088212396, 0.017712255527999203, 0.01621337986584891, 0.014714504203698625, 0.013215628541548327, 0.01171675287939804, 0.010217877217247743, 0.008719001555097456, 0.00722012589294716, 0.005] + start_nd_arc: + fixed: LE_reinforcement + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.5000006654037845, 0.5000006654037842, 0.5002060277935696, 0.5006729174479077, 0.5011391259091965, 0.5291322625784711, 0.5300124122193507, 0.5301594323492335, 0.5299107446267098, 0.5300120908148028, 0.5302520214971173, 0.5305205825668036, 0.5309090526568446, 0.5314050378185041, 0.5319747587868415, 0.5326868973775315, 0.5336242724476626, 0.5347259492451241, 0.535805032186113, 0.53683115647017, 0.5379675472573262, 0.5392376069917852, 0.5405095220659138, 0.5419072991140025, 0.5434043918737881, 0.5449554290807325, 0.5465197662097414, 0.5481121172705192, 0.5497340953029762, 0.5514025757565802, 0.5531425470581248, 0.5549647025112825, 0.5568162147596158, 0.5587999286456888, 0.5608022930052419, 0.5626370123602913, 0.5638345482721141, 0.5643234669632766, 0.5640428823451725, 0.5637745550808557, 0.5031598823532262, 0.5031598823532264, 0.5031598823532264, 0.5031598823532264, 0.503159882353226, 0.5031598823532264, 0.5031598823532263, 0.5031598823532261, 0.5031598823532264, 0.5031598823532263] + end_nd_arc: + fixed: Spar_Cap_PS + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.6805338907416869, 0.6762385706910707, 0.6730711171317508, 0.6720853702156632, 0.6715238147243324, 0.669935389335444, 0.6655671806707737, 0.6593685054871514, 0.6542602412429819, 0.6497245342820102, 0.6454069412689026, 0.6414525764647957, 0.638054167874096, 0.6347842632835325, 0.6316279880452333, 0.6288379737893852, 0.6262727455681317, 0.6238260766815458, 0.6213782858463365, 0.6188336115785243, 0.6161050675653428, 0.6132734389653898, 0.6108151075104965, 0.6084315592635592, 0.6061360135894776, 0.6040465000196825, 0.6021893219907264, 0.6005759965995441, 0.5989893575552869, 0.597678693729204, 0.5965148532340123, 0.5955479758414505, 0.5946638354193096, 0.5937850706722259, 0.5929333561343686, 0.5921546654447176, 0.5914841613577797, 0.5908616137911897, 0.5902818381435507, 0.5891481668469944, 0.5882190142913776, 0.587127116089011, 0.5859273639036329, 0.5845492608597633, 0.5829528707258185, 0.5813428646646273, 0.5805691826718496, 0.5831933888206003, 0.6037661157825593, 0.5517514618941431] + fiber_orientation: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975] + - name: TE_PS_filler + material: medium_density_foam + thickness: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8] + values: [0.06, 0.058035714285714295, 0.05607142857142858, 0.054575895022411045, 0.05308036147339351, 0.05158482792437597, 0.050089294375358445, 0.048593760826340926, 0.04695017158918484, 0.04530658235202875, 0.04366299311487268, 0.042019403877716605, 0.04028556194373745, 0.038551720009758296, 0.03681787807577915, 0.03508403614180001, 0.03335019420782087, 0.03180246182598129, 0.030254729444141714, 0.028706997062302113, 0.027159264680462522, 0.02561153229862294, 0.024031676944498204, 0.022451821590373466, 0.020871966236248707, 0.01929211088212396, 0.017712255527999203, 0.01621337986584891, 0.014714504203698625, 0.013215628541548327, 0.01171675287939804, 0.010217877217247743, 0.008719001555097456, 0.00722012589294716, 0.005] + start_nd_arc: + fixed: Spar_Cap_PS + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.7356289756876568, 0.7312460662386859, 0.7286730236724258, 0.7294474126733143, 0.7313947841645901, 0.7325614855337423, 0.7310456205354565, 0.7261912281448659, 0.7214492616779489, 0.7174500498432717, 0.7138227389883401, 0.7106821848822004, 0.7083664896440395, 0.706608225360398, 0.7054002188274302, 0.7048751228329473, 0.704884173132177, 0.7052136706128975, 0.7054430773357353, 0.7053828975452364, 0.7052857834602544, 0.7052267973747748, 0.7053551098582448, 0.7056588017546606, 0.706137893727661, 0.7068790621094218, 0.707895897308622, 0.7092454836023632, 0.710683976497374, 0.7124729706961983, 0.7145225677419331, 0.7169041670534638, 0.7193930422582125, 0.7221182334480782, 0.7251101008872411, 0.7283218043655051, 0.7317593630837627, 0.7353780876479066, 0.7391679465393722, 0.7450144681466131, 0.7492419021866944, 0.7538321812649158, 0.75893584756046, 0.7645063291560645, 0.7705501486026827, 0.7767902237288686, 0.782472780340851, 0.785155110302541, 0.7726127613360285, 0.8336468679387509] + end_nd_arc: + fixed: TE_reinforcement + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [1.0, 1.0, 1.0, 1.0, 1.0, 0.9652077243342787, 0.963623088964065, 0.9628762651901587, 0.9626727664250184, 0.9623747135770769, 0.9619912234892014, 0.9615391064347752, 0.9609375990166981, 0.9600977988461858, 0.9590154273432239, 0.9577571394202432, 0.9563269846866416, 0.9547846700381379, 0.953297338061445, 0.9519170633518266, 0.9504551578361603, 0.948914800883675, 0.9474777764734732, 0.9459848652827214, 0.9444433999232313, 0.9428707988390338, 0.9412741248233913, 0.9396280627762116, 0.9379474339210627, 0.9362254016850032, 0.9344401586067106, 0.9325798937711037, 0.9307059962006095, 0.9287037984578598, 0.9265684751372931, 0.9243515894884514, 0.9220693323744539, 0.9197130700796018, 0.9172854953356547, 0.9134076103891008, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + fiber_orientation: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975] + - name: Shell_skin_inner + material: glass_triax + fiber_orientation: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + thickness: + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + values: [0.05, 0.044591322442333034, 0.03941021393803537, 0.03441462790857325, 0.029675608520261305, 0.025, 0.018746440238161553, 0.012935658206036059, 0.009215755156010518, 0.006529885804404432, 0.004719386872874692, 0.0036740071088946866, 0.0030103299903238034, 0.0024577650586213515, 0.002138757820887468, 0.0020411160305407556, 0.002001744944261837, 0.002000350046275909, 0.0020000346243765573, 0.0020000022429495484, 0.002000000000106529, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.002, 0.0018889580666709893, 0.0016806403193428728, 0.0013888318819373973, 0.001170340950441993, 0.0010489579419444472, 0.001008605037770434, 0.001001324884824214, 0.001, 0.001, 0.001] + start_nd_arc: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + end_nd_arc: + values: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] + grid: [0.0, 0.02, 0.04, 0.06, 0.08, 0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95, 0.975, 1.0] + - name: web0_skinLE + material: glass_biax + web: web0 + thickness: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95] + values: [0.002, 0.001955882352941177, 0.0019117647058823533, 0.0018781751823215315, 0.0018445856587607098, 0.001810996135199888, 0.0017774066116390667, 0.0017438170880782456, 0.0017069022495966651, 0.0016699874111150846, 0.0016330725726335042, 0.0015961577341519235, 0.0015572158297523914, 0.0015182739253528598, 0.0014793320209533286, 0.0014403901165537974, 0.0014014482121542657, 0.0013666863083910236, 0.0013319244046277818, 0.0012971625008645394, 0.0012624005971012973, 0.0012276386933380555, 0.0011921553110529005, 0.0011566719287677458, 0.001121188546482591, 0.0010857051641974364, 0.0010502217819122813, 0.0010165571948479433, 0.000982892607783605, 0.0009492280207192671, 0.000915563433654929, 0.0008818988465905908, 0.0008482342595262528, 0.0008145696724619145, 0.0007647058823529412, 0.0007316176470588235, 0.0006985294117647059, 0.0006654411764705883, 0.0006323529411764706, 0.0005992647058823529, 0.0005661764705882353, 0.0005330882352941178, 0.0005000000000000001] + fiber_orientation: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95] + - name: web0_filler + material: medium_density_foam + web: web0 + thickness: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95] + values: [0.043, 0.04188235294117647, 0.04076470588235294, 0.039913771285478794, 0.03906283668860465, 0.038211902091730504, 0.03736096749485635, 0.0365100328979822, 0.03557485698978217, 0.034639681081582135, 0.03370450517338209, 0.03276932926518204, 0.031782801020393915, 0.03079627277560579, 0.029809744530817663, 0.02882321628602953, 0.027836688041241398, 0.026956053145905937, 0.02607541825057048, 0.025194783355235006, 0.024314148459899543, 0.02343351356456408, 0.022534601213340156, 0.021635688862116233, 0.020736776510892303, 0.019837864159668383, 0.01893895180844446, 0.01808611560281456, 0.017233279397184664, 0.016380443191554763, 0.015527606985924867, 0.014674770780294964, 0.013821934574665068, 0.012969098369035169, 0.011705882352941174, 0.010867647058823525, 0.01002941176470588, 0.009191176470588236, 0.008352941176470586, 0.0075147058823529355, 0.006676470588235292, 0.005838235294117648, 0.005000000000000001] + fiber_orientation: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95] + - name: web0_skinTE + material: glass_biax + web: web0 + thickness: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95] + values: [0.002, 0.001955882352941177, 0.0019117647058823533, 0.0018781751823215315, 0.0018445856587607098, 0.001810996135199888, 0.0017774066116390667, 0.0017438170880782456, 0.0017069022495966651, 0.0016699874111150846, 0.0016330725726335042, 0.0015961577341519235, 0.0015572158297523914, 0.0015182739253528598, 0.0014793320209533286, 0.0014403901165537974, 0.0014014482121542657, 0.0013666863083910236, 0.0013319244046277818, 0.0012971625008645394, 0.0012624005971012973, 0.0012276386933380555, 0.0011921553110529005, 0.0011566719287677458, 0.001121188546482591, 0.0010857051641974364, 0.0010502217819122813, 0.0010165571948479433, 0.000982892607783605, 0.0009492280207192671, 0.000915563433654929, 0.0008818988465905908, 0.0008482342595262528, 0.0008145696724619145, 0.0007647058823529412, 0.0007316176470588235, 0.0006985294117647059, 0.0006654411764705883, 0.0006323529411764706, 0.0005992647058823529, 0.0005661764705882353, 0.0005330882352941178, 0.0005000000000000001] + fiber_orientation: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95] + - name: web1_skinLE + material: glass_biax + web: web1 + thickness: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95] + values: [0.002, 0.001955882352941177, 0.0019117647058823533, 0.0018781751823215315, 0.0018445856587607098, 0.001810996135199888, 0.0017774066116390667, 0.0017438170880782456, 0.0017069022495966651, 0.0016699874111150846, 0.0016330725726335042, 0.0015961577341519235, 0.0015572158297523914, 0.0015182739253528598, 0.0014793320209533286, 0.0014403901165537974, 0.0014014482121542657, 0.0013666863083910236, 0.0013319244046277818, 0.0012971625008645394, 0.0012624005971012973, 0.0012276386933380555, 0.0011921553110529005, 0.0011566719287677458, 0.001121188546482591, 0.0010857051641974364, 0.0010502217819122813, 0.0010165571948479433, 0.000982892607783605, 0.0009492280207192671, 0.000915563433654929, 0.0008818988465905908, 0.0008482342595262528, 0.0008145696724619145, 0.0007647058823529412, 0.0007316176470588235, 0.0006985294117647059, 0.0006654411764705883, 0.0006323529411764706, 0.0005992647058823529, 0.0005661764705882353, 0.0005330882352941178, 0.0005000000000000001] + fiber_orientation: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95] + - name: web1_filler + material: medium_density_foam + web: web1 + thickness: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95] + values: [0.043, 0.04188235294117647, 0.04076470588235294, 0.039913771285478794, 0.03906283668860465, 0.038211902091730504, 0.03736096749485635, 0.0365100328979822, 0.03557485698978217, 0.034639681081582135, 0.03370450517338209, 0.03276932926518204, 0.031782801020393915, 0.03079627277560579, 0.029809744530817663, 0.02882321628602953, 0.027836688041241398, 0.026956053145905937, 0.02607541825057048, 0.025194783355235006, 0.024314148459899543, 0.02343351356456408, 0.022534601213340156, 0.021635688862116233, 0.020736776510892303, 0.019837864159668383, 0.01893895180844446, 0.01808611560281456, 0.017233279397184664, 0.016380443191554763, 0.015527606985924867, 0.014674770780294964, 0.013821934574665068, 0.012969098369035169, 0.011705882352941174, 0.010867647058823525, 0.01002941176470588, 0.009191176470588236, 0.008352941176470586, 0.0075147058823529355, 0.006676470588235292, 0.005838235294117648, 0.005000000000000001] + fiber_orientation: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95] + - name: web1_skinTE + material: glass_biax + web: web1 + thickness: + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95] + values: [0.002, 0.001955882352941177, 0.0019117647058823533, 0.0018781751823215315, 0.0018445856587607098, 0.001810996135199888, 0.0017774066116390667, 0.0017438170880782456, 0.0017069022495966651, 0.0016699874111150846, 0.0016330725726335042, 0.0015961577341519235, 0.0015572158297523914, 0.0015182739253528598, 0.0014793320209533286, 0.0014403901165537974, 0.0014014482121542657, 0.0013666863083910236, 0.0013319244046277818, 0.0012971625008645394, 0.0012624005971012973, 0.0012276386933380555, 0.0011921553110529005, 0.0011566719287677458, 0.001121188546482591, 0.0010857051641974364, 0.0010502217819122813, 0.0010165571948479433, 0.000982892607783605, 0.0009492280207192671, 0.000915563433654929, 0.0008818988465905908, 0.0008482342595262528, 0.0008145696724619145, 0.0007647058823529412, 0.0007316176470588235, 0.0006985294117647059, 0.0006654411764705883, 0.0006323529411764706, 0.0005992647058823529, 0.0005661764705882353, 0.0005330882352941178, 0.0005000000000000001] + fiber_orientation: + values: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + grid: [0.1, 0.125, 0.15, 0.1690340633511322, 0.18806812670226436, 0.20710219005339656, 0.22613625340452875, 0.24517031675566095, 0.2660887252285566, 0.2870071337014522, 0.30792554217434787, 0.3288439506472435, 0.350911029806978, 0.3729781089667125, 0.39504518812644707, 0.41711226728618156, 0.4391793464459161, 0.45887775857841995, 0.47857617071092373, 0.49827458284342757, 0.5179729949759314, 0.5376714071084352, 0.5577786570700229, 0.5778859070316106, 0.5979931569931983, 0.618100406954786, 0.6382076569163737, 0.657284256252832, 0.6763608555892903, 0.6954374549257486, 0.7145140542622068, 0.7335906535986652, 0.7526672529351234, 0.7717438522715817, 0.8, 0.8187500000000001, 0.8375, 0.85625, 0.875, 0.89375, 0.9125, 0.9312499999999999, 0.95] + + hub: + diameter: 7.94 + cone_angle: 0.06981317007977318 # 4 deg + drag_coefficient: 0.5 + flange_t2shell_t: 6.0 + flange_OD2hub_D: 0.6 + flange_ID2OD: 0.8 + hub_blade_spacing_margin: 1.2 + hub_stress_concentration: 3.0 + n_front_brackets: 5 + n_rear_brackets: 5 + clearance_hub_spinner: 0.5 + spin_hole_incr: 1.2 + pitch_system_scaling_factor: 0.75 + spinner_gust_ws: 70.0 + hub_material: cast_iron + spinner_material: glass_uni + nacelle: + drivetrain: + uptilt: 0.10471975511965977 # 6 deg + distance_tt_hub: 5.614 + overhang: 12.0313 + drag_coefficient: 0.5 + distance_hub2mb: 1.0 + distance_mb2mb: 1.2 + lss_diameter: [3.0, 3.0] + lss_wall_thickness: [0.1, 0.1] + nose_diameter: [2.2, 2.2] + nose_wall_thickness: [0.1, 0.1] + generator_length: 2.15 + bedplate_wall_thickness: + grid: [0.0, 1.0] + values: [0.05, 0.05] + gear_ratio: 1.0 + gearbox_efficiency: 1.0 + mb1Type: CARB + mb2Type: SRB + uptower: True + lss_material: steel_drive + bedplate_material: steel_drive + generator: + generator_type: PMSG_Outer + rho_Fe: 7700.0 + rho_Fes: 7850.0 + rho_Copper: 8900.0 + rho_PM: 7450.0 + B_r: 1.279 + P_Fe0e: 1.0 + P_Fe0h: 4.0 + S_N: -0.002 + alpha_p: 1.0995574287564276 #0.5*np.pi*0.7 + b_r_tau_r: 0.45 + b_ro: 0.004 + b_s_tau_s: 0.45 + b_so: 0.004 + cofi: 0.9 + freq: 60.0 + h_i: 0.004 + h_sy0: 0.0 + h_w: 0.005 + k_fes: 0.8 + k_fillr: 0.55 + k_fills: 0.65 + k_s: 0.2 + m: 3 + mu_0: 1.2566370614359173e-06 #np.pi*4e-7 + mu_r: 1.06 + phi: 1.5707963267948966 # 90 deg + q1: 5 + q2: 4 + ratio_mw2pp: 0.8 + resist_Cu: 2.52e-8 #1.8e-8*1.4 + y_tau_pr: 0.8333333 #10. / 12 + y_tau_p: 0.8 #12./15. + rad_ag: 5.12623359 + len_s: 2.23961662 + tau_p: 0.1610453779 + tau_s: 0.1339360726 + h_s: 0.3769449149 + b_s: 0.05121796888 + b_t: 0.08159225451 + h_t: 0.3859449149 + h_ys: 0.03618114528 + h_yr: 0.0361759511 + h_m: 0.0995385122 + b_m: 0.1288363023 + B_g: 1.38963289 + B_symax: 1.63455514 + B_rymax: 1.63478983 + p: 100 + R_s: 0.02457052 + L_s: 0.01138752 + S: 240 + t_r: 0.0607657125 + h_sr: 0.04677116325 + t_s: 0.06065213554 + h_ss: 0.04650825863 + E_p: 1905.2558883257652 + b: 2.0 + c: 5.0 + N_c: 2 + B_tmax: 1.9 + u_allow_pcent: 8.5 + y_allow_pcent: 1.0 + z_allow_deg: 0.05 + sigma: 60e3 + + h_0: 0.05 + C_Cu: 4.786 + C_Fe: 0.556 + C_Fes: 0.50139 + C_PM: 50.0 + # mass: + # generator: 371.95 + # turret: 13.362 + # bedplate: 39.434 + # shaft: 19.504 + # main_bearing: 4.699 + # flange: 3.627 + # yaw_system: 100.0 + # other: 50.0 + # blade: 65.2535 + # hub: 190.0 + # nacelle_other: 100.0 + # nacelle: 797.275 + # rna: 993.035 + # rna_center_of_mass: [-6.853, 0.0, 4.351] + # tower: 803.5 + # monopile: 1138.0 + tower: + outer_shape_bem: + reference_axis: &ref_axis_tower + x: + grid: [0.0, 1.0] + values: [0.0, 0.0] + y: + grid: [0.0, 1.0] + values: [0.0, 0.0] + z: + grid: &grid_tower [0.0, 7.728811463372998e-05, 0.10047454902385111, 0.10055183713848485, 0.20094909804770222, 0.20102638616233595, 0.30142364707155334, 0.30150093518618704, 0.40189819609540445, 0.40197548421003826, 0.5023727451192556, 0.5024500332338894, 0.6028472941431067, 0.6029245822577405, 0.7033218431669578, 0.7033991312815916, 0.8037963921908089, 0.8038736803054427, 0.90427094121466, 0.9043482293292937, 1.0] + values: [15, 15.01, 28, 28.01, 41, 41.01, 54, 54.01, 67, 67.01, 80, 80.01, 93, 93.01, 106, 106.01, 119, 119.01, 132, 132.01, 144.386] + outer_diameter: + grid: *grid_tower + values: [10., 10., 10., 10., 9.926, 9.926, 9.443, 9.443, 8.833, 8.833, 8.151, 8.151, 7.39, 7.39, 6.909, 6.909, 6.748, 6.748, 6.572, 6.572, 6.5] + drag_coefficient: + grid: [0.0, 1.0] + values: [0.5, 0.5] + internal_structure_2d_fem: + outfitting_factor: 1.07 + reference_axis: *ref_axis_tower + layers: + - name: tower_wall + material: steel + thickness: + grid: *grid_tower + values: [0.041058, 0.039496, 0.039496, 0.036456, 0.036456, 0.033779, 0.033779, 0.032192, 0.032192, 0.030708, 0.030708, 0.029101, 0.029101, 0.027213, 0.027213, 0.024009, 0.024009, 0.020826, 0.020826, 0.023998, 0.023998] + monopile: + transition_piece_mass: 100e3 + outer_shape_bem: + reference_axis: &ref_axis_mono + x: + grid: [0.0, 1.0] + values: [0.0, 0.0] + y: + grid: [0.0, 1.0] + values: [0.0, 0.0] + z: + grid: &grid_mono [0.0, 0.5, 0.50001111, 0.55555556, 0.55556667, 0.61111111, 0.61112222, 0.66666667, 0.66667778, 0.72222222, 0.72223333, 0.77777778, 0.77778889, 0.83333333, 0.83334444, 0.88888889, 0.8889 , 0.94444444, 0.94445556, 1.0] + values: [-75, -30., -29.999, -25., -24.999, -20., -19.999, -15., -14.999, -10., -9.999, -5., -4.999, 0., 0.001, 5., 5.001, 10., 10.001, 15.] + outer_diameter: + grid: *grid_mono + values: [10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10., 10.] + drag_coefficient: + grid: [0.0, 1.0] + values: [0.5, 0.5] + internal_structure_2d_fem: + outfitting_factor: 1.07 + reference_axis: *ref_axis_mono + layers: + - name: monopile_wall + material: steel + thickness: + grid: *grid_mono + values: [0.055341, 0.055341, 0.055341, 0.055341, 0.053449, 0.053449, 0.051509, 0.051509, 0.049527, 0.049527, 0.047517, 0.047517, 0.045517, 0.045517, 0.043527, 0.043527, 0.042242, 0.042242, 0.041058, 0.041058] + +airfoils: + - name: circular + coordinates: + x: [1.00000, 0.99901, 0.99606, 0.99114, 0.98429, 0.97553, 0.96489, 0.95241, 0.93815, 0.92216, 0.90451, 0.88526, 0.86448, 0.84227, 0.81871, 0.79389, 0.76791, 0.74088, 0.71289, 0.68406, 0.65451, 0.62434, 0.59369, 0.56267, 0.53140, 0.50000, 0.46860, 0.43733, 0.40631, 0.37566, 0.34549, 0.31594, 0.28711, 0.25912, 0.23209, 0.20611, 0.18129, 0.15773, 0.13552, 0.11474, 0.09549, 0.07784, 0.06185, 0.04759, 0.03511, 0.02447, 0.01571, 0.00886, 0.00394, 0.00099, 0.00000, 0.00099, 0.00394, 0.00886, 0.01571, 0.02447, 0.03511, 0.04759, 0.06185, 0.07784, 0.09549, 0.11474, 0.13552, 0.15773, 0.18129, 0.20611, 0.23209, 0.25912, 0.28711, 0.31594, 0.34549, 0.37566, 0.40631, 0.43733, 0.46860, 0.50000, 0.53140, 0.56267, 0.59369, 0.62434, 0.65451, 0.68406, 0.71289, 0.74088, 0.76791, 0.79389, 0.81871, 0.84227, 0.86448, 0.88526, 0.90451, 0.92216, 0.93815, 0.95241, 0.96489, 0.97553, 0.98429, 0.99114, 0.99606, 0.99901, 1.00000] + y: [0.00000, 0.03140, 0.06267, 0.09369, 0.12434, 0.15451, 0.18406, 0.21289, 0.24088, 0.26791, 0.29389, 0.31871, 0.34227, 0.36448, 0.38526, 0.40451, 0.42216, 0.43815, 0.45241, 0.46489, 0.47553, 0.48429, 0.49114, 0.49606, 0.49901, 0.50000, 0.49901, 0.49606, 0.49114, 0.48429, 0.47553, 0.46489, 0.45241, 0.43815, 0.42216, 0.40451, 0.38526, 0.36448, 0.34227, 0.31871, 0.29389, 0.26791, 0.24088, 0.21289, 0.18406, 0.15451, 0.12434, 0.09369, 0.06267, 0.03140, 0.00000, -0.03140, -0.06267, -0.09369, -0.12434, -0.15451, -0.18406, -0.21289, -0.24088, -0.26791, -0.29389, -0.31871, -0.34227, -0.36448, -0.38526, -0.40451, -0.42216, -0.43815, -0.45241, -0.46489, -0.47553, -0.48429, -0.49114, -0.49606, -0.49901, -0.50000, -0.49901, -0.49606, -0.49114, -0.48429, -0.47553, -0.46489, -0.45241, -0.43815, -0.42216, -0.40451, -0.38526, -0.36448, -0.34227, -0.31871, -0.29389, -0.26791, -0.24088, -0.21289, -0.18406, -0.15451, -0.12434, -0.09369, -0.06267, -0.03140, 0.00000] + relative_thickness: 1.0000 + aerodynamic_center: 0.5000 + polars: + - configuration: Default + re: 3.00E+06 + c_l: + grid: &grid005 [-3.14, 3.14] + values: [0.0001, 0.0001] + c_d: + grid: *grid005 + values: [0.35, 0.35] + c_m: + grid: *grid005 + values: [-0.0001, -0.0001] + - name: SNL-FFA-W3-500 + coordinates: + x: [1.00000000, 0.99944304, 0.99812049, 0.99569352, 0.99230484, 0.98802844, 0.98281508, 0.97666422, 0.96964069, 0.96174313, 0.95297315, 0.94338928, 0.93301284, 0.92185147, 0.90995468, 0.89736121, 0.88408503, 0.87016290, 0.85565276, 0.84057695, 0.82497463, 0.80889455, 0.79236237, 0.77542101, 0.75812546, 0.74050180, 0.72259209, 0.70444539, 0.68608843, 0.66757021, 0.64892678, 0.63018643, 0.61140138, 0.59259673, 0.57380843, 0.55507570, 0.53641763, 0.51787958, 0.49948103, 0.48125155, 0.46322225, 0.44540666, 0.42784323, 0.41053864, 0.39352525, 0.37681123, 0.36041977, 0.34436494, 0.32865846, 0.31331898, 0.29834798, 0.28376580, 0.26956679, 0.25577362, 0.24237780, 0.22939648, 0.21681735, 0.20465763, 0.19290757, 0.18157496, 0.17065819, 0.16014896, 0.15005511, 0.14035465, 0.13106750, 0.12216148, 0.11365876, 0.10553619, 0.09779065, 0.09042902, 0.08341621, 0.07677403, 0.07046920, 0.06450016, 0.05888182, 0.05356799, 0.04857581, 0.04389793, 0.03949498, 0.03539484, 0.03157626, 0.02800644, 0.02471592, 0.02168071, 0.01886319, 0.01629514, 0.01396620, 0.01181764, 0.00988361, 0.00818368, 0.00663128, 0.00524853, 0.00408271, 0.00308998, 0.00219098, 0.00145967, 0.00096333, 0.00059878, 0.00028988, 0.00007804, 0.00000000, 0.00007807, 0.00029009, 0.00059937, 0.00096448, 0.00146264, 0.00219661, 0.00309879, 0.00409516, 0.00526774, 0.00665839, 0.00821941, 0.00993095, 0.01187982, 0.01404463, 0.01639219, 0.01898469, 0.02182867, 0.02489252, 0.02822001, 0.03182924, 0.03568998, 0.03984236, 0.04430035, 0.04903788, 0.05410025, 0.05948747, 0.06518787, 0.07124791, 0.07764648, 0.08439704, 0.09152340, 0.09900711, 0.10688721, 0.11514762, 0.12380644, 0.13287211, 0.14233176, 0.15221460, 0.16249918, 0.17321393, 0.18434125, 0.19590296, 0.20788328, 0.22029378, 0.23312344, 0.24637487, 0.26004146, 0.27412439, 0.28861129, 0.30349962, 0.31877410, 0.33443448, 0.35045732, 0.36684322, 0.38356093, 0.40060975, 0.41795607, 0.43559330, 0.45349250, 0.47163211, 0.48999236, 0.50853595, 0.52724867, 0.54608860, 0.56503090, 0.58404504, 0.60308800, 0.62213765, 0.64114752, 0.66008031, 0.67890619, 0.69757164, 0.71604492, 0.73429135, 0.75225234, 0.76989792, 0.78719153, 0.80407383, 0.82051349, 0.83646946, 0.85189026, 0.86674791, 0.88100970, 0.89461041, 0.90752456, 0.91973040, 0.93117530, 0.94182765, 0.95167536, 0.96067486, 0.96878747, 0.97601191, 0.98233053, 0.98768615, 0.99208631, 0.99557391, 0.99806302, 0.99942968, 1.00000000] + y: [0.01050000, 0.01086983, 0.01174520, 0.01334146, 0.01554892, 0.01830045, 0.02160574, 0.02544031, 0.02973944, 0.03448209, 0.03964881, 0.04519300, 0.05091599, 0.05708281, 0.06358138, 0.07042743, 0.07734661, 0.08467081, 0.09218020, 0.09990118, 0.10756554, 0.11533963, 0.12314445, 0.13105863, 0.13880784, 0.14656091, 0.15414446, 0.16162243, 0.16907630, 0.17621938, 0.18309227, 0.18987002, 0.19625049, 0.20235037, 0.20831587, 0.21376127, 0.21902667, 0.22392548, 0.22835814, 0.23243074, 0.23602942, 0.23922962, 0.24206676, 0.24439517, 0.24632908, 0.24781218, 0.24907553, 0.24961759, 0.25000000, 0.24991460, 0.24940354, 0.24844020, 0.24723387, 0.24555930, 0.24358422, 0.24124765, 0.23860390, 0.23566026, 0.23243210, 0.22883962, 0.22496679, 0.22083239, 0.21649585, 0.21197874, 0.20722821, 0.20232328, 0.19717400, 0.19203678, 0.18655863, 0.18109338, 0.17545947, 0.16968205, 0.16378555, 0.15776490, 0.15169879, 0.14556478, 0.13948626, 0.13333780, 0.12704970, 0.12086576, 0.11468985, 0.10849184, 0.10222460, 0.09589286, 0.08980724, 0.08374779, 0.07773838, 0.07187918, 0.06601030, 0.06018911, 0.05390950, 0.04732699, 0.04014879, 0.03372622, 0.02591034, 0.02003190, 0.01481159, 0.01096429, 0.00515421, 0.00237777, 0.00000000, -0.00676349, -0.00927010, -0.01485268, -0.01909805, -0.02425157, -0.03028001, -0.03749177, -0.04339708, -0.04974300, -0.05593635, -0.06191302, -0.06758273, -0.07340171, -0.07913246, -0.08498169, -0.09099183, -0.09692059, -0.10302984, -0.10915375, -0.11531066, -0.12113778, -0.12714727, -0.13319857, -0.13898026, -0.14485030, -0.15067474, -0.15648787, -0.16231789, -0.16795081, -0.17355519, -0.17904803, -0.18436086, -0.18964752, -0.19466991, -0.19967718, -0.20448577, -0.20911273, -0.21356541, -0.21790461, -0.22189862, -0.22585022, -0.22942761, -0.23282521, -0.23594152, -0.23867787, -0.24127615, -0.24342871, -0.24536479, -0.24668824, -0.24775175, -0.24840680, -0.24870000, -0.24831476, -0.24764160, -0.24639477, -0.24454631, -0.24221768, -0.23936461, -0.23593587, -0.23173768, -0.22714767, -0.22177388, -0.21593576, -0.20951266, -0.20242622, -0.19484392, -0.18680862, -0.17830670, -0.16936964, -0.16006127, -0.15031319, -0.14049402, -0.13044955, -0.12028637, -0.11020462, -0.10038818, -0.09073070, -0.08140578, -0.07271636, -0.06444497, -0.05666457, -0.04953952, -0.04299056, -0.03713052, -0.03197890, -0.02747518, -0.02370172, -0.02037524, -0.01799359, -0.01593023, -0.01421422, -0.01283805, -0.01177726, -0.01099873, -0.01045135, -0.01008163, -0.00985456, -0.00974345, -0.00970000] + relative_thickness: 0.500 + aerodynamic_center: 0.316 + polars: + - configuration: Default + re: 8.10E+06 + c_l: + grid: &grid006_1 [-3.141, -2.96706, -2.79253, -2.61799, -2.44346, -2.26893, -2.09440, -1.91986, -1.74533, -1.57080, -1.39626, -1.22173, -1.04720, -0.87266, -0.69813, -0.52360, -0.34907, -0.34470, -0.33598, -0.33161, -0.32725, -0.32289, -0.31852, -0.31416, -0.30543, -0.30107, -0.29671, -0.29234, -0.28798, -0.28362, -0.27925, -0.27489, -0.27053, -0.26616, -0.25744, -0.25307, -0.24871, -0.24435, -0.23998, -0.23562, -0.23126, -0.22689, -0.22253, -0.21817, -0.21380, -0.20944, -0.20508, -0.20071, -0.19199, -0.18762, -0.18326, -0.17890, -0.17453, -0.17017, -0.16581, -0.16144, -0.15708, -0.15272, -0.14835, -0.14399, -0.13963, -0.13526, -0.13090, -0.12654, -0.12217, -0.11781, -0.11345, -0.10908, -0.10472, -0.10036, -0.09599, -0.09163, -0.08727, -0.08290, -0.07854, -0.07418, -0.06981, -0.06545, -0.06109, -0.05672, -0.05236, -0.04800, -0.04363, -0.03927, -0.03491, -0.03054, -0.02618, -0.02182, -0.01745, -0.01309, -0.00873, -0.00436, 0.00000, 0.00401, 0.00820, 0.01222, 0.01641, 0.02042, 0.02443, 0.02862, 0.03264, 0.03683, 0.04084, 0.04485, 0.04904, 0.05306, 0.05725, 0.06126, 0.06528, 0.06946, 0.07348, 0.07767, 0.08168, 0.08570, 0.08988, 0.09390, 0.09809, 0.10210, 0.10612, 0.11030, 0.11432, 0.11851, 0.12252, 0.12654, 0.13073, 0.13474, 0.13893, 0.14294, 0.14696, 0.15115, 0.15516, 0.15917, 0.17244, 0.18570, 0.19897, 0.21206, 0.22532, 0.23859, 0.25168, 0.26494, 0.27821, 0.29130, 0.30456, 0.31782, 0.33109, 0.34418, 0.35744, 0.37071, 0.38380, 0.39706, 0.41033, 0.42342, 0.43668, 0.44995, 0.46304, 0.47630, 0.48956, 0.50283, 0.51592, 0.52918, 0.54245, 0.55554, 0.56880, 0.58207, 0.59516, 0.60842, 0.62169, 0.63495, 0.64804, 0.66131, 0.67457, 0.68766, 0.70092, 0.71419, 0.72728, 0.74054, 0.75381, 0.76690, 0.78016, 0.79343, 0.80669, 0.81978, 0.83305, 0.84631, 0.85940, 0.87266, 1.04720, 1.22173, 1.39626, 1.57080, 1.74533, 1.91986, 2.09440, 2.26893, 2.44346, 2.61799, 2.79253, 2.96706, 3.141] + values: [0.0000, 0.4419, 0.8837, 0.9674, 0.7801, 0.6293, 0.4785, 0.3189, 0.1553, 0.0000, -0.1553, -0.3189, -0.4784, -0.6293, -0.7801, -0.9674, -1.0281, -1.0243, -1.0052, -0.9971, -1.0052, -0.9995, -0.9908, -0.9815, -0.9764, -0.9705, -0.9655, -0.9662, -0.9544, -0.9444, -0.9405, -0.9433, -0.9330, -0.9211, -0.9158, -0.9070, -0.8959, -0.8926, -0.8808, -0.8722, -0.8660, -0.8626, -0.8489, -0.8363, -0.8363, -0.8271, -0.8141, -0.8004, -0.7890, -0.7862, -0.7747, -0.7701, -0.7674, -0.7506, -0.7290, -0.7095, -0.6855, -0.6590, -0.6319, -0.6019, -0.5718, -0.5424, -0.5098, -0.4767, -0.4454, -0.4142, -0.3791, -0.3460, -0.3144, -0.2817, -0.2461, -0.2133, -0.1827, -0.1494, -0.1158, -0.0837, -0.0529, -0.0225, 0.0089, 0.0392, 0.0686, 0.0974, 0.1260, 0.1555, 0.1853, 0.2146, 0.2430, 0.2713, 0.3006, 0.3295, 0.3578, 0.3857, 0.4135, 0.4425, 0.4715, 0.5003, 0.5286, 0.5567, 0.5850, 0.6135, 0.6417, 0.6697, 0.6975, 0.7251, 0.7528, 0.7807, 0.8083, 0.8358, 0.8631, 0.8902, 0.9173, 0.9444, 0.9713, 0.9981, 1.0249, 1.0515, 1.0779, 1.1041, 1.1302, 1.1560, 1.1818, 1.2076, 1.2334, 1.2589, 1.2841, 1.3088, 1.3331, 1.3570, 1.3810, 1.4054, 1.4295, 1.4531, 1.5154, 1.5749, 1.6151, 1.6443, 1.6824, 1.7146, 1.7362, 1.7627, 1.7706, 1.7639, 1.7604, 1.7251, 1.7035, 1.6784, 1.6505, 1.6227, 1.6067, 1.5972, 1.5892, 1.5815, 1.5563, 1.5272, 1.4982, 1.4691, 1.4401, 1.4110, 1.3820, 1.3622, 1.3424, 1.3225, 1.3027, 1.2829, 1.2631, 1.2433, 1.2234, 1.2036, 1.1838, 1.1640, 1.1442, 1.1243, 1.1064, 1.0905, 1.0745, 1.0586, 1.0426, 1.0267, 1.0107, 0.9948, 0.9788, 0.9628, 0.9469, 0.9309, 0.9150, 0.8990, 0.6836, 0.4556, 0.2219, 0.0000, -0.1553, -0.3189, -0.4784, -0.6293, -0.7801, -0.9674, -0.8837, -0.4418, 0.0000] + c_d: + grid: *grid006_1 + values: [0.0844, 0.0844, 0.1268, 0.2927, 0.4970, 0.7161, 0.9246, 1.0985, 1.2182, 1.2707, 1.2182, 1.0985, 0.9246, 0.7161, 0.4970, 0.2927, 0.1499, 0.1472, 0.1447, 0.1433, 0.1403, 0.1386, 0.1373, 0.1360, 0.1322, 0.1306, 0.1290, 0.1268, 0.1258, 0.1246, 0.1229, 0.1206, 0.1195, 0.1185, 0.1150, 0.1138, 0.1127, 0.1110, 0.1100, 0.1089, 0.1075, 0.1059, 0.1051, 0.1042, 0.1023, 0.1013, 0.1004, 0.0997, 0.0971, 0.0956, 0.0948, 0.0940, 0.0925, 0.0917, 0.0912, 0.0902, 0.0895, 0.0891, 0.0887, 0.0879, 0.0875, 0.0873, 0.0868, 0.0864, 0.0862, 0.0860, 0.0856, 0.0853, 0.0852, 0.0850, 0.0847, 0.0846, 0.0845, 0.0843, 0.0842, 0.0840, 0.0840, 0.0839, 0.0838, 0.0838, 0.0838, 0.0838, 0.0838, 0.0838, 0.0838, 0.0837, 0.0837, 0.0838, 0.0838, 0.0838, 0.0838, 0.0838, 0.0838, 0.0839, 0.0839, 0.0839, 0.0840, 0.0840, 0.0841, 0.0841, 0.0842, 0.0842, 0.0843, 0.0843, 0.0844, 0.0845, 0.0846, 0.0846, 0.0847, 0.0847, 0.0848, 0.0849, 0.0850, 0.0851, 0.0852, 0.0853, 0.0853, 0.0854, 0.0856, 0.0857, 0.0858, 0.0859, 0.0860, 0.0861, 0.0862, 0.0864, 0.0865, 0.0867, 0.0869, 0.0870, 0.0871, 0.0873, 0.0879, 0.0886, 0.0895, 0.0912, 0.0930, 0.0954, 0.0989, 0.1024, 0.1076, 0.1144, 0.1211, 0.1310, 0.1399, 0.1492, 0.1591, 0.1691, 0.1778, 0.1858, 0.1937, 0.2014, 0.2135, 0.2267, 0.2399, 0.2531, 0.2663, 0.2795, 0.2927, 0.3078, 0.3230, 0.3381, 0.3532, 0.3684, 0.3835, 0.3987, 0.4138, 0.4289, 0.4441, 0.4592, 0.4743, 0.4895, 0.5052, 0.5214, 0.5376, 0.5538, 0.5701, 0.5863, 0.6025, 0.6188, 0.6350, 0.6512, 0.6675, 0.6837, 0.6999, 0.7161, 0.9246, 1.0985, 1.2182, 1.2707, 1.2182, 1.0985, 0.9246, 0.7161, 0.4970, 0.2927, 0.1268, 0.0844, 0.0844] + c_m: + grid: *grid006_1 + values: [0.0000, 0.3125, 0.2831, 0.2632, 0.2048, 0.1932, 0.2008, 0.2136, 0.2221, 0.2198, 0.1960, 0.1635, 0.1285, 0.0965, 0.0716, 0.0522, -0.0063, -0.0089, -0.0099, -0.0105, -0.0110, -0.0116, -0.0120, -0.0126, -0.0135, -0.0139, -0.0143, -0.0147, -0.0151, -0.0155, -0.0158, -0.0161, -0.0164, -0.0168, -0.0173, -0.0175, -0.0178, -0.0181, -0.0184, -0.0186, -0.0188, -0.0188, -0.0192, -0.0194, -0.0194, -0.0196, -0.0198, -0.0200, -0.0199, -0.0196, -0.0194, -0.0184, -0.0183, -0.0192, -0.0205, -0.0224, -0.0247, -0.0267, -0.0287, -0.0320, -0.0345, -0.0367, -0.0399, -0.0430, -0.0453, -0.0476, -0.0510, -0.0538, -0.0560, -0.0586, -0.0619, -0.0644, -0.0663, -0.0688, -0.0715, -0.0737, -0.0756, -0.0774, -0.0793, -0.0811, -0.0826, -0.0838, -0.0852, -0.0867, -0.0883, -0.0897, -0.0910, -0.0921, -0.0936, -0.0949, -0.0961, -0.0972, -0.0983, -0.0995, -0.1008, -0.1019, -0.1029, -0.1040, -0.1050, -0.1061, -0.1072, -0.1082, -0.1091, -0.1100, -0.1109, -0.1119, -0.1128, -0.1137, -0.1146, -0.1153, -0.1161, -0.1170, -0.1178, -0.1185, -0.1192, -0.1199, -0.1206, -0.1212, -0.1218, -0.1224, -0.1230, -0.1235, -0.1240, -0.1245, -0.1250, -0.1254, -0.1257, -0.1259, -0.1262, -0.1265, -0.1267, -0.1270, -0.1265, -0.1256, -0.1214, -0.1163, -0.1133, -0.1107, -0.1080, -0.1063, -0.1042, -0.1025, -0.1013, -0.1001, -0.0998, -0.1001, -0.1016, -0.1036, -0.1064, -0.1099, -0.1136, -0.1180, -0.1249, -0.1325, -0.1400, -0.1476, -0.1551, -0.1627, -0.1703, -0.1740, -0.1777, -0.1815, -0.1852, -0.1889, -0.1926, -0.1964, -0.2001, -0.2039, -0.2076, -0.2113, -0.2150, -0.2188, -0.2218, -0.2242, -0.2266, -0.2289, -0.2313, -0.2337, -0.2361, -0.2384, -0.2408, -0.2432, -0.2455, -0.2479, -0.2503, -0.2527, -0.2833, -0.3156, -0.3482, -0.3773, -0.3877, -0.3865, -0.3806, -0.3803, -0.4032, -0.4854, -0.5325, -0.3906, 0.0000] + - name: FFA-W3-211 + coordinates: + x: [1.00000000, 0.99944304, 0.99812049, 0.99569352, 0.99230484, 0.98802844, 0.98281508, 0.97666422, 0.96964069, 0.96174313, 0.95297315, 0.94338928, 0.93301284, 0.92185147, 0.90995468, 0.89736121, 0.88408503, 0.87016290, 0.85565276, 0.84057695, 0.82497463, 0.80889455, 0.79236237, 0.77542101, 0.75812546, 0.74050180, 0.72259209, 0.70444539, 0.68608843, 0.66757021, 0.64892678, 0.63018643, 0.61140138, 0.59259673, 0.57380843, 0.55507570, 0.53641763, 0.51787958, 0.49948103, 0.48125155, 0.46322225, 0.44540666, 0.42784323, 0.41053864, 0.39352525, 0.37681123, 0.36041977, 0.34436494, 0.32865846, 0.31331898, 0.29834798, 0.28376580, 0.26956679, 0.25577362, 0.24237780, 0.22939648, 0.21681735, 0.20465763, 0.19290757, 0.18157496, 0.17065819, 0.16014896, 0.15005511, 0.14035465, 0.13106750, 0.12216148, 0.11365876, 0.10553619, 0.09779065, 0.09042902, 0.08341621, 0.07677403, 0.07046920, 0.06450016, 0.05888182, 0.05356799, 0.04857581, 0.04389793, 0.03949498, 0.03539484, 0.03157626, 0.02800644, 0.02471592, 0.02168071, 0.01886319, 0.01629514, 0.01396620, 0.01181764, 0.00988361, 0.00818368, 0.00663128, 0.00524853, 0.00408271, 0.00308998, 0.00219098, 0.00145967, 0.00096333, 0.00059878, 0.00028988, 0.00007804, 0.00000000, 0.00007807, 0.00029009, 0.00059937, 0.00096448, 0.00146264, 0.00219661, 0.00309879, 0.00409516, 0.00526774, 0.00665839, 0.00821941, 0.00993095, 0.01187982, 0.01404463, 0.01639219, 0.01898469, 0.02182867, 0.02489252, 0.02822001, 0.03182924, 0.03568998, 0.03984236, 0.04430035, 0.04903788, 0.05410025, 0.05948747, 0.06518787, 0.07124791, 0.07764648, 0.08439704, 0.09152340, 0.09900711, 0.10688721, 0.11514762, 0.12380644, 0.13287211, 0.14233176, 0.15221460, 0.16249918, 0.17321393, 0.18434125, 0.19590296, 0.20788328, 0.22029378, 0.23312344, 0.24637487, 0.26004146, 0.27412439, 0.28861129, 0.30349962, 0.31877410, 0.33443448, 0.35045732, 0.36684322, 0.38356093, 0.40060975, 0.41795607, 0.43559330, 0.45349250, 0.47163211, 0.48999236, 0.50853595, 0.52724867, 0.54608860, 0.56503090, 0.58404504, 0.60308800, 0.62213765, 0.64114752, 0.66008031, 0.67890619, 0.69757164, 0.71604492, 0.73429135, 0.75225234, 0.76989792, 0.78719153, 0.80407383, 0.82051349, 0.83646946, 0.85189026, 0.86674791, 0.88100970, 0.89461041, 0.90752456, 0.91973040, 0.93117530, 0.94182765, 0.95167536, 0.96067486, 0.96878747, 0.97601191, 0.98233053, 0.98768615, 0.99208631, 0.99557391, 0.99806302, 0.99942968, 1.00000000] + y: [0.00094000, 0.00103065, 0.00124638, 0.00164402, 0.00220291, 0.00291409, 0.00378945, 0.00483319, 0.00603820, 0.00740799, 0.00894481, 0.01063951, 0.01252416, 0.01457200, 0.01679743, 0.01918007, 0.02174274, 0.02445460, 0.02734973, 0.03038334, 0.03355780, 0.03687673, 0.04034551, 0.04394717, 0.04766271, 0.05148560, 0.05540822, 0.05935846, 0.06338795, 0.06740015, 0.07143043, 0.07541763, 0.07938054, 0.08322037, 0.08694859, 0.09053734, 0.09399430, 0.09729729, 0.10036579, 0.10331235, 0.10607106, 0.10853282, 0.11080624, 0.11273489, 0.11446777, 0.11590406, 0.11702887, 0.11783286, 0.11838114, 0.11853000, 0.11841639, 0.11788959, 0.11712560, 0.11610432, 0.11482051, 0.11325080, 0.11140878, 0.10943488, 0.10728747, 0.10495952, 0.10246481, 0.09993483, 0.09720396, 0.09441280, 0.09156945, 0.08863322, 0.08564543, 0.08260200, 0.07954082, 0.07647418, 0.07338251, 0.07029227, 0.06719918, 0.06411436, 0.06106016, 0.05802518, 0.05503441, 0.05207936, 0.04914881, 0.04627113, 0.04344932, 0.04066150, 0.03795784, 0.03530762, 0.03269165, 0.03015797, 0.02768421, 0.02525671, 0.02291827, 0.02065439, 0.01838965, 0.01632704, 0.01433576, 0.01227890, 0.01000166, 0.00710112, 0.00462180, 0.00275976, 0.00126291, 0.00032267, 0.00000000, -0.00031745, -0.00119506, -0.00249968, -0.00404020, -0.00605796, -0.00861062, -0.01064741, -0.01229951, -0.01388914, -0.01542391, -0.01691326, -0.01854300, -0.02029739, -0.02206055, -0.02383464, -0.02571335, -0.02764445, -0.02960227, -0.03162977, -0.03370972, -0.03582576, -0.03799324, -0.04020728, -0.04245258, -0.04473761, -0.04705148, -0.04938937, -0.05176912, -0.05417055, -0.05658760, -0.05901518, -0.06144042, -0.06386580, -0.06627274, -0.06864841, -0.07099827, -0.07329403, -0.07554549, -0.07773395, -0.07982626, -0.08182466, -0.08370902, -0.08544832, -0.08703499, -0.08846568, -0.08971729, -0.09075297, -0.09157733, -0.09216120, -0.09245766, -0.09247435, -0.09216287, -0.09149343, -0.09046835, -0.08903859, -0.08720695, -0.08496013, -0.08226184, -0.07911059, -0.07550249, -0.07147457, -0.06705220, -0.06234033, -0.05743173, -0.05235704, -0.04718857, -0.04196804, -0.03681547, -0.03177633, -0.02692515, -0.02230712, -0.01797987, -0.01398064, -0.01032443, -0.00703746, -0.00411605, -0.00155747, 0.00059313, 0.00235599, 0.00375549, 0.00481536, 0.00553358, 0.00595799, 0.00609949, 0.00599693, 0.00565716, 0.00513478, 0.00447175, 0.00377384, 0.00309945, 0.00245775, 0.00185661, 0.00130603, 0.00082003, 0.00040675, 0.00006986, -0.00017578, -0.00031253, -0.00037000] + relative_thickness: 0.211 + aerodynamic_center: 0.25 + description: FFA-W3-211 (Re=1.00e+07)FFA-W3 airfoil data for 10 MW sized rotor, computed using EllipSys2D v16.0, 70% free transition, 30% fully turbulent, 360 deg extrapolated using AirfoilPreppy, no 3D correction. F Zahle, DTU Wind Energy 11 May 2017 + polars: + - configuration: Default + re: 1.00E+07 + c_l: + grid: &grid004 [-3.14, -3.101699414, -3.061806173, -3.021912933, -2.98202, -2.94213, -2.902233213, -2.862339973, -2.82245, -2.78255, -2.74266, -2.70277, -2.662873773, -2.622980533, -2.583087293, -2.510780795, -2.438474298, -2.3661678, -2.293861303, -2.221554805, -2.149248307, -2.07694181, -2.004635312, -1.932328815, -1.860022317, -1.78771582, -1.715409322, -1.643102824, -1.570796327, -1.498489829, -1.426183332, -1.353876834, -1.281570336, -1.209263839, -1.136957341, -1.064650844, -0.992344346, -0.920037849, -0.847731351, -0.775424853, -0.703118356, -0.630811858, -0.558505361, -0.488692191, -0.41887902, -0.34906585, -0.314159265, -0.27925268, -0.244346095, -0.20943951, -0.174532925, -0.13962634, -0.104719755, -0.06981317, -0.034906585, -0.017453293, 0, 0.017453293, 0.034906585, 0.052359878, 0.06981317, 0.087266463, 0.104719755, 0.122173048, 0.13962634, 0.157079633, 0.174532925, 0.191986218, 0.20943951, 0.226892803, 0.244346095, 0.261799388, 0.27925268, 0.314159265, 0.34906585, 0.41887902, 0.488692191, 0.558505361, 0.630811858, 0.703118356, 0.775424853, 0.847731351, 0.920037849, 0.992344346, 1.064650844, 1.136957341, 1.209263839, 1.281570336, 1.353876834, 1.426183332, 1.498489829, 1.570796327, 1.643102824, 1.715409322, 1.78771582, 1.860022317, 1.932328815, 2.004635312, 2.07694181, 2.149248307, 2.221554805, 2.293861303, 2.3661678, 2.438474298, 2.510780795, 2.583087293, 2.622980533, 2.662873773, 2.702767013, 2.742660253, 2.782553493, 2.822446733, 2.862339973, 2.902233213, 2.942126453, 2.982019693, 3.021912933, 3.061806173, 3.101699414, 3.14] + values: [0.0, 0.05402504999999973, 0.1080501000000001, 0.1620751499999998, 0.2161002000000001, 0.2701252499999999, 0.3241502999999996, 0.3781753499999999, 0.4322003999999997, 0.4862254499999999, 0.5402504999999997, 0.5942755500000001, 0.6483005999999998, 0.70232565, 0.7563506999999998, 0.7318783269239915, 0.7065528355994307, 0.6776040044026928, 0.6433295384757597, 0.6027662380766498, 0.5555015408989838, 0.5015565175320579, 0.441306592466731, 0.3754225708324188, 0.3048226261816518, 0.2306301410703767, 0.1541346331849047, 0.0767543574378533, 6.938893903907228e-17, -0.07675435743785307, -0.1541346331849047, -0.2306301410703764, -0.3048226261816516, -0.3754225708324188, -0.4413065924667308, -0.5015565175320579, -0.5555015408989837, -0.6027662380766498, -0.6433295384757594, -0.6776040044026929, -0.7065528355994307, -0.7318783269239915, -0.7563507000000002, -0.85636, -1.18292, -1.23596, -1.22536, -1.20476, -1.18332, -1.10093, -0.882085, -0.62981, -0.376701, -0.121772, 0.128101, 0.25192, 0.375354, 0.498281, 0.6205240000000001, 0.742004, 0.862375, 0.98114, 1.09662, 1.20904, 1.3168, 1.42209, 1.52361, 1.61988, 1.70937, 1.78681, 1.8429, 1.85313, 1.80951, 1.66033, 1.56152, 1.43327, 1.29062, 1.080501, 1.045540467034274, 1.009361193713472, 0.9680057205752755, 0.9190421978225134, 0.8610946258237854, 0.7935736298556911, 0.7165093107600827, 0.6304379892381867, 0.5363179583320269, 0.4354608945452166, 0.3294716301005378, 0.2201923331212924, 0.1096490820540758, 9.71445146547012e-17, -0.07675435743785305, -0.1541346331849047, -0.2306301410703764, -0.3048226261816516, -0.3754225708324188, -0.4413065924667307, -0.5015565175320579, -0.5555015408989835, -0.6027662380766498, -0.6433295384757597, -0.6776040044026928, -0.7065528355994307, -0.7318783269239915, -0.7563507000000002, -0.70232565, -0.6483005999999998, -0.5942755500000001, -0.5402504999999997, -0.4862254499999999, -0.4322004000000002, -0.3781753499999999, -0.3241503000000002, -0.2701252499999999, -0.2161002000000001, -0.1620751499999998, -0.1080501000000001, -0.05402504999999973, 4.85722573273506e-18] + c_d: + grid: *grid004 + values: [0.02464146255885971, 0.0253381915760185, 0.02742386791262707, 0.03088494454883671, 0.03569894666004403, 0.05599104793704501, 0.08143456941199648, 0.111118119977472, 0.1448529823401942, 0.1824246843171338, 0.2235943631517934, 0.2681002849817088, 0.3156595097900088, 0.3659696912483095, 0.418711, 0.5194063216490634, 0.624878215320418, 0.7329268692419378, 0.8412988626197737, 0.9477341973794456, 1.050013466313862, 1.146004172998905, 1.233705236549658, 1.311288752188593, 1.37713813588963, 1.429881856853057, 1.468422053679695, 1.491957436925499, 1.5, 1.491957436925499, 1.468422053679695, 1.429881856853057, 1.37713813588963, 1.311288752188593, 1.233705236549658, 1.146004172998905, 1.050013466313863, 0.9477341973794456, 0.8412988626197743, 0.7329268692419378, 0.6248782153204183, 0.5194063216490632, 0.418711, 0.286909, 0.139597, 0.0834459, 0.0650916, 0.0488819, 0.0341723, 0.0213247, 0.0138586, 0.0107541, 0.008815799999999999, 0.00702184, 0.00663047, 0.00664363, 0.00670056, 0.00680819, 0.00697859, 0.00720311, 0.007514809999999999, 0.00795847, 0.00872169, 0.009683530000000001, 0.0109695, 0.0122748, 0.0136859, 0.0152881, 0.0171709, 0.0197355, 0.0236792, 0.0309403, 0.0430319, 0.0773041, 0.112017, 0.18408, 0.275892, 0.418711, 0.5194063216490632, 0.6248782153204185, 0.7329268692419378, 0.8412988626197743, 0.9477341973794456, 1.050013466313863, 1.146004172998905, 1.233705236549658, 1.311288752188593, 1.37713813588963, 1.429881856853057, 1.468422053679695, 1.491957436925499, 1.5, 1.491957436925499, 1.468422053679695, 1.429881856853057, 1.37713813588963, 1.311288752188593, 1.233705236549658, 1.146004172998905, 1.050013466313863, 0.9477341973794456, 0.8412988626197737, 0.7329268692419378, 0.624878215320418, 0.5194063216490634, 0.418711, 0.3659696912483095, 0.3156595097900088, 0.2681002849817088, 0.2235943631517934, 0.1824246843171338, 0.1448529823401946, 0.111118119977472, 0.08143456941199678, 0.05599104793704501, 0.03569894666004403, 0.03088494454883671, 0.02742386791262707, 0.0253381915760185, 0.02464146255885971] + c_m: + grid: *grid004 + values: [0.0, 0.09142857142857111, 0.1828571428571434, 0.2742857142857145, 0.3657142857142855, 0.3919171380083344, 0.3789845588216693, 0.3660519796350041, 0.353119400448339, 0.3476784059442646, 0.3647121654879618, 0.3817459250316594, 0.3987796845753567, 0.4158134441190544, 0.4195454592054533, 0.4228655348597155, 0.4263227290676669, 0.4316310237504245, 0.436939318433182, 0.4438932390944983, 0.4517132786918981, 0.458974087487912, 0.4644773137652843, 0.4699805400426567, 0.4709637155505512, 0.4710052138148045, 0.4682373893923625, 0.4614896911637687, 0.4547419929351749, 0.440261605201769, 0.4257812174683632, 0.4082106877197933, 0.3884588812546371, 0.3681533816888277, 0.3451901552398833, 0.322226928790939, 0.2986411272359326, 0.2748572336017246, 0.2512805789575005, 0.2280976783942457, 0.2049147778309909, 0.1541624777852183, 0.101368, 0.0652698, 0.01647420000000005, -0.0035156, -0.006716600000000001, -0.00881275, -0.0110092, -0.02268789999999999, -0.0439675, -0.0575595, -0.0674747, -0.07680390000000001, -0.08283, -0.08534420000000001, -0.0877721, -0.0901057, -0.0923415, -0.094469, -0.0964635, -0.0982801, -0.0997729, -0.100947, -0.1016336, -0.1020701, -0.1021297, -0.1017353, -0.100868, -0.09936350000000001, -0.0971987, -0.0940975, -0.0914393, -0.09242, -0.09870720000000001, -0.117702, -0.145658, -0.18266, -0.2091344193953699, -0.2353353726197231, -0.2578445325353481, -0.2803536924509731, -0.301631395515415, -0.3222609633950237, -0.3424694255055559, -0.3613544123418473, -0.3802393991781386, -0.3970410534414668, -0.4134086800854276, -0.4284443809438795, -0.4415931869395273, -0.4547419929351749, -0.4614896911637686, -0.4682373893923625, -0.4710052138148045, -0.4709637155505513, -0.4699805400426568, -0.4644773137652844, -0.458974087487912, -0.4517132786918981, -0.4438932390944983, -0.436939318433182, -0.4316310237504245, -0.4263227290676669, -0.4228655348597155, -0.4195454592054533, -0.4158134441190544, -0.3987796845753567, -0.3817459250316594, -0.3647121654879618, -0.3476784059442646, -0.3702622575911958, -0.4060519796350041, -0.4418417016788119, -0.4776314237226202, -0.457142857142857, -0.3428571428571431, -0.2285714285714291, -0.1142857142857139, 0.0] + - name: FFA-W3-241 + coordinates: + x: [1.00000000, 0.99944304, 0.99812049, 0.99569352, 0.99230484, 0.98802844, 0.98281508, 0.97666422, 0.96964069, 0.96174313, 0.95297315, 0.94338928, 0.93301284, 0.92185147, 0.90995468, 0.89736121, 0.88408503, 0.87016290, 0.85565276, 0.84057695, 0.82497463, 0.80889455, 0.79236237, 0.77542101, 0.75812546, 0.74050180, 0.72259209, 0.70444539, 0.68608843, 0.66757021, 0.64892678, 0.63018643, 0.61140138, 0.59259673, 0.57380843, 0.55507570, 0.53641763, 0.51787958, 0.49948103, 0.48125155, 0.46322225, 0.44540666, 0.42784323, 0.41053864, 0.39352525, 0.37681123, 0.36041977, 0.34436494, 0.32865846, 0.31331898, 0.29834798, 0.28376580, 0.26956679, 0.25577362, 0.24237780, 0.22939648, 0.21681735, 0.20465763, 0.19290757, 0.18157496, 0.17065819, 0.16014896, 0.15005511, 0.14035465, 0.13106750, 0.12216148, 0.11365876, 0.10553619, 0.09779065, 0.09042902, 0.08341621, 0.07677403, 0.07046920, 0.06450016, 0.05888182, 0.05356799, 0.04857581, 0.04389793, 0.03949498, 0.03539484, 0.03157626, 0.02800644, 0.02471592, 0.02168071, 0.01886319, 0.01629514, 0.01396620, 0.01181764, 0.00988361, 0.00818368, 0.00663128, 0.00524853, 0.00408271, 0.00308998, 0.00219098, 0.00145967, 0.00096333, 0.00059878, 0.00028988, 0.00007804, 0.00000000, 0.00007807, 0.00029009, 0.00059937, 0.00096448, 0.00146264, 0.00219661, 0.00309879, 0.00409516, 0.00526774, 0.00665839, 0.00821941, 0.00993095, 0.01187982, 0.01404463, 0.01639219, 0.01898469, 0.02182867, 0.02489252, 0.02822001, 0.03182924, 0.03568998, 0.03984236, 0.04430035, 0.04903788, 0.05410025, 0.05948747, 0.06518787, 0.07124791, 0.07764648, 0.08439704, 0.09152340, 0.09900711, 0.10688721, 0.11514762, 0.12380644, 0.13287211, 0.14233176, 0.15221460, 0.16249918, 0.17321393, 0.18434125, 0.19590296, 0.20788328, 0.22029378, 0.23312344, 0.24637487, 0.26004146, 0.27412439, 0.28861129, 0.30349962, 0.31877410, 0.33443448, 0.35045732, 0.36684322, 0.38356093, 0.40060975, 0.41795607, 0.43559330, 0.45349250, 0.47163211, 0.48999236, 0.50853595, 0.52724867, 0.54608860, 0.56503090, 0.58404504, 0.60308800, 0.62213765, 0.64114752, 0.66008031, 0.67890619, 0.69757164, 0.71604492, 0.73429135, 0.75225234, 0.76989792, 0.78719153, 0.80407383, 0.82051349, 0.83646946, 0.85189026, 0.86674791, 0.88100970, 0.89461041, 0.90752456, 0.91973040, 0.93117530, 0.94182765, 0.95167536, 0.96067486, 0.96878747, 0.97601191, 0.98233053, 0.98768615, 0.99208631, 0.99557391, 0.99806302, 0.99942968, 1.00000000] + y: [0.00425000, 0.00436210, 0.00462832, 0.00511699, 0.00579959, 0.00666146, 0.00771282, 0.00895410, 0.01037253, 0.01196864, 0.01374233, 0.01568187, 0.01778563, 0.02006929, 0.02251710, 0.02513781, 0.02790923, 0.03084911, 0.03393611, 0.03716418, 0.04052857, 0.04400982, 0.04761716, 0.05132667, 0.05512297, 0.05899393, 0.06293412, 0.06690084, 0.07088321, 0.07486708, 0.07883497, 0.08275436, 0.08661408, 0.09037299, 0.09402539, 0.09754917, 0.10092636, 0.10414685, 0.10719326, 0.11006231, 0.11271220, 0.11514729, 0.11735341, 0.11931701, 0.12102638, 0.12248809, 0.12367942, 0.12460233, 0.12523852, 0.12560509, 0.12567380, 0.12546547, 0.12498975, 0.12423636, 0.12323884, 0.12200236, 0.12053786, 0.11886654, 0.11699332, 0.11492977, 0.11269298, 0.11029800, 0.10776785, 0.10510781, 0.10233142, 0.09945032, 0.09649044, 0.09345853, 0.09036359, 0.08722175, 0.08403450, 0.08082678, 0.07759443, 0.07434789, 0.07110954, 0.06787343, 0.06465344, 0.06145399, 0.05826012, 0.05510950, 0.05199557, 0.04891845, 0.04589051, 0.04289625, 0.03991914, 0.03700233, 0.03418463, 0.03137187, 0.02861090, 0.02603794, 0.02342947, 0.02076107, 0.01830451, 0.01595100, 0.01327076, 0.01049860, 0.00746215, 0.00453721, 0.00204453, 0.00050657, 0.00000000, -0.00049018, -0.00184125, -0.00381822, -0.00605713, -0.00870563, -0.01122916, -0.01342212, -0.01534366, -0.01716693, -0.01909642, -0.02130654, -0.02351414, -0.02579914, -0.02815987, -0.03053974, -0.03296857, -0.03549058, -0.03807470, -0.04072568, -0.04345995, -0.04623151, -0.04907034, -0.05196961, -0.05490886, -0.05790431, -0.06095001, -0.06402491, -0.06712837, -0.07024411, -0.07337047, -0.07649115, -0.07959568, -0.08268285, -0.08571902, -0.08870503, -0.09161742, -0.09443777, -0.09716583, -0.09976981, -0.10223862, -0.10454454, -0.10668939, -0.10863043, -0.11037325, -0.11187776, -0.11314131, -0.11414886, -0.11486604, -0.11529721, -0.11537968, -0.11513331, -0.11450572, -0.11349774, -0.11208121, -0.11023393, -0.10793345, -0.10519348, -0.10197553, -0.09829760, -0.09414081, -0.08952202, -0.08447312, -0.07905685, -0.07331017, -0.06729960, -0.06109331, -0.05477058, -0.04842523, -0.04213791, -0.03600811, -0.03012656, -0.02457508, -0.01942136, -0.01470107, -0.01045419, -0.00673447, -0.00355842, -0.00093374, 0.00117919, 0.00284598, 0.00409407, 0.00495249, 0.00546535, 0.00561958, 0.00547727, 0.00504714, 0.00440395, 0.00360867, 0.00270812, 0.00180270, 0.00091285, 0.00005718, -0.00074311, -0.00146131, -0.00207989, -0.00258907, -0.00296293, -0.00317197, -0.00326000] + relative_thickness: 0.241 + aerodynamic_center: 0.25 + description: FFA-W3-241 (Re=1.00e+07)FFA-W3 airfoil data for 10 MW sized rotor, computed using EllipSys2D v16.0, 70% free transition, 30% fully turbulent, 360 deg extrapolated using AirfoilPreppy, no 3D correction. F Zahle, DTU Wind Energy 11 May 2017 + polars: + - configuration: Default + re: 1.00E+07 + c_l: + grid: *grid004 + values: [0.0, 0.05817799999999972, 0.1163560000000001, 0.1745339999999998, 0.2327120000000001, 0.2908899999999999, 0.3490679999999995, 0.4072459999999999, 0.4654239999999996, 0.523602, 0.5817799999999997, 0.6399580000000001, 0.6981359999999998, 0.7563140000000002, 0.8144919999999998, 0.7792464302724209, 0.7451071591370395, 0.7088137481222476, 0.6683458115525404, 0.6225263575881583, 0.5707951681755026, 0.5130692040229429, 0.4496493863351846, 0.3811527280710469, 0.3084584943135641, 0.2326621563088236, 0.1550337076275116, 0.07697853256272918, 6.938893903907228e-17, -0.07697853256272895, -0.1550337076275116, -0.2326621563088234, -0.3084584943135639, -0.3811527280710469, -0.4496493863351844, -0.5130692040229429, -0.5707951681755025, -0.6225263575881583, -0.6683458115525401, -0.7088137481222476, -0.7451071591370394, -0.7792464302724209, -0.8144920000000002, -1.077809, -1.12692, -1.1448, -1.12797, -1.09392, -1.05961, -1.031215, -0.937059, -0.673795, -0.403909, -0.14226, 0.1158039, 0.243824, 0.371129, 0.497655, 0.6233420000000001, 0.747981, 0.871372, 0.993202, 1.11325, 1.23037, 1.34496, 1.45407, 1.55911, 1.65779, 1.74834, 1.82666, 1.88831, 1.92579, 1.92722, 1.80055, 1.63088, 1.43345, 1.28805, 1.16356, 1.113209186103459, 1.064438798767199, 1.012591068746068, 0.9547797307893432, 0.8893233679830833, 0.8154216688221464, 0.7329560057470612, 0.6423562661931204, 0.5445038972443527, 0.4406549918765199, 0.3323745090126048, 0.2214767251821594, 0.1099693322324699, 9.71445146547012e-17, -0.07697853256272895, -0.1550337076275116, -0.2326621563088234, -0.3084584943135639, -0.3811527280710469, -0.4496493863351842, -0.5130692040229429, -0.5707951681755022, -0.6225263575881583, -0.6683458115525404, -0.7088137481222476, -0.7451071591370395, -0.7792464302724209, -0.8144920000000002, -0.7563140000000002, -0.6981359999999998, -0.6399580000000001, -0.5817799999999997, -0.523602, -0.4654240000000002, -0.4072459999999999, -0.3490680000000003, -0.2908899999999999, -0.2327120000000001, -0.1745339999999998, -0.1163560000000001, -0.05817799999999972, 0.0] + c_d: + grid: *grid004 + values: [0.01177544706410594, 0.01248241260198391, 0.01459878240467653, 0.01811096054673407, 0.02299639088779405, 0.02922369647552087, 0.05381969831066666, 0.08379399231618556, 0.1178630778979731, 0.1558119510371117, 0.1974011487998909, 0.242368269760347, 0.2904296400140884, 0.3412821141889975, 0.394605, 0.4964515212818632, 0.6031945752737573, 0.7126277071805793, 0.8224902609928115, 0.9305144490385059, 1.034472560839366, 1.132223326418828, 1.221756466908459, 1.301234503184361, 1.369030950546305, 1.423764102933106, 1.464325702272211, 1.489903895366822, 1.5, 1.489903895366822, 1.464325702272211, 1.423764102933106, 1.369030950546305, 1.301234503184361, 1.22175646690846, 1.132223326418828, 1.034472560839366, 0.9305144490385059, 0.8224902609928122, 0.7126277071805793, 0.6031945752737576, 0.4964515212818629, 0.394605, 0.222521, 0.151592, 0.0969875, 0.0774353, 0.0612223, 0.0466721, 0.0330216, 0.0202688, 0.01167894, 0.00917822, 0.00838762, 0.00810497, 0.008082470000000001, 0.00812783, 0.008240289999999999, 0.00841819, 0.00867336, 0.00900857, 0.00944734, 0.00997828, 0.01070204, 0.0115279, 0.0126944, 0.0139646, 0.015453, 0.0172426, 0.0196113, 0.0229324, 0.0279527, 0.0360913, 0.0653426, 0.1045871, 0.191485, 0.286294, 0.394605, 0.4964515212818629, 0.6031945752737579, 0.7126277071805793, 0.8224902609928122, 0.9305144490385059, 1.034472560839366, 1.132223326418828, 1.22175646690846, 1.301234503184361, 1.369030950546305, 1.423764102933106, 1.464325702272211, 1.489903895366822, 1.5, 1.489903895366822, 1.464325702272211, 1.423764102933106, 1.369030950546305, 1.301234503184361, 1.22175646690846, 1.132223326418828, 1.034472560839367, 0.9305144490385059, 0.8224902609928115, 0.7126277071805793, 0.6031945752737573, 0.4964515212818632, 0.394605, 0.3412821141889975, 0.2904296400140884, 0.242368269760347, 0.1974011487998909, 0.1558119510371117, 0.1178630778979735, 0.08379399231618556, 0.05381969831066696, 0.02922369647552087, 0.02299639088779405, 0.01811096054673407, 0.01459878240467653, 0.01248241260198391, 0.01177544706410594] + c_m: + grid: *grid004 + values: [0.0, 0.09142857142857111, 0.1828571428571434, 0.2742857142857145, 0.3657142857142855, 0.3956763450520955, 0.3887584971354481, 0.3818406492188006, 0.3749228013021533, 0.3740843285591013, 0.3914839813368354, 0.4088836341145699, 0.4262832868923042, 0.4436829396700386, 0.4453716275543733, 0.4443643566491176, 0.4435983994287483, 0.4460901769543472, 0.4485819544799459, 0.4536989103045144, 0.4601975389180142, 0.4663277194153696, 0.4712999201191282, 0.4762721208228869, 0.4770470794649723, 0.4769476126775422, 0.4740887008224939, 0.467320575121653, 0.4605524494208121, 0.4450945670669362, 0.4296366847130603, 0.4112541220321606, 0.3908070791204207, 0.3698777372479554, 0.3466333603640094, 0.3233889834800633, 0.2998354483726866, 0.2761835447396729, 0.2527992279113344, 0.2299233260118784, 0.2070474241124226, 0.1456060142046668, 0.0813079, 0.0459169, 0.01901030000000003, 0.0006315499999999995, -0.00342245, -0.0058652, -0.00652125, -0.007554099999999999, -0.0224323, -0.055829, -0.0715901, -0.0812253, -0.08892019999999999, -0.0923527, -0.0955637, -0.0985678, -0.1013918, -0.1040302, -0.106451, -0.108633, -0.110572, -0.112141, -0.113368, -0.113958, -0.114027, -0.113365, -0.111865, -0.109351, -0.1060607, -0.1023772, -0.09886919999999999, -0.0949704, -0.0999569, -0.125888, -0.154528, -0.183965, -0.2109869139440005, -0.2376791550670783, -0.2599208131076984, -0.2821624711483185, -0.3032292136924059, -0.3236775796593707, -0.3438049093730668, -0.3629232680050612, -0.3820416266370555, -0.3994389100207872, -0.4164776360611308, -0.4323092709995288, -0.4464308602101705, -0.4605524494208121, -0.467320575121653, -0.4740887008224939, -0.4769476126775422, -0.4770470794649723, -0.4762721208228869, -0.4712999201191283, -0.4663277194153696, -0.4601975389180142, -0.4536989103045144, -0.4485819544799459, -0.4460901769543472, -0.4435983994287483, -0.4443643566491176, -0.4453716275543733, -0.4436829396700386, -0.4262832868923042, -0.4088836341145699, -0.3914839813368354, -0.3740843285591013, -0.3920656584450101, -0.4218406492188006, -0.4516156399925907, -0.4813906307663812, -0.457142857142857, -0.3428571428571431, -0.2285714285714291, -0.1142857142857139, 0.0] + - name: FFA-W3-270blend + coordinates: + x: [1.00000, 0.99185, 0.98371, 0.97556, 0.96742, 0.95928, 0.95114, 0.94301, 0.93488, 0.92675, 0.91863, 0.91052, 0.90241, 0.89430, 0.88620, 0.87810, 0.87001, 0.86192, 0.85384, 0.84576, 0.83768, 0.82960, 0.82153, 0.81346, 0.80539, 0.79732, 0.78926, 0.78120, 0.77314, 0.76508, 0.75702, 0.74897, 0.74091, 0.73285, 0.72480, 0.71675, 0.70869, 0.70064, 0.69258, 0.68452, 0.67647, 0.66841, 0.66035, 0.65229, 0.64422, 0.63616, 0.62809, 0.62002, 0.61195, 0.60387, 0.59580, 0.58772, 0.57963, 0.57154, 0.56345, 0.55536, 0.54726, 0.53916, 0.53105, 0.52294, 0.51482, 0.50670, 0.49858, 0.49045, 0.48231, 0.47417, 0.46603, 0.45787, 0.44972, 0.44155, 0.43338, 0.42521, 0.41702, 0.40884, 0.40064, 0.39244, 0.38424, 0.37602, 0.36781, 0.35958, 0.35135, 0.34312, 0.33488, 0.32664, 0.31839, 0.31014, 0.30188, 0.29363, 0.28537, 0.27711, 0.26886, 0.26060, 0.25235, 0.24411, 0.23587, 0.22763, 0.21941, 0.21120, 0.20301, 0.19483, 0.18668, 0.17855, 0.17045, 0.16238, 0.15434, 0.14635, 0.13840, 0.13050, 0.12265, 0.11487, 0.10716, 0.09952, 0.09197, 0.08451, 0.07715, 0.06991, 0.06280, 0.05584, 0.04905, 0.04245, 0.03607, 0.02994, 0.02413, 0.01869, 0.01370, 0.00923, 0.00540, 0.00249, 0.00064, 0.00000, 0.00089, 0.00355, 0.00749, 0.01224, 0.01762, 0.02340, 0.02952, 0.03592, 0.04253, 0.04932, 0.05615, 0.06309, 0.07018, 0.07740, 0.08474, 0.09218, 0.09973, 0.10736, 0.11507, 0.12286, 0.13073, 0.13866, 0.14664, 0.15469, 0.16279, 0.17093, 0.17911, 0.18733, 0.19559, 0.20387, 0.21219, 0.22053, 0.22889, 0.23726, 0.24566, 0.25406, 0.26248, 0.27090, 0.27933, 0.28777, 0.29620, 0.30464, 0.31308, 0.32151, 0.32994, 0.33836, 0.34678, 0.35518, 0.36358, 0.37196, 0.38033, 0.38869, 0.39703, 0.40536, 0.41366, 0.42195, 0.43023, 0.43848, 0.44671, 0.45493, 0.46312, 0.47129, 0.47944, 0.48757, 0.49567, 0.50376, 0.51182, 0.51986, 0.52788, 0.53589, 0.54387, 0.55184, 0.55979, 0.56773, 0.57566, 0.58358, 0.59149, 0.59939, 0.60729, 0.61518, 0.62308, 0.63098, 0.63888, 0.64679, 0.65471, 0.66263, 0.67057, 0.67853, 0.68649, 0.69448, 0.70248, 0.71050, 0.71854, 0.72660, 0.73468, 0.74278, 0.75091, 0.75905, 0.76722, 0.77541, 0.78362, 0.79186, 0.80012, 0.80839, 0.81669, 0.82499, 0.83332, 0.84165, 0.85000, 0.85835, 0.86671, 0.87508, 0.88346, 0.89184, 0.90022, 0.90859, 0.91697, 0.92533, 0.93369, 0.94203, 0.95037, 0.95868, 0.96698, 0.97526, 0.98352, 0.99177, 1.00000] + y: [0.00652, 0.00831, 0.01010, 0.01188, 0.01366, 0.01544, 0.01721, 0.01898, 0.02076, 0.02255, 0.02435, 0.02617, 0.02799, 0.02982, 0.03166, 0.03350, 0.03535, 0.03721, 0.03907, 0.04094, 0.04281, 0.04468, 0.04655, 0.04843, 0.05031, 0.05219, 0.05407, 0.05594, 0.05782, 0.05969, 0.06157, 0.06343, 0.06530, 0.06716, 0.06901, 0.07086, 0.07270, 0.07453, 0.07636, 0.07817, 0.07997, 0.08176, 0.08355, 0.08531, 0.08707, 0.08881, 0.09054, 0.09225, 0.09395, 0.09562, 0.09729, 0.09893, 0.10055, 0.10215, 0.10373, 0.10529, 0.10683, 0.10834, 0.10983, 0.11129, 0.11272, 0.11413, 0.11551, 0.11686, 0.11817, 0.11946, 0.12070, 0.12191, 0.12307, 0.12420, 0.12529, 0.12633, 0.12733, 0.12828, 0.12918, 0.13003, 0.13083, 0.13157, 0.13226, 0.13288, 0.13345, 0.13395, 0.13438, 0.13474, 0.13502, 0.13523, 0.13537, 0.13541, 0.13537, 0.13524, 0.13501, 0.13469, 0.13426, 0.13373, 0.13308, 0.13233, 0.13146, 0.13048, 0.12936, 0.12811, 0.12672, 0.12518, 0.12350, 0.12167, 0.11969, 0.11754, 0.11522, 0.11274, 0.11008, 0.10724, 0.10422, 0.10101, 0.09759, 0.09398, 0.09014, 0.08609, 0.08180, 0.07727, 0.07247, 0.06739, 0.06196, 0.05624, 0.05049, 0.04439, 0.03792, 0.03107, 0.02383, 0.01614, 0.00809, -0.00019, -0.00845, -0.01641, -0.02407, -0.03122, -0.03781, -0.04403, -0.04990, -0.05547, -0.06076, -0.06581, -0.07063, -0.07523, -0.07962, -0.08382, -0.08782, -0.09164, -0.09527, -0.09873, -0.10201, -0.10512, -0.10805, -0.11082, -0.11341, -0.11584, -0.11811, -0.12022, -0.12218, -0.12397, -0.12562, -0.12711, -0.12846, -0.12966, -0.13073, -0.13166, -0.13245, -0.13312, -0.13365, -0.13406, -0.13435, -0.13451, -0.13455, -0.13447, -0.13428, -0.13397, -0.13354, -0.13301, -0.13236, -0.13161, -0.13075, -0.12978, -0.12871, -0.12753, -0.12626, -0.12489, -0.12341, -0.12185, -0.12020, -0.11845, -0.11662, -0.11470, -0.11270, -0.11061, -0.10845, -0.10621, -0.10390, -0.10152, -0.09907, -0.09656, -0.09399, -0.09137, -0.08870, -0.08599, -0.08323, -0.08044, -0.07761, -0.07476, -0.07188, -0.06898, -0.06607, -0.06315, -0.06024, -0.05732, -0.05441, -0.05152, -0.04864, -0.04579, -0.04297, -0.04019, -0.03745, -0.03476, -0.03213, -0.02955, -0.02704, -0.02459, -0.02222, -0.01993, -0.01771, -0.01559, -0.01356, -0.01163, -0.00982, -0.00814, -0.00656, -0.00511, -0.00377, -0.00254, -0.00143, -0.00044, 0.00045, 0.00121, 0.00185, 0.00237, 0.00275, 0.00300, 0.00310, 0.00305, 0.00287, 0.00254, 0.00207, 0.00147, 0.00074, -0.00012, -0.00112, -0.00224, -0.00347, -0.00479, -0.00613] + relative_thickness: 0.27 + aerodynamic_center: 0.25 + description: FFA-W3-270blend (Re=1.00e+07)FFA-W3 airfoil data for 10 MW sized rotor, computed using EllipSys2D v16.0, 70% free transition, 30% fully turbulent, 360 deg extrapolated using AirfoilPreppy, no 3D correction. F Zahle, DTU Wind Energy 11 May 2017 + polars: + - configuration: Default + re: 1.00E+07 + c_l: + grid: *grid004 + values: [0.0, 0.06212899999999969, 0.1242580000000001, 0.1863869999999997, 0.2485160000000001, 0.3106449999999998, 0.3727739999999995, 0.4349029999999999, 0.4970319999999995, 0.5591609999999999, 0.6212899999999997, 0.683419, 0.7455479999999997, 0.8076770000000001, 0.8698059999999997, 0.8165994877207365, 0.7681195983700221, 0.7203990529680618, 0.6709524633842782, 0.6182782428591802, 0.5615751466576534, 0.5005693936664801, 0.435401383049361, 0.3665455794904564, 0.294749276481367, 0.220982292815904, 0.1463931471210668, 0.07226926649699138, 5.551115123125782e-17, -0.07226926649699114, -0.1463931471210668, -0.2209822928159038, -0.2947492764813668, -0.3665455794904564, -0.4354013830493607, -0.5005693936664801, -0.5615751466576534, -0.6182782428591802, -0.670952463384278, -0.720399052968062, -0.7681195983700218, -0.8165994877207366, -0.8698060000000001, -1.098365, -1.083388, -1.069897, -1.05454, -1.034316, -1.0836, -1.094892, -0.926646, -0.696755, -0.436276, -0.162516, 0.1070901, 0.239931, 0.371578, 0.502096, 0.631386, 0.759514, 0.886379, 1.011723, 1.1343, 1.25536, 1.37379, 1.48841, 1.59782, 1.70005, 1.7919, 1.86782, 1.92687, 1.90901, 1.88548, 1.72106, 1.54737, 1.37176, 1.33611, 1.24258, 1.16657069674391, 1.097313711957174, 1.029141504240088, 0.9585035191203972, 0.8832546326559717, 0.8022502095109334, 0.7150991338092573, 0.6220019757848009, 0.5236365421292234, 0.4210703949733812, 0.3156889897370054, 0.2091330673158098, 0.1032418092814159, 8.326672684688674e-17, -0.07226926649699114, -0.1463931471210668, -0.2209822928159038, -0.2947492764813668, -0.3665455794904564, -0.4354013830493606, -0.5005693936664801, -0.5615751466576532, -0.6182782428591802, -0.6709524633842782, -0.7203990529680618, -0.7681195983700221, -0.8165994877207365, -0.8698060000000001, -0.8076770000000001, -0.7455479999999997, -0.683419, -0.6212899999999997, -0.5591609999999999, -0.4970320000000003, -0.4349029999999999, -0.3727740000000002, -0.3106449999999998, -0.2485160000000001, -0.1863869999999997, -0.1242580000000001, -0.06212899999999969, 0.0] + c_d: + grid: *grid004 + values: [0.01545145617525337, 0.01610777849333327, 0.01807252594664354, 0.02133302578443705, 0.02586825350874664, 0.0328883659372791, 0.05680723323792564, 0.08471401060447561, 0.1164322402549705, 0.1517613784098523, 0.1904780692421837, 0.2323375637112758, 0.2770752742559843, 0.3244084554599586, 0.374038, 0.4688206353049151, 0.5681448033676435, 0.669952728182786, 0.7721361869547816, 0.8725804326764182, 0.9692082460346432, 1.060023197499036, 1.143151216970164, 1.2168796037278, 1.279692662884011, 1.330303224997329, 1.367679391470503, 1.391065948041092, 1.4, 1.391065948041092, 1.367679391470503, 1.330303224997329, 1.279692662884011, 1.2168796037278, 1.143151216970164, 1.060023197499036, 0.9692082460346436, 0.8725804326764182, 0.772136186954782, 0.669952728182786, 0.5681448033676437, 0.4688206353049149, 0.374038, 0.218804, 0.159821, 0.107443, 0.0869038, 0.0684434, 0.0473338, 0.0308488, 0.0198433, 0.0143943, 0.01154858, 0.0102638, 0.009755139999999999, 0.00967083, 0.00967746, 0.00976408, 0.00992886, 0.010161, 0.01045406, 0.01082305, 0.0113994, 0.0119786, 0.0126691, 0.01353, 0.014604, 0.0159672, 0.0177673, 0.0203501, 0.0238496, 0.0323572, 0.042586, 0.07671539999999999, 0.119141, 0.201888, 0.279811, 0.374038, 0.4688206353049149, 0.568144803367644, 0.669952728182786, 0.772136186954782, 0.8725804326764182, 0.9692082460346436, 1.060023197499036, 1.143151216970165, 1.2168796037278, 1.279692662884011, 1.330303224997329, 1.367679391470503, 1.391065948041092, 1.4, 1.391065948041092, 1.367679391470503, 1.330303224997329, 1.279692662884011, 1.2168796037278, 1.143151216970165, 1.060023197499036, 0.9692082460346441, 0.8725804326764182, 0.7721361869547816, 0.669952728182786, 0.5681448033676435, 0.4688206353049151, 0.374038, 0.3244084554599586, 0.2770752742559843, 0.2323375637112758, 0.1904780692421837, 0.1517613784098523, 0.1164322402549709, 0.08471401060447561, 0.05680723323792593, 0.0328883659372791, 0.02586825350874664, 0.02133302578443705, 0.01807252594664354, 0.01610777849333327, 0.01545145617525337] + c_m: + grid: *grid004 + values: [0.0, 0.09142857142857111, 0.1828571428571434, 0.2742857142857145, 0.3657142857142855, 0.398737801088277, 0.3967182828295202, 0.3946987645707634, 0.3926792463120065, 0.3954394359195705, 0.4125387491260962, 0.4296380623326223, 0.4467373755391482, 0.4638366887456743, 0.4618649693614473, 0.4533531927531447, 0.4452312591985079, 0.4423722068683588, 0.4395131545382098, 0.4407199239530665, 0.4440665995495052, 0.4473877569319844, 0.4506287142134481, 0.4538696714949119, 0.4537721512199265, 0.4529791147873474, 0.4497339697805277, 0.4430150042935336, 0.4362960388065394, 0.4217990029204757, 0.4073019670344121, 0.3901952202168646, 0.3712463245065051, 0.3519042921503085, 0.3306752038940939, 0.3094461156378793, 0.288154029724106, 0.2668418991011094, 0.2457600706579958, 0.2251158163566598, 0.2044715620553239, 0.1395658926964592, 0.07138159999999999, 0.0439981, 0.02166040000000002, 0.0042194, -0.0003518499999999999, -0.00333715, -0.0028317, -0.005556199999999997, -0.02951695, -0.04821780000000001, -0.06483219999999999, -0.0791866, -0.0904066, -0.0951728, -0.0995328, -0.103549, -0.1072544, -0.11068, -0.113846, -0.116729, -0.119232, -0.121448, -0.123281, -0.124605, -0.125264, -0.125048, -0.1237, -0.120926, -0.117251, -0.109308, -0.105246, -0.102916, -0.110171, -0.134308, -0.157774, -0.184317, -0.2100189556620827, -0.2353116690528524, -0.2550796117808968, -0.2748475545089412, -0.2934552427836259, -0.3114522708197002, -0.3292470257762263, -0.3464060653398862, -0.363565104903546, -0.3794935737543194, -0.3951656737065747, -0.4098287562719499, -0.4230623975392447, -0.4362960388065394, -0.4430150042935335, -0.4497339697805277, -0.4529791147873474, -0.4537721512199265, -0.4538696714949119, -0.4506287142134482, -0.4473877569319844, -0.4440665995495052, -0.4407199239530665, -0.4395131545382098, -0.4423722068683588, -0.4452312591985079, -0.4533531927531447, -0.4618649693614473, -0.4638366887456743, -0.4467373755391482, -0.4296380623326223, -0.4125387491260962, -0.3954394359195705, -0.4098221034548635, -0.4346987645707633, -0.4595754256866629, -0.4844520868025629, -0.457142857142857, -0.3428571428571431, -0.2285714285714291, -0.1142857142857139, 0.0] + - name: FFA-W3-301 + coordinates: + x: [1.00000000, 0.99944304, 0.99812049, 0.99569352, 0.99230484, 0.98802844, 0.98281508, 0.97666422, 0.96964069, 0.96174313, 0.95297315, 0.94338928, 0.93301284, 0.92185147, 0.90995468, 0.89736121, 0.88408503, 0.87016290, 0.85565276, 0.84057695, 0.82497463, 0.80889455, 0.79236237, 0.77542101, 0.75812546, 0.74050180, 0.72259209, 0.70444539, 0.68608843, 0.66757021, 0.64892678, 0.63018643, 0.61140138, 0.59259673, 0.57380843, 0.55507570, 0.53641763, 0.51787958, 0.49948103, 0.48125155, 0.46322225, 0.44540666, 0.42784323, 0.41053864, 0.39352525, 0.37681123, 0.36041977, 0.34436494, 0.32865846, 0.31331898, 0.29834798, 0.28376580, 0.26956679, 0.25577362, 0.24237780, 0.22939648, 0.21681735, 0.20465763, 0.19290757, 0.18157496, 0.17065819, 0.16014896, 0.15005511, 0.14035465, 0.13106750, 0.12216148, 0.11365876, 0.10553619, 0.09779065, 0.09042902, 0.08341621, 0.07677403, 0.07046920, 0.06450016, 0.05888182, 0.05356799, 0.04857581, 0.04389793, 0.03949498, 0.03539484, 0.03157626, 0.02800644, 0.02471592, 0.02168071, 0.01886319, 0.01629514, 0.01396620, 0.01181764, 0.00988361, 0.00818368, 0.00663128, 0.00524853, 0.00408271, 0.00308998, 0.00219098, 0.00145967, 0.00096333, 0.00059878, 0.00028988, 0.00007804, 0.00000000, 0.00007807, 0.00029009, 0.00059937, 0.00096448, 0.00146264, 0.00219661, 0.00309879, 0.00409516, 0.00526774, 0.00665839, 0.00821941, 0.00993095, 0.01187982, 0.01404463, 0.01639219, 0.01898469, 0.02182867, 0.02489252, 0.02822001, 0.03182924, 0.03568998, 0.03984236, 0.04430035, 0.04903788, 0.05410025, 0.05948747, 0.06518787, 0.07124791, 0.07764648, 0.08439704, 0.09152340, 0.09900711, 0.10688721, 0.11514762, 0.12380644, 0.13287211, 0.14233176, 0.15221460, 0.16249918, 0.17321393, 0.18434125, 0.19590296, 0.20788328, 0.22029378, 0.23312344, 0.24637487, 0.26004146, 0.27412439, 0.28861129, 0.30349962, 0.31877410, 0.33443448, 0.35045732, 0.36684322, 0.38356093, 0.40060975, 0.41795607, 0.43559330, 0.45349250, 0.47163211, 0.48999236, 0.50853595, 0.52724867, 0.54608860, 0.56503090, 0.58404504, 0.60308800, 0.62213765, 0.64114752, 0.66008031, 0.67890619, 0.69757164, 0.71604492, 0.73429135, 0.75225234, 0.76989792, 0.78719153, 0.80407383, 0.82051349, 0.83646946, 0.85189026, 0.86674791, 0.88100970, 0.89461041, 0.90752456, 0.91973040, 0.93117530, 0.94182765, 0.95167536, 0.96067486, 0.96878747, 0.97601191, 0.98233053, 0.98768615, 0.99208631, 0.99557391, 0.99806302, 0.99942968, 1.00000000] + y: [0.00910000, 0.00920000, 0.00950000, 0.01019102, 0.01098722, 0.01199275, 0.01321968, 0.01466868, 0.01632499, 0.01818939, 0.02026184, 0.02252870, 0.02498942, 0.02765633, 0.03053579, 0.03361281, 0.03688185, 0.04032843, 0.04394211, 0.04771939, 0.05163426, 0.05568122, 0.05982650, 0.06406600, 0.06838366, 0.07275679, 0.07716884, 0.08159288, 0.08599950, 0.09039339, 0.09473603, 0.09901837, 0.10322855, 0.10734222, 0.11135197, 0.11523528, 0.11897139, 0.12256454, 0.12599750, 0.12923028, 0.13226220, 0.13505874, 0.13762622, 0.13995045, 0.14202895, 0.14383082, 0.14537434, 0.14664124, 0.14762855, 0.14834512, 0.14878260, 0.14891816, 0.14878205, 0.14835062, 0.14763902, 0.14665631, 0.14540796, 0.14389445, 0.14211807, 0.14009933, 0.13784051, 0.13536035, 0.13267642, 0.12980554, 0.12676333, 0.12356534, 0.12022661, 0.11677002, 0.11319471, 0.10953142, 0.10578776, 0.10198070, 0.09812104, 0.09422341, 0.09031596, 0.08637472, 0.08242770, 0.07849466, 0.07455546, 0.07065466, 0.06678259, 0.06292689, 0.05912309, 0.05536396, 0.05161464, 0.04793510, 0.04434298, 0.04073649, 0.03723028, 0.03384709, 0.03041742, 0.02707923, 0.02385868, 0.02069172, 0.01762608, 0.01440222, 0.01148451, 0.00815940, 0.00393427, 0.00099040, 0.00000000, -0.00098225, -0.00384270, -0.00790888, -0.01119444, -0.01395867, -0.01702693, -0.01986336, -0.02277707, -0.02585365, -0.02902721, -0.03225419, -0.03547540, -0.03879837, -0.04222416, -0.04566210, -0.04918739, -0.05281184, -0.05645700, -0.06018234, -0.06397734, -0.06781374, -0.07171229, -0.07567108, -0.07964775, -0.08365890, -0.08769058, -0.09172845, -0.09577523, -0.09980010, -0.10380363, -0.10776788, -0.11166856, -0.11550890, -0.11924991, -0.12288261, -0.12639744, -0.12975871, -0.13295904, -0.13598144, -0.13880129, -0.14139247, -0.14375103, -0.14585175, -0.14767899, -0.14921756, -0.15044292, -0.15133677, -0.15189867, -0.15207861, -0.15188281, -0.15127833, -0.15026529, -0.14880776, -0.14689814, -0.14451131, -0.14165209, -0.13829471, -0.13444104, -0.13009554, -0.12525581, -0.11992601, -0.11413531, -0.10791833, -0.10131971, -0.09437959, -0.08717996, -0.07979531, -0.07230410, -0.06481810, -0.05743322, -0.05023546, -0.04333091, -0.03680342, -0.03071971, -0.02512324, -0.02004647, -0.01555998, -0.01170028, -0.00840864, -0.00567019, -0.00346747, -0.00177337, -0.00057044, 0.00016442, 0.00045227, 0.00040686, 0.00004808, -0.00057005, -0.00142772, -0.00241285, -0.00346998, -0.00454941, -0.00560222, -0.00657537, -0.00743116, -0.00814595, -0.00867599, -0.00897410, -0.00910000] + relative_thickness: 0.301 + aerodynamic_center: 0.25 + description: FFA-W3-301 (Re=1.00e+07)FFA-W3 airfoil data for 10 MW sized rotor, computed using EllipSys2D v16.0, 70% free transition, 30% fully turbulent, 360 deg extrapolated using AirfoilPreppy, no 3D correction. F Zahle, DTU Wind Energy 11 May 2017 + polars: + - configuration: Default + re: 1.00E+07 + c_l: + grid: *grid004 + values: [0.0, 0.06508199999999967, 0.1301640000000001, 0.1952459999999998, 0.2603280000000001, 0.3254099999999999, 0.3904919999999995, 0.4555739999999999, 0.5206559999999996, 0.585738, 0.6508199999999997, 0.715902, 0.7809839999999998, 0.846066, 0.9111479999999997, 0.8425694639516688, 0.7818670055437373, 0.7244843098402394, 0.667547427011095, 0.609281551992159, 0.5486798966824511, 0.4853029569076038, 0.4191485137572729, 0.3505614104968701, 0.2801663191638286, 0.2088141134973738, 0.1375365290426018, 0.06750612865947059, 5.551115123125782e-17, -0.06750612865947038, -0.1375365290426018, -0.2088141134973736, -0.2801663191638284, -0.3505614104968701, -0.4191485137572727, -0.4853029569076038, -0.5486798966824509, -0.609281551992159, -0.6675474270110948, -0.7244843098402394, -0.7818670055437373, -0.8425694639516688, -0.9111480000000001, -1.103486, -1.107375, -1.118153, -1.123325, -1.118652, -1.1162, -1.095881, -0.917674, -0.69311, -0.453961, -0.177791, 0.1047965, 0.243828, 0.381107, 0.5166029999999999, 0.650441, 0.782666, 0.913264, 1.042074, 1.16873, 1.29296, 1.4139, 1.53088, 1.64208, 1.74568, 1.83887, 1.91764, 1.97413, 1.99916, 1.99377, 1.9172, 1.73683, 1.47321, 1.36017, 1.30164, 1.203670662788098, 1.116952865062482, 1.034977585486056, 0.9536391814444213, 0.8704022171316559, 0.7838284238320727, 0.693289938439434, 0.5987835910818179, 0.5008020149955287, 0.4002375988054692, 0.2983058764248194, 0.1964807557751455, 0.09643732665638627, 8.326672684688674e-17, -0.06750612865947038, -0.1375365290426018, -0.2088141134973736, -0.2801663191638284, -0.3505614104968701, -0.4191485137572725, -0.4853029569076038, -0.5486798966824507, -0.609281551992159, -0.667547427011095, -0.7244843098402394, -0.7818670055437373, -0.8425694639516688, -0.9111480000000001, -0.846066, -0.7809839999999998, -0.715902, -0.6508199999999997, -0.585738, -0.5206560000000002, -0.4555739999999999, -0.3904920000000002, -0.3254099999999999, -0.2603280000000001, -0.1952459999999998, -0.1301640000000001, -0.06508199999999967, 0.0] + c_d: + grid: *grid004 + values: [0.02453989413077065, 0.02514126696655583, 0.02694148005423172, 0.0299288043911851, 0.03408378132946343, 0.0393793433196372, 0.05910284988155377, 0.08494939547754046, 0.114325143494607, 0.1470439970668735, 0.1828986924361337, 0.2216621162205011, 0.2630887488137909, 0.3069162247345773, 0.352867, 0.4406145937890228, 0.5325517261189517, 0.6267691333264902, 0.7213108445444507, 0.8142149582541823, 0.9035545382610917, 0.9874777756507463, 1.064246578628016, 1.132272784981496, 1.19015124156423, 1.236689060603091, 1.270930442477054, 1.292176547172617, 1.3, 1.292176547172617, 1.270930442477054, 1.236689060603091, 1.19015124156423, 1.132272784981496, 1.064246578628016, 0.9874777756507463, 0.903554538261092, 0.8142149582541823, 0.7213108445444512, 0.6267691333264902, 0.5325517261189519, 0.4406145937890226, 0.352867, 0.217209, 0.156289, 0.10335, 0.0818035, 0.06330720000000001, 0.0471828, 0.032802, 0.0235149, 0.017929, 0.0143063, 0.0124233, 0.01159539, 0.01143042, 0.01138052, 0.0114316, 0.0115641, 0.0117667, 0.0120419, 0.0123928, 0.0128344, 0.0133791, 0.0140591, 0.0148823, 0.0159193, 0.0172569, 0.0190791, 0.0216874, 0.0257196, 0.0322202, 0.0415688, 0.067308, 0.1052596, 0.192293, 0.274492, 0.352867, 0.4406145937890226, 0.5325517261189521, 0.6267691333264902, 0.7213108445444512, 0.8142149582541823, 0.903554538261092, 0.9874777756507463, 1.064246578628016, 1.132272784981496, 1.19015124156423, 1.236689060603091, 1.270930442477054, 1.292176547172617, 1.3, 1.292176547172617, 1.270930442477054, 1.236689060603091, 1.19015124156423, 1.132272784981496, 1.064246578628016, 0.9874777756507463, 0.9035545382610924, 0.8142149582541823, 0.7213108445444507, 0.6267691333264902, 0.5325517261189517, 0.4406145937890228, 0.352867, 0.3069162247345773, 0.2630887488137909, 0.2216621162205011, 0.1828986924361337, 0.1470439970668735, 0.1143251434946074, 0.08494939547754046, 0.05910284988155404, 0.0393793433196372, 0.03408378132946343, 0.0299288043911851, 0.02694148005423172, 0.02514126696655583, 0.02453989413077065] + c_m: + grid: *grid004 + values: [0.0, 0.09142857142857111, 0.1828571428571434, 0.2742857142857145, 0.3657142857142855, 0.4008479484483313, 0.4022046659656612, 0.4035613834829912, 0.4049181010003212, 0.410104381457541, 0.4267793507344299, 0.4434543200113192, 0.4601292892882083, 0.4768042585650976, 0.4716193548383783, 0.4565615711452652, 0.4420245323683359, 0.4345175499598886, 0.4270105675514412, 0.4248258607787128, 0.4254423517089942, 0.426341502984693, 0.428129015345989, 0.4299165277072851, 0.4291631429094398, 0.4278804045367734, 0.4244392242984268, 0.4179402514170336, 0.4114412785356402, 0.3980388844999113, 0.3846364904641824, 0.3689207722089825, 0.3515721192106265, 0.333909388721252, 0.3147390862749883, 0.2955687838287246, 0.2765333377333091, 0.2575408004767999, 0.2387316755889837, 0.2202710342016843, 0.201810392814385, 0.1364412483721886, 0.0675974, 0.0423056, 0.02025520000000002, 0.00406595, 0.0001726999999999999, -0.00166925, -0.00119555, -0.004634049999999997, -0.02493615, -0.04303885, -0.0586752, -0.0760125, -0.0912123, -0.0976331, -0.1034111, -0.1086068, -0.113328, -0.117621, -0.121539, -0.125103, -0.128279, -0.131041, -0.133322, -0.135032, -0.135991, -0.136052, -0.135138, -0.133217, -0.1302, -0.126409, -0.122652, -0.116751, -0.116518, -0.1379, -0.162419, -0.184634, -0.2089385047751449, -0.2327633156993916, -0.2501122596365111, -0.2674612035736305, -0.2836521401300903, -0.2992335991177817, -0.3147151510781997, -0.3298827095243295, -0.3450502679704591, -0.3594241755410271, -0.3736327391791864, -0.3870155054073988, -0.3992283919715195, -0.4114412785356402, -0.4179402514170336, -0.4244392242984268, -0.4278804045367734, -0.4291631429094398, -0.4299165277072851, -0.428129015345989, -0.426341502984693, -0.4254423517089942, -0.4248258607787128, -0.4270105675514412, -0.4345175499598886, -0.4420245323683359, -0.4565615711452652, -0.4716193548383783, -0.4768042585650976, -0.4601292892882083, -0.4434543200113192, -0.4267793507344299, -0.410104381457541, -0.4220609581431781, -0.4435613834829912, -0.465061808822804, -0.4865622341626171, -0.457142857142857, -0.3428571428571431, -0.2285714285714291, -0.1142857142857139, 0.0] + - name: FFA-W3-330blend + coordinates: + x: [1.00000, 0.99170, 0.98339, 0.97509, 0.96678, 0.95847, 0.95016, 0.94186, 0.93356, 0.92526, 0.91697, 0.90869, 0.90042, 0.89215, 0.88388, 0.87562, 0.86736, 0.85911, 0.85086, 0.84261, 0.83437, 0.82613, 0.81789, 0.80966, 0.80143, 0.79320, 0.78497, 0.77674, 0.76852, 0.76029, 0.75207, 0.74385, 0.73562, 0.72740, 0.71918, 0.71096, 0.70273, 0.69451, 0.68629, 0.67806, 0.66983, 0.66161, 0.65338, 0.64514, 0.63691, 0.62867, 0.62044, 0.61219, 0.60395, 0.59570, 0.58745, 0.57920, 0.57094, 0.56268, 0.55441, 0.54614, 0.53787, 0.52959, 0.52130, 0.51301, 0.50471, 0.49641, 0.48813, 0.47986, 0.47159, 0.46330, 0.45501, 0.44671, 0.43840, 0.43008, 0.42176, 0.41342, 0.40507, 0.39672, 0.38835, 0.37998, 0.37159, 0.36320, 0.35479, 0.34638, 0.33796, 0.32953, 0.32109, 0.31264, 0.30418, 0.29572, 0.28725, 0.27878, 0.27030, 0.26182, 0.25334, 0.24486, 0.23638, 0.22790, 0.21943, 0.21097, 0.20253, 0.19410, 0.18570, 0.17733, 0.16898, 0.16068, 0.15243, 0.14423, 0.13610, 0.12803, 0.12004, 0.11215, 0.10436, 0.09669, 0.08915, 0.08176, 0.07453, 0.06748, 0.06064, 0.05402, 0.04763, 0.04151, 0.03567, 0.03015, 0.02498, 0.02018, 0.01577, 0.01178, 0.00830, 0.00551, 0.00340, 0.00177, 0.00052, 0.00000, 0.00048, 0.00174, 0.00344, 0.00581, 0.00892, 0.01269, 0.01696, 0.02165, 0.02674, 0.03223, 0.03808, 0.04424, 0.05068, 0.05737, 0.06428, 0.07139, 0.07869, 0.08616, 0.09378, 0.10153, 0.10940, 0.11738, 0.12545, 0.13361, 0.14184, 0.15013, 0.15849, 0.16691, 0.17537, 0.18388, 0.19242, 0.20099, 0.20960, 0.21822, 0.22687, 0.23554, 0.24423, 0.25292, 0.26162, 0.27033, 0.27905, 0.28776, 0.29648, 0.30519, 0.31389, 0.32259, 0.33128, 0.33996, 0.34863, 0.35728, 0.36592, 0.37455, 0.38315, 0.39174, 0.40031, 0.40885, 0.41738, 0.42588, 0.43435, 0.44281, 0.45123, 0.45963, 0.46801, 0.47636, 0.48469, 0.49300, 0.50128, 0.50954, 0.51777, 0.52599, 0.53418, 0.54235, 0.55050, 0.55863, 0.56675, 0.57484, 0.58293, 0.59100, 0.59905, 0.60710, 0.61517, 0.62324, 0.63131, 0.63937, 0.64743, 0.65549, 0.66356, 0.67163, 0.67971, 0.68780, 0.69590, 0.70402, 0.71216, 0.72031, 0.72849, 0.73669, 0.74491, 0.75315, 0.76143, 0.76973, 0.77805, 0.78641, 0.79480, 0.80321, 0.81165, 0.82012, 0.82861, 0.83712, 0.84566, 0.85422, 0.86280, 0.87141, 0.88003, 0.88866, 0.89733, 0.90601, 0.91468, 0.92334, 0.93199, 0.94063, 0.94925, 0.95784, 0.96639, 0.97489, 0.98332, 0.99168, 1.00000] + y: [0.01245, 0.01460, 0.01675, 0.01888, 0.02100, 0.02310, 0.02520, 0.02730, 0.02940, 0.03151, 0.03364, 0.03578, 0.03793, 0.04009, 0.04226, 0.04443, 0.04660, 0.04878, 0.05097, 0.05315, 0.05535, 0.05754, 0.05973, 0.06193, 0.06412, 0.06630, 0.06849, 0.07067, 0.07285, 0.07503, 0.07720, 0.07937, 0.08153, 0.08369, 0.08584, 0.08798, 0.09011, 0.09223, 0.09434, 0.09644, 0.09853, 0.10062, 0.10268, 0.10474, 0.10678, 0.10881, 0.11083, 0.11283, 0.11481, 0.11678, 0.11874, 0.12067, 0.12259, 0.12449, 0.12636, 0.12822, 0.13005, 0.13187, 0.13365, 0.13542, 0.13715, 0.13886, 0.14053, 0.14218, 0.14378, 0.14536, 0.14689, 0.14838, 0.14984, 0.15124, 0.15260, 0.15392, 0.15518, 0.15639, 0.15755, 0.15865, 0.15969, 0.16067, 0.16158, 0.16242, 0.16319, 0.16389, 0.16451, 0.16504, 0.16549, 0.16586, 0.16613, 0.16630, 0.16636, 0.16632, 0.16615, 0.16587, 0.16546, 0.16491, 0.16423, 0.16340, 0.16240, 0.16125, 0.15991, 0.15839, 0.15668, 0.15478, 0.15266, 0.15033, 0.14779, 0.14502, 0.14203, 0.13880, 0.13531, 0.13157, 0.12757, 0.12331, 0.11877, 0.11395, 0.10886, 0.10348, 0.09783, 0.09189, 0.08568, 0.07919, 0.07242, 0.06539, 0.05810, 0.05056, 0.04277, 0.03473, 0.02648, 0.01806, 0.00955, 0.00098, -0.00761, -0.01613, -0.02443, -0.03254, -0.04061, -0.04840, -0.05589, -0.06310, -0.07004, -0.07669, -0.08304, -0.08910, -0.09487, -0.10036, -0.10559, -0.11054, -0.11522, -0.11964, -0.12380, -0.12771, -0.13138, -0.13483, -0.13805, -0.14107, -0.14388, -0.14648, -0.14889, -0.15111, -0.15313, -0.15498, -0.15666, -0.15816, -0.15950, -0.16068, -0.16169, -0.16255, -0.16325, -0.16381, -0.16421, -0.16447, -0.16458, -0.16456, -0.16440, -0.16410, -0.16368, -0.16312, -0.16244, -0.16164, -0.16071, -0.15967, -0.15851, -0.15723, -0.15585, -0.15435, -0.15274, -0.15103, -0.14921, -0.14729, -0.14527, -0.14316, -0.14094, -0.13864, -0.13625, -0.13378, -0.13123, -0.12861, -0.12592, -0.12316, -0.12034, -0.11746, -0.11452, -0.11153, -0.10849, -0.10540, -0.10226, -0.09909, -0.09589, -0.09265, -0.08939, -0.08611, -0.08281, -0.07950, -0.07618, -0.07286, -0.06955, -0.06624, -0.06295, -0.05968, -0.05644, -0.05323, -0.05005, -0.04693, -0.04386, -0.04085, -0.03790, -0.03502, -0.03221, -0.02949, -0.02685, -0.02431, -0.02187, -0.01954, -0.01733, -0.01523, -0.01325, -0.01138, -0.00965, -0.00805, -0.00658, -0.00526, -0.00410, -0.00310, -0.00227, -0.00161, -0.00114, -0.00084, -0.00073, -0.00081, -0.00107, -0.00152, -0.00216, -0.00302, -0.00415, -0.00559, -0.00734, -0.00939, -0.01163] + relative_thickness: 0.33 + aerodynamic_center: 0.25 + description: FFA-W3-301 (Re=1.00e+07)FFA-W3 airfoil data for 10 MW sized rotor, computed using EllipSys2D v16.0, 70% free transition, 30% fully turbulent, 360 deg extrapolated using AirfoilPreppy, no 3D correction. F Zahle, DTU Wind Energy 11 May 2017 + polars: + - configuration: Default + re: 1.00E+07 + c_l: + grid: *grid004 + values: [0.0, 0.06960149999999965, 0.1392030000000001, 0.2088044999999997, 0.2784060000000002, 0.3480074999999999, 0.4176089999999995, 0.4872104999999998, 0.5568119999999995, 0.6264135, 0.6960149999999997, 0.7656165, 0.8352179999999998, 0.9048195000000001, 0.9744209999999996, 0.8941183973804908, 0.8238242323892865, 0.7584487054483077, 0.694771700400014, 0.6307857502445491, 0.5653233786594135, 0.4978317825846658, 0.4282276639730173, 0.3567973260845799, 0.2841230983261536, 0.2110254796034336, 0.1385149580974572, 0.06775009005483451, 5.551115123125782e-17, -0.06775009005483429, -0.1385149580974572, -0.2110254796034334, -0.2841230983261534, -0.3567973260845799, -0.4282276639730171, -0.4978317825846658, -0.5653233786594133, -0.6307857502445491, -0.6947717004000136, -0.7584487054483077, -0.8238242323892863, -0.8941183973804909, -0.974421, -1.163075, -1.14892, -1.094505, -1.058015, -1.022809, -0.9981, -0.9851519999999999, -0.8958320000000001, -0.675386, -0.432471, -0.15881, 0.1345552, 0.280145, 0.423864, 0.565193, 0.704102, 0.840706, 0.9750019999999999, 1.1068, 1.23603, 1.36223, 1.48424, 1.60097, 1.7101, 1.80957, 1.89473, 1.95698, 1.98576, 1.9926, 1.99617, 1.96398, 1.81179, 1.56073, 1.46798, 1.39203, 1.277311996257844, 1.176891760556123, 1.08349815064044, 0.992531000571448, 0.9011225003493559, 0.8076048266563048, 0.7111882608352368, 0.6117538056757384, 0.5097104658351143, 0.4058901404659335, 0.3014649708620477, 0.1978785115677961, 0.09678584293547757, 8.326672684688674e-17, -0.06775009005483429, -0.1385149580974572, -0.2110254796034334, -0.2841230983261534, -0.3567973260845799, -0.4282276639730169, -0.4978317825846658, -0.5653233786594131, -0.6307857502445491, -0.694771700400014, -0.7584487054483077, -0.8238242323892865, -0.8941183973804908, -0.9744210000000001, -0.9048195000000001, -0.8352179999999998, -0.7656165, -0.6960149999999997, -0.6264135, -0.5568120000000003, -0.4872104999999998, -0.4176090000000002, -0.3480074999999999, -0.2784060000000002, -0.2088044999999997, -0.1392030000000001, -0.06960149999999965, 0.0] + c_d: + grid: *grid004 + values: [0.03168571521941585, 0.03228140266288518, 0.03406456851341789, 0.0370235109307087, 0.04113881642980572, 0.04638348058883583, 0.05732035840915397, 0.08318567098401346, 0.1125829925233925, 0.1453261918323379, 0.1812079664122574, 0.2200011597912121, 0.2614602049918655, 0.305322684955921, 0.351311, 0.4391329017052526, 0.5311520852033039, 0.6254588580353811, 0.7200967823138212, 0.8131034537151256, 0.9025514000831145, 0.9865882461859409, 1.063475306514778, 1.131623800850965, 1.189627936976401, 1.236294170322686, 1.270666030181773, 1.292043994668456, 1.3, 1.292043994668456, 1.270666030181773, 1.236294170322686, 1.189627936976401, 1.131623800850965, 1.063475306514778, 0.9865882461859409, 0.9025514000831149, 0.8131034537151256, 0.7200967823138217, 0.6254588580353811, 0.5311520852033041, 0.4391329017052525, 0.351311, 0.206482, 0.150009, 0.1059962, 0.0873187, 0.0705096, 0.0547408, 0.040516, 0.0292903, 0.0220672, 0.0173521, 0.0147274, 0.0136237, 0.0133913, 0.0132988, 0.0133253, 0.0134498, 0.0136649, 0.0139703, 0.0143689, 0.0148623, 0.0154718, 0.016227, 0.0171756, 0.0184081, 0.0200962, 0.0225804, 0.0267093, 0.0338017, 0.0433308, 0.0535441, 0.0770596, 0.1116943, 0.191027, 0.271993, 0.351311, 0.4391329017052525, 0.5311520852033044, 0.6254588580353811, 0.7200967823138217, 0.8131034537151256, 0.9025514000831149, 0.9865882461859409, 1.063475306514779, 1.131623800850965, 1.189627936976401, 1.236294170322686, 1.270666030181773, 1.292043994668456, 1.3, 1.292043994668456, 1.270666030181773, 1.236294170322686, 1.189627936976401, 1.131623800850965, 1.063475306514779, 0.9865882461859409, 0.9025514000831153, 0.8131034537151256, 0.7200967823138212, 0.6254588580353811, 0.5311520852033039, 0.4391329017052526, 0.351311, 0.305322684955921, 0.2614602049918655, 0.2200011597912121, 0.1812079664122574, 0.1453261918323379, 0.1125829925233929, 0.08318567098401346, 0.05732035840915425, 0.04638348058883583, 0.04113881642980572, 0.0370235109307087, 0.03406456851341789, 0.03228140266288518, 0.03168571521941585] + c_m: + grid: *grid004 + values: [0.0, 0.09142857142857111, 0.1828571428571434, 0.2742857142857145, 0.3657142857142855, 0.4030821424574265, 0.4080135703893089, 0.4129449983211914, 0.4178764262530736, 0.4258631889251984, 0.4430159558180491, 0.4601687227109004, 0.4773214896037513, 0.4944742564966026, 0.4874268393378757, 0.4683872766528996, 0.4499633576163694, 0.4398506278338583, 0.4297378980513473, 0.4258929905516545, 0.425346936885024, 0.4252813661659265, 0.4267258847105035, 0.4281704032550807, 0.4274502570093805, 0.4262791389323726, 0.4230280176733544, 0.4168302252398217, 0.4106324328062891, 0.3975198115445945, 0.3844071902829, 0.3690545818236761, 0.352120805930902, 0.3349050700922636, 0.3163359265134768, 0.2977667829346901, 0.2794656421081109, 0.2612497748845071, 0.2432235332181071, 0.2255575801103945, 0.2078916270026819, 0.1373055674439705, 0.0627995, 0.0390491, 0.01853040000000002, 0.00440755, -0.00060625, -0.0034157, -0.004014449999999999, -0.002720500000000002, -0.0119793, -0.0345781, -0.05465579999999999, -0.0742549, -0.0926969, -0.1007386, -0.1080164, -0.114496, -0.12028, -0.125458, -0.130108, -0.134252, -0.137926, -0.14108, -0.143626, -0.145449, -0.146355, -0.146348, -0.145444, -0.143779, -0.141846, -0.140038, -0.138226, -0.133505, -0.131348, -0.1466, -0.172424, -0.194168, -0.2179194639550666, -0.2411497346779242, -0.2573438967659587, -0.2735380588539932, -0.2886164715857071, -0.3031076478140942, -0.3175696519048828, -0.3319399721346473, -0.3463102923644117, -0.3601353362640616, -0.3738467809282708, -0.3868127372299756, -0.3987225850181323, -0.4106324328062891, -0.4168302252398217, -0.4230280176733544, -0.4262791389323726, -0.4274502570093805, -0.4281704032550807, -0.4267258847105035, -0.4252813661659265, -0.4253469368850241, -0.4258929905516545, -0.4297378980513473, -0.4398506278338583, -0.4499633576163694, -0.4683872766528996, -0.4874268393378757, -0.4944742564966026, -0.4773214896037513, -0.4601687227109004, -0.4430159558180491, -0.4258631889251983, -0.4350192833959307, -0.4529449983211913, -0.4708707132464517, -0.4887964281717123, -0.457142857142857, -0.3428571428571431, -0.2285714285714291, -0.1142857142857139, 0.0] + - name: FFA-W3-360 + coordinates: + x: [1.00000000, 0.99944304, 0.99812049, 0.99569352, 0.99230484, 0.98802844, 0.98281508, 0.97666422, 0.96964069, 0.96174313, 0.95297315, 0.94338928, 0.93301284, 0.92185147, 0.90995468, 0.89736121, 0.88408503, 0.87016290, 0.85565276, 0.84057695, 0.82497463, 0.80889455, 0.79236237, 0.77542101, 0.75812546, 0.74050180, 0.72259209, 0.70444539, 0.68608843, 0.66757021, 0.64892678, 0.63018643, 0.61140138, 0.59259673, 0.57380843, 0.55507570, 0.53641763, 0.51787958, 0.49948103, 0.48125155, 0.46322225, 0.44540666, 0.42784323, 0.41053864, 0.39352525, 0.37681123, 0.36041977, 0.34436494, 0.32865846, 0.31331898, 0.29834798, 0.28376580, 0.26956679, 0.25577362, 0.24237780, 0.22939648, 0.21681735, 0.20465763, 0.19290757, 0.18157496, 0.17065819, 0.16014896, 0.15005511, 0.14035465, 0.13106750, 0.12216148, 0.11365876, 0.10553619, 0.09779065, 0.09042902, 0.08341621, 0.07677403, 0.07046920, 0.06450016, 0.05888182, 0.05356799, 0.04857581, 0.04389793, 0.03949498, 0.03539484, 0.03157626, 0.02800644, 0.02471592, 0.02168071, 0.01886319, 0.01629514, 0.01396620, 0.01181764, 0.00988361, 0.00818368, 0.00663128, 0.00524853, 0.00408271, 0.00308998, 0.00219098, 0.00145967, 0.00096333, 0.00059878, 0.00028988, 0.00007804, 0.00000000, 0.00007807, 0.00029009, 0.00059937, 0.00096448, 0.00146264, 0.00219661, 0.00309879, 0.00409516, 0.00526774, 0.00665839, 0.00821941, 0.00993095, 0.01187982, 0.01404463, 0.01639219, 0.01898469, 0.02182867, 0.02489252, 0.02822001, 0.03182924, 0.03568998, 0.03984236, 0.04430035, 0.04903788, 0.05410025, 0.05948747, 0.06518787, 0.07124791, 0.07764648, 0.08439704, 0.09152340, 0.09900711, 0.10688721, 0.11514762, 0.12380644, 0.13287211, 0.14233176, 0.15221460, 0.16249918, 0.17321393, 0.18434125, 0.19590296, 0.20788328, 0.22029378, 0.23312344, 0.24637487, 0.26004146, 0.27412439, 0.28861129, 0.30349962, 0.31877410, 0.33443448, 0.35045732, 0.36684322, 0.38356093, 0.40060975, 0.41795607, 0.43559330, 0.45349250, 0.47163211, 0.48999236, 0.50853595, 0.52724867, 0.54608860, 0.56503090, 0.58404504, 0.60308800, 0.62213765, 0.64114752, 0.66008031, 0.67890619, 0.69757164, 0.71604492, 0.73429135, 0.75225234, 0.76989792, 0.78719153, 0.80407383, 0.82051349, 0.83646946, 0.85189026, 0.86674791, 0.88100970, 0.89461041, 0.90752456, 0.91973040, 0.93117530, 0.94182765, 0.95167536, 0.96067486, 0.96878747, 0.97601191, 0.98233053, 0.98768615, 0.99208631, 0.99557391, 0.99806302, 0.99942968, 1.00000000] + y: [0.01298, 0.01325853, 0.01353706, 0.01425486, 0.01525428, 0.01651103, 0.01803672, 0.01982832, 0.02186386, 0.02414104, 0.02665721, 0.02939433, 0.03232052, 0.03546139, 0.03882788, 0.04241907, 0.04621907, 0.0502115, 0.05437484, 0.05871183, 0.06320372, 0.06783179, 0.07258367, 0.0774446, 0.08239019, 0.0874051, 0.09248713, 0.09759082, 0.10272181, 0.10784802, 0.11295158, 0.11803077, 0.12305212, 0.12798755, 0.13283094, 0.13755364, 0.14213236, 0.14655625, 0.15079572, 0.154834, 0.15864807, 0.16221448, 0.16552639, 0.16856113, 0.17131655, 0.17376419, 0.17590809, 0.17773697, 0.17926124, 0.1804571, 0.18132691, 0.18187835, 0.18211123, 0.18203856, 0.1816423, 0.18093968, 0.17992886, 0.17862311, 0.17701528, 0.17511379, 0.17293277, 0.17048889, 0.16779353, 0.16485463, 0.16169458, 0.15833173, 0.15478881, 0.15107876, 0.14720829, 0.1431886, 0.13900782, 0.13472385, 0.13032428, 0.12581676, 0.12122098, 0.1165324, 0.11179119, 0.1070022, 0.10214717, 0.09729272, 0.09242295, 0.08751215, 0.082622, 0.07774621, 0.07282291, 0.06792968, 0.06314469, 0.05839354, 0.05371176, 0.04930104, 0.04462295, 0.03955245, 0.03423391, 0.02812695, 0.02159887, 0.01655986, 0.01252495, 0.0086197, 0.00433033, 0.00116483, 0, -0.00122141, -0.00493025, -0.01006582, -0.01404194, -0.01818461, -0.02325986, -0.02895118, -0.03402694, -0.0386738, -0.04323037, -0.04768713, -0.05194808, -0.05640002, -0.06095587, -0.06550064, -0.07015335, -0.07483557, -0.07944017, -0.08403249, -0.08861876, -0.0931531, -0.09767683, -0.10219736, -0.1066798, -0.1111375, -0.11555036, -0.11988963, -0.12416757, -0.12835267, -0.13244594, -0.13644393, -0.14032619, -0.14410659, -0.14776228, -0.15130027, -0.15469824, -0.15792918, -0.16099444, -0.1638649, -0.16652604, -0.16898536, -0.1712123, -0.17318495, -0.17488931, -0.17629521, -0.1773821, -0.17812893, -0.17850404, -0.17851925, -0.17811179, -0.17729404, -0.17604386, -0.17433908, -0.17216093, -0.1694972, -0.1663269, -0.16263694, -0.15840197, -0.15361785, -0.14832974, -0.14256755, -0.13636209, -0.12973893, -0.12273428, -0.11537409, -0.10772338, -0.09984354, -0.09178588, -0.08364761, -0.07551824, -0.06749519, -0.05967989, -0.05219166, -0.04509834, -0.03849862, -0.03246027, -0.02700348, -0.02213341, -0.01786296, -0.01419831, -0.0111631, -0.00876892, -0.00697163, -0.00574853, -0.0050322, -0.00479161, -0.004, -0.0037, -0.0034, -0.0031, -0.0028, -0.0025, -0.0022, -0.0019, -0.0016, -0.0013, -0.001, -0.0007, -0.0007] + relative_thickness: 0.36 + aerodynamic_center: 0.25 + description: FFA-W3-360 (Re=1.00e+07)FFA-W3 airfoil data for 10 MW sized rotor, computed using EllipSys2D v16.0, 70% free transition, 30% fully turbulent, 360 deg extrapolated using AirfoilPreppy, no 3D correction. F Zahle, DTU Wind Energy 11 May 2017 + polars: + - configuration: Default + re: 1.00E+07 + c_l: + grid: *grid004 + values: [0.0, 0.07178149999999964, 0.1435630000000001, 0.2153444999999997, 0.2871260000000001, 0.3589074999999998, 0.4306889999999995, 0.5024704999999998, 0.5742519999999995, 0.6460334999999998, 0.7178149999999996, 0.7895965, 0.8613779999999996, 0.9331595000000001, 1.004941, 0.9189832441278815, 0.8440624787712528, 0.7748315757714827, 0.7079034441742906, 0.6411583915080097, 0.5733514328047344, 0.5038751148063706, 0.432607030599928, 0.3598052464255929, 0.2860316675426308, 0.2120921414269119, 0.1389869075032731, 0.06786776586894963, 5.551115123125782e-17, -0.06786776586894941, -0.1389869075032731, -0.2120921414269117, -0.2860316675426306, -0.3598052464255929, -0.4326070305999278, -0.5038751148063706, -0.5733514328047343, -0.6411583915080097, -0.7079034441742903, -0.7748315757714827, -0.8440624787712528, -0.9189832441278818, -1.004941, -1.113059, -1.054248, -0.982473, -0.9417260000000001, -0.893331, -0.8547150000000001, -0.82348, -0.795409, -0.636498, -0.390949, -0.1307082, 0.1617258, 0.311214, 0.459562, 0.605659, 0.748677, 0.888617, 1.025442, 1.15878, 1.28822, 1.41282, 1.5309, 1.64065, 1.73926, 1.81971, 1.87065, 1.89221, 1.8791, 1.88111, 1.86359, 1.73324, 1.59357, 1.46708, 1.44834, 1.43563, 1.312833205896974, 1.20580354110179, 1.106902251102118, 1.011290634534701, 0.9159405592971568, 0.8190734754353348, 0.7198215925805296, 0.6180100437141822, 0.5140074948937041, 0.4086166679180437, 0.3029887734670167, 0.1985527250046759, 0.09695395124135632, 8.326672684688674e-17, -0.06786776586894941, -0.1389869075032731, -0.2120921414269117, -0.2860316675426306, -0.3598052464255929, -0.4326070305999276, -0.5038751148063706, -0.5733514328047341, -0.6411583915080097, -0.7079034441742906, -0.7748315757714827, -0.8440624787712528, -0.9189832441278815, -1.004941, -0.9331595000000001, -0.8613779999999996, -0.7895965, -0.7178149999999996, -0.6460334999999998, -0.5742520000000002, -0.5024704999999998, -0.4306890000000002, -0.3589074999999998, -0.2871260000000001, -0.2153444999999997, -0.1435630000000001, -0.07178149999999964, 0.0] + c_d: + grid: *grid004 + values: [0.03714766955647932, 0.03773901133374287, 0.03950914701885524, 0.04244639553319605, 0.04653137791244361, 0.05173713798667876, 0.06067799498087878, 0.08650795664630631, 0.1158646406574733, 0.1485619804842812, 0.1843927466024767, 0.2231298637078226, 0.2645278540548881, 0.3083243977394954, 0.354242, 0.4419239296599637, 0.5337885553342574, 0.6279269922758957, 0.7223836823125198, 0.8151971682421021, 0.9044409874864893, 0.9882638315724093, 1.064928133355331, 1.132846276742087, 1.190613673317614, 1.237038015703063, 1.271164097300969, 1.29229368065155, 1.3, 1.29229368065155, 1.271164097300969, 1.237038015703063, 1.190613673317614, 1.132846276742087, 1.064928133355331, 0.9882638315724093, 0.9044409874864896, 0.8151971682421021, 0.7223836823125203, 0.6279269922758957, 0.5337885553342576, 0.4419239296599635, 0.354242, 0.204936, 0.154343, 0.1096723, 0.09248859999999999, 0.07597419999999999, 0.0605412, 0.0464115, 0.0344101, 0.0254821, 0.0199403, 0.0165337, 0.0150697, 0.0147703, 0.0146486, 0.0146633, 0.0148131, 0.0150716, 0.0154399, 0.0159264, 0.0165411, 0.0173108, 0.0183096, 0.0196309, 0.0214988, 0.0244544, 0.0296621, 0.0376996, 0.0482436, 0.0583757, 0.0699237, 0.1016591, 0.139159, 0.210024, 0.282003, 0.354242, 0.4419239296599635, 0.5337885553342578, 0.6279269922758957, 0.7223836823125203, 0.8151971682421021, 0.9044409874864896, 0.9882638315724093, 1.064928133355331, 1.132846276742087, 1.190613673317614, 1.237038015703063, 1.271164097300969, 1.29229368065155, 1.3, 1.29229368065155, 1.271164097300969, 1.237038015703063, 1.190613673317614, 1.132846276742087, 1.064928133355331, 0.9882638315724093, 0.9044409874864899, 0.8151971682421021, 0.7223836823125198, 0.6279269922758957, 0.5337885553342574, 0.4419239296599637, 0.354242, 0.3083243977394954, 0.2645278540548881, 0.2231298637078226, 0.1843927466024767, 0.1485619804842812, 0.1158646406574736, 0.08650795664630631, 0.06067799498087906, 0.05173713798667876, 0.04653137791244361, 0.04244639553319605, 0.03950914701885524, 0.03773901133374287, 0.03714766955647932] + c_m: + grid: *grid004 + values: [0.0, 0.09142857142857111, 0.1828571428571434, 0.2742857142857145, 0.3657142857142855, 0.4031304636095545, 0.4081392053848416, 0.4131479471601289, 0.418156688935416, 0.4262712900595553, 0.4437034692302504, 0.461135648400946, 0.4785678275716412, 0.4960000067423368, 0.4883022318330648, 0.4678431517000181, 0.4480322988503198, 0.4369725143258274, 0.4259127298013352, 0.421499653933276, 0.4205848457791291, 0.4202386282975503, 0.4216794100726142, 0.4231201918476781, 0.4225819015145725, 0.4216313044922648, 0.4186442496773847, 0.4127722129896937, 0.4069001763020025, 0.3942614888768985, 0.3816228014517944, 0.3667605953544247, 0.3503288466648674, 0.3336237480028024, 0.3156065694727009, 0.2975893909425995, 0.2798918070549701, 0.2622959123717636, 0.2449149435317124, 0.2279423337936562, 0.2109697240555999, 0.1352478121095765, 0.0551741, 0.0321142, 0.01267890000000002, -0.002821250000000001, -0.0074129, -0.0110695, -0.01250065, -0.01177275, -0.01082065, -0.0276937, -0.05106789999999999, -0.0714813, -0.0917883, -0.1011907, -0.1098835, -0.1177635, -0.124769, -0.130977, -0.136484, -0.141299, -0.1454, -0.14875, -0.151175, -0.15262, -0.153103, -0.152545, -0.151206, -0.149693, -0.145621, -0.143578, -0.140948, -0.137106, -0.140821, -0.156927, -0.179786, -0.201472, -0.2240866097136903, -0.2461860684392151, -0.2613309888245065, -0.2764759092097978, -0.2906206368276409, -0.3042389471994587, -0.3178704108777556, -0.3315432135192723, -0.3452160161607889, -0.3584572127501313, -0.3716084914119372, -0.3840462584573971, -0.3954732173796998, -0.4069001763020025, -0.4127722129896937, -0.4186442496773847, -0.4216313044922648, -0.4225819015145725, -0.4231201918476781, -0.4216794100726143, -0.4202386282975503, -0.4205848457791291, -0.421499653933276, -0.4259127298013352, -0.4369725143258274, -0.4480322988503198, -0.4678431517000181, -0.4883022318330649, -0.4960000067423369, -0.4785678275716413, -0.461135648400946, -0.4437034692302504, -0.4262712900595553, -0.4352995460782729, -0.4531479471601288, -0.4709963482419844, -0.4888447493238403, -0.457142857142857, -0.3428571428571431, -0.2285714285714291, -0.1142857142857139, 0.0] + +materials: + - name: Gelcoat + orth: 0 + rho: 1235.0 + E: 3.440e+009 + G: 1.323e+009 + nu: 0.300 + alpha: 0.0 + Xt: 74 + Xc: 87 + S: 2.126E7 + GIc: 303 + GIIc: 3446 + alp0: 53 + ply_t: 5.0E-4 + waste: 0.25 + unit_cost: 7.23 + component_id: 0 + - name: steel + description: Steel of the tower and monopile ASTM A572 Grade 50 + source: http://www.matweb.com/search/DataSheet.aspx?MatGUID=9ced5dc901c54bd1aef19403d0385d7f + orth: 0 + rho: 7800 + alpha: 0.0 + E: 200.e+009 + nu: 0.3 + G: 79.3e+009 + GIc: 0 #Place holder, currently not used + GIIc: 0 #Place holder, currently not used + alp0: 0 #Place holder, currently not used + Xt: 450.e+006 + Xc: 450.e+006 + S: 0 + Xy: 345.e+6 + unit_cost: 0.7 + - name: steel_drive + description: Steel of the drivetrain ASTM 4140 40Cr1Mo28 + source: http://www.matweb.com/search/DataSheet.aspx?MatGUID=38108bfd64c44b4c9c6a02af78d5b6c6 + orth: 0 + rho: 7850 + alpha: 0.0 + E: 205.e+009 + nu: 0.3 + G: 80.0e+009 + GIc: 0 #Place holder, currently not used + GIIc: 0 #Place holder, currently not used + alp0: 0 #Place holder, currently not used + Xt: 814.e+006 + Xc: 814.e+006 + S: 0 + Xy: 485.e+6 + unit_cost: 0.9 + - name: cast_iron + description: TODO Steel of the drivetrain ASTM 4140 40Cr1Mo28 + source: TODO http://www.matweb.com/search/DataSheet.aspx?MatGUID=38108bfd64c44b4c9c6a02af78d5b6c6 + orth: 0 + rho: 7200 + alpha: 0.0 + E: 118.e+009 + nu: 0.3 + G: 47.6e+009 + GIc: 0 #Place holder, currently not used + GIIc: 0 #Place holder, currently not used + alp0: 0 #Place holder, currently not used + Xt: 310.e+006 + Xc: 310.e+006 + S: 0 + Xy: 265.e+6 + unit_cost: 0.5 + - name: glass_uni + description: Vectorply E-LT-5500, Epikote MGS RIMR 135/Epicure MGS RIMH 1366 epoxy + source: MSU composites database 3D property tests, Engineering Mechanics of Composite Materials, Daniel, I & Ishai, O., 1994, pg. 34 + orth: 1 + rho: 1940.0 + E: [4.46E10, 1.7E10, 1.67E10] + G: [3.27E9, 3.48E9, 3.5E9] + nu: [0.262, 0.35, 0.264] + Xt: [6.092E8, 3.81E7, 1.529E7] + Xc: [4.7471E8, 1.1264E8, 1.1322E8] + S: [1.891E7, 1.724E7, 1.316E7] + GIc: 303 + GIIc: 3446 + alp0: 53 + fvf: 0.57 + fwf: 0.7450682696347697 + ply_t: 0.005 + unit_cost: 1.87 + waste: 0.05 + fiber_density: 2535.5 + area_density_dry: 7.227162215457267 + component_id: 5 + - name: CarbonUD + E: [114500000000.0, 8390000000.0, 8390000000.0] + G: [5990000000.0, 5990000000.0, 5990000000.0] + rho: 1500.0 + orth: 1 + nu: [0.27, 0.27, 0.27] + Xt: [1546.e6, 0.0, 0.0] + Xc: [1047.e6, 0.0, 0.0] + S: [0.0, 0.0, 0.0] + GIc: 0.0 + GIIc: 0.0 + alp0: 0.0 + fvf: 0.5384615384615385 + fwf: 0.6461538461538463 + ply_t: 0.0010317460317460314 + unit_cost: 30. + waste: 0.05 + fiber_density: 1800. + area_density_dry: 1.000 + component_id: 4 +# - name: Carbon_SNL +# E: [149.4e+09, 9.1e+09, 9.1e+09] +# G: [3.0e+09, 3.0e+09, 2.6e+09] +# rho: 1600.0 +# orth: 1 +# nu: [0.323, 0.323, None] +# Xt: [1357.e+06, 0.0, 0.0] +# Xc: [1183.e+06, 0.0, 0.0] +# ply_t: 0.004 +# unit_cost: 8.38 +# waste: 0.05 +# fiber_density: 1800. +# component_id: 4 + - name: glass_biax + description: Vectorply E-LT-5500, Epikote MGS RIMR 135/Epicure MGS RIMH 1366 epoxy + source: MSU composites database 3D property tests, Engineering Mechanics of Composite Materials, Daniel, I & Ishai, O., 1994, pg. 34 + orth: 1 + rho: 1940.0 + E: [1.11E10, 1.11E10, 1.67E10] + G: [1.353E10, 3.49E9, 3.49E9] + nu: [0.5, 0.0, 0.066] + Xt: [4.29E7, 4.26E7, 1.53E7] + Xc: [7.07E7, 7.07E7, 1.132E8] + S: [1.034E8, 1.72E7, 1.32E7] + GIc: 303 + GIIc: 3446 + alp0: 53 + fvf: 0.57 + fwf: 0.7450682696347697 + ply_t: 0.001 + waste: 0.15 + unit_cost: 3.00 + fiber_density: 2535.5 + area_density_dry: 1.4454324430914534 + component_id: 3 + roll_mass: 181.4368 + - name: glass_triax + description: Vectorply E-LT-5500, Epikote MGS RIMR 135/Epicure MGS RIMH 1366 epoxy + source: MSU composites database 3D property tests, Engineering Mechanics of Composite Materials, Daniel, I & Ishai, O., 1994, pg. 34 + orth: 1.0 + rho: 1940.0 + E: [2.87E10, 1.66E10, 1.67E10] + G: [8.4E9, 3.49E9, 3.49E9] + nu: [0.5, 0.0, 0.17] + Xt: [3.96E8, 7.64E7, 1.53E7] + Xc: [4.489E8, 1.747E8, 1.132E8] + S: [1.034E8, 1.72E7, 1.32E7] + GIc: 303 + GIIc: 3446 + alp0: 53 + fvf: 0.57 + fwf: 0.7450682696347697 + ply_t: 0.001 + unit_cost: 2.86 + waste: 0.15 + fiber_density: 2535.5 + area_density_dry: 1.4454324430914534 + component_id: 2 + roll_mass: 181.4368 +# - name: balsa +# description: 1-2-3 are radial, tangential, axial, G12 calculated from assumed isotropy +# source: M.Ashby 2011 Materials Selection in Mech Design (4th Ed) and K.E.Easterling 1982 On the mechanics of balsa and other woods +# rho: 110.0 +# E: [0.05e+009, 0.05e+009, 2.73e+009] +# G: [0.01667e+009, 0.15e+009, 0.15e+009] +# nu: [0.5, 0.013, 0.013] +# Xt: [0.0, 0.0, 0.0] +# Xc: [0.0, 0.0, 0.0] +# orth: 1 +# ply_t: 0.005 +# component_id: 1 +# waste: 0.2 +# unit_cost: 13 + - name: medium_density_foam + description: Airex C70.130 PVC Foam, source 'https://www.3accorematerials.com/uploads/documents/TDS-AIREX-C70-E_1106.pdf' + orth: 0.0 + rho: 130.0 + E: 1.292E8 + G: 4.8946969696969695E7 + nu: 0.32 + Xt: 2083000.0 + Xc: 1563000.0 + S: 1250000.0 + GIc: 303 + GIIc: 3446 + alp0: 53 + component_id: 1 + waste: 0.2 + unit_cost: 13 + - name: resin + description: epoxy + E: 1.e+6 + nu: 0.3 #Place holder, currently not used + G: 312500.0 #Place holder, currently not used + GIc: 0 #Place holder, currently not used + GIIc: 0 #Place holder, currently not used + alp0: 0 #Place holder, currently not used + Xt: 0 #Place holder, currently not used + Xc: 0 #Place holder, currently not used + S: 0 #Place holder, currently not used + rho: 1150. + alpha: 0.0 + orth: 0 + unit_cost: 3.63 + +control: + supervisory: + Vin: 3.0 + Vout: 25.0 + maxTS: 95. + pitch: + PC_zeta: 1.0 # Pitch controller desired damping ratio [-] + PC_omega: 0.2 # Pitch controller desired natural frequency [rad/s] + ps_percent: 0.8 # Percent peak shaving [%, <= 1 ], {default = 80%} + max_pitch: 1.57 # Maximum pitch angle [rad], {default = 90 degrees} + max_pitch_rate: 0.03490658503988659 # 2 deg/s + min_pitch: 0. # Minimum pitch angle [rad], {default = 0 degrees} + torque: + control_type: tsr_tracking + tsr: 9.0 + VS_zeta: 1.0 # Torque controller desired damping ratio [-] + VS_omega: 0.2 # Torque controller desired natural frequency [rad/s] + max_torque_rate: 1500000. + VS_minspd: 0.52 # Minimum rotor speed [rad/s], {default = 0 rad/s} + VS_maxspd: 0.8 # Minimum rotor speed [rad/s], {default = 0 rad/s} + setpoint_smooth: + ss_vsgain: 1 # Torque controller setpoint smoother gain bias percentage [%, <= 1 ], {default = 100%} + ss_pcgain: .001 # Pitch controller setpoint smoother gain bias percentage [%, <= 1 ], {default = 0.1%} + shutdown: + limit_type: gen_speed + limit_value: 2.0 + +environment: + air_density: 1.225 + air_dyn_viscosity: 1.81e-5 + weib_shape_parameter: 2. + air_speed_sound: 340. + shear_exp: 0.2 + water_density: 1025.0 + water_dyn_viscosity: 1.3351e-3 + soil_shear_modulus: 140e6 + soil_poisson: 0.4 + water_depth: 30.0 + significant_wave_height: 4.52 + significant_wave_period: 9.45 + +bos: + plant_turbine_spacing: 7 + plant_row_spacing: 7 + commissioning_pct: 0.01 + decommissioning_pct: 0.15 + distance_to_substation: 1.0 + distance_to_interconnection: 8.5 + interconnect_voltage: 130. + distance_to_site: 115. + distance_to_landfall: 50. + port_cost_per_month: 2e6 + site_auction_price: 100e6 + site_assessment_plan_cost: 1e6 + site_assessment_cost: 25e6 + construction_operations_plan_cost: 2.5e6 + boem_review_cost: 0.0 + design_install_plan_cost: 2.5e6 + +costs: + wake_loss_factor: 0.15 + fixed_charge_rate: 0.056 + bos_per_kW: 4053. + opex_per_kW: 137. + turbine_number: 40. + labor_rate: 58.8 + painting_rate: 30.0 + blade_mass_cost_coeff: 14.6 + hub_mass_cost_coeff: 3.9 + pitch_system_mass_cost_coeff: 22.1 + spinner_mass_cost_coeff: 11.1 + lss_mass_cost_coeff: 11.9 + bearing_mass_cost_coeff: 4.5 + gearbox_mass_cost_coeff: 12.9 + hss_mass_cost_coeff: 6.8 + generator_mass_cost_coeff: 12.4 + bedplate_mass_cost_coeff: 2.9 + yaw_mass_cost_coeff: 8.3 + converter_mass_cost_coeff: 18.8 + transformer_mass_cost_coeff: 18.8 + hvac_mass_cost_coeff: 124.0 + cover_mass_cost_coeff: 5.7 + elec_connec_machine_rating_cost_coeff: 41.85 + platforms_mass_cost_coeff: 17.1 + tower_mass_cost_coeff: 2.9 + controls_machine_rating_cost_coeff: 21.15 + crane_cost: 12e3 diff --git a/examples/13_design_of_experiments/analysis_options.yaml b/examples/13_design_of_experiments/analysis_options.yaml new file mode 100644 index 000000000..dfaad0118 --- /dev/null +++ b/examples/13_design_of_experiments/analysis_options.yaml @@ -0,0 +1,179 @@ +general: + folder_output: outputs + fname_output: doe_output +design_variables: + blade: + aero_shape: + twist: + 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 + lower_bound: [0.3490658503988659, 0.08726646259971647, 0.08726646259971647, 0., -0.08726646259971647, -0.08726646259971647, -0.08726646259971647, -0.08726646259971647] # Lower bounds for the twist in [rad] at the n_opt locations + upper_bound: [0.4363323129985824, 0.3490658503988659, 0.3490658503988659, 0.2617993877991494, 0.2617993877991494, 0.17453292519943295, 0.17453292519943295, 0.08726646259971647] # Upper bounds for the twist in [rad] at the n_opt locations + chord: + flag: True # Flag to optimize the chord + n_opt: 8 # Number of control points along blade span + min_gain: 0.2 # Nondimensional lower bound at the n_opt locations + max_gain: 2.0 # Nondimensional upper bound at the n_opt locations + af_positions: + flag: False # Flag to optimize the airfoil positions + af_start: 4 # Index of the first airfoil from blade root that can have the location optimized. First airfoil is number 0. Last airfoil is always locked at blade tip. + structure: + spar_cap_ss: + flag: False # Flag to optimize the spar cap thickness on the suction side + n_opt: 8 # Number of control points along blade span + min_gain: 0.2 # Nondimensional lower bound at the n_opt locations + max_gain: 2.0 # Nondimensional upper bound at the n_opt locations + spar_cap_ps: + flag: False # Flag to optimize the spar cap thickness on the pressure side + equal_to_suction: True # Flag to impose the spar cap thickness on pressure and suction sides equal + n_opt: 8 # Number of control points along blade span + min_gain: 0.2 # Nondimensional lower bound at the n_opt locations + max_gain: 2.0 # Nondimensional upper bound at the n_opt locations + te_ss: + flag: False # Flag to optimize the trailing edge reinforcement thickness on the suction side + n_opt: 8 # Number of control points along blade span + min_gain: 0.2 # Nondimensional lower bound at the n_opt locations + max_gain: 2.0 # Nondimensional upper bound at the n_opt locations + te_ps: + flag: False # Flag to optimize the trailing edge reinforcement thickness on the pressure side + n_opt: 8 # Number of control points along blade span + min_gain: 0.2 # Nondimensional lower bound at the n_opt locations + max_gain: 2.0 # Nondimensional upper bound at the n_opt locations + dac: + te_flap_ext: + flag: False + min_ext: 0.0 + max_ext: 0.3 + te_flap_end: + flag: False + min_end: 0.3 + max_end: 1.0 + control: + tsr: + flag: False # Flag to optimize the rotor tip speed ratio + min_gain: 0.9 # Nondimensional lower bound + max_gain: 1.1 # Nondimensional upper bound + servo: + pitch_control: + flag: False + omega_min: 0.1 + omega_max: 0.7 + zeta_min: 0.4 + zeta_max: 1.5 + torque_control: + flag: False + omega_min: 0.1 + omega_max: 0.7 + zeta_min: 0.4 + zeta_max: 1.5 + flap_control: + flag: False + omega_min: 2.7 + omega_max: 3.5 + zeta_min: 0.7 + zeta_max: 1.3 + ipc_control: + flag: False + Ki_min: 0.0 + Ki_max: 1e-8 + tower: + outer_diameter: + flag: False + lower_bound: 3.87 + upper_bound: 10.0 + layer_thickness: + flag: False + lower_bound: 4.e-3 + upper_bound: 2.e-1 + +merit_figure: 'blade_tip_deflection' # Merit figure of the optimization problem. The options are 'AEP' - 'LCOE' - 'Cp' - 'blade_mass' - 'blade_tip_deflection' + +constraints: + blade: + strains_spar_cap_ss: + flag: False # Flag to impose constraints on maximum strains (absolute value) in the spar cap on the blade suction side + max: 3500.e-6 # Value of maximum strains [-] + strains_spar_cap_ps: + flag: False # Flag to impose constraints on maximum strains (absolute value) in the spar cap on the blade pressure side + max: 3500.e-6 # Value of maximum strains [-] + tip_deflection: + flag: False # Constraint that maximum tip deflection cannot exceed 70% of tower clearance. Only for upwind rotors + margin: 1.4175 + rail_transport: + flag: False + 8_axle: False + 4_axle: False + stall: + flag: False # Constraint on minimum stall margin + margin: 0.05233 # Value of minimum stall margin in [rad] + chord: + flag: False # Constraint on maximum chord + max: 4.75 # Value of maximum chord in [m] + frequency: + flap_above_3P: False + edge_above_3P: False + flap_below_3P: False + edge_below_3P: False + moment_coefficient: + flag: False + max: 0.15 #0.16333 + min: 0.13 + match_cl_cd: + flag_cl: False + flag_cd: False + filename: /path2file.txt + match_L_D: + flag_L: False + flag_D: False + filename: /path2file.txt + tower: + height_constraint: + flag: False + lower_bound: 1.e-2 + upper_bound: 1.e-2 + stress: + flag: False + global_buckling: + flag: False + shell_buckling: + flag: False + constr_d_to_t: + flag: False + constr_taper: + flag: False + slope: + flag: False + frequency_1: + flag: False + lower_bound: 0.13 + upper_bound: 0.40 + control: + flap_control: + flag: False + min: 0.0 + max: 0.1 + rotor_overspeed: + flag: False + min: 0.0 + max: 0.2 + +driver: + optimization: + flag: False # Flag to enable optimization + tol: 1.e-2 # Optimality tolerance + max_major_iter: 10 # Maximum number of major design iterations (SNOPT) + max_minor_iter: 100 # Maximum number of minor design iterations (SNOPT) + max_iter: 100 # Maximum number of iterations (SLSQP) + solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN' + step_size: 1.e-3 # Step size for finite differencing + form: central # Finite differencing mode, either forward or central + design_of_experiments: + flag: True # Flag to enable design of experiments + run_parallel: False # Flag to run using parallel processing + generator: Uniform # Type of input generator. (Uniform) + num_samples: 5 # number of samples for (Uniform only) + +recorder: + flag: True # Flag to activate OpenMDAO recorder + file_name: log_opt.sql # Name of OpenMDAO recorder diff --git a/examples/13_design_of_experiments/doe_driver.py b/examples/13_design_of_experiments/doe_driver.py new file mode 100644 index 000000000..c3497f919 --- /dev/null +++ b/examples/13_design_of_experiments/doe_driver.py @@ -0,0 +1,25 @@ +import os + +from wisdem import run_wisdem +from wisdem.commonse.mpi_tools import MPI +from wisdem.postprocessing.compare_designs import run + +mydir = os.path.dirname(os.path.realpath(__file__)) # get path to this file +fname_wt_input = mydir + os.sep + "IEA-15-240-RWT.yaml" +fname_modeling_options = mydir + os.sep + "modeling_options.yaml" +fname_analysis_options = mydir + os.sep + "analysis_options.yaml" + +wt_opt, modeling_options, analysis_options = run_wisdem(fname_wt_input, fname_modeling_options, fname_analysis_options) + +if MPI: + rank = MPI.COMM_WORLD.Get_rank() +else: + rank = 0 + +if rank == 0: + print( + "RUN COMPLETED. RESULTS ARE AVAILABLE HERE: " + + os.path.join(mydir, analysis_options["general"]["folder_output"]) + ) + +run([wt_opt], ["optimized"], modeling_options, analysis_options) diff --git a/examples/13_design_of_experiments/modeling_options.yaml b/examples/13_design_of_experiments/modeling_options.yaml new file mode 100644 index 000000000..985910630 --- /dev/null +++ b/examples/13_design_of_experiments/modeling_options.yaml @@ -0,0 +1,91 @@ +General: + verbosity: False # When set to True, the code prints to screen many infos + +WISDEM: + RotorSE: + flag: True + n_pitch_perf_surfaces: 15 + n_tsr_perf_surfaces: 15 + spar_cap_ss: Spar_Cap_SS + spar_cap_ps: Spar_Cap_PS + TowerSE: + flag: True + DriveSE: + flag: True + FloatingSE: + flag: True + BOS: + flag: True + +Level3: # Options for WEIS fidelity level 3 = nonlinear time domain + flag: False + simulation: + TMax: 1. + DT: 0.01 + CompElast: 1 + CompInflow: 1 + CompAero: 2 + CompServo: 1 + CompHydro: 1 + CompSub: 1 + CompMooring: 0 + CompIce: 0 + linearization: + Linearize: False + ElastoDyn: + FlapDOF1: True + FlapDOF2: True + EdgeDOF: True + TeetDOF: False + DrTrDOF: False + GenDOF: True + YawDOF: False + TwFADOF1 : True + TwFADOF2 : True + TwSSDOF1 : True + TwSSDOF2 : True + PtfmSgDOF: False + PtfmSwDOF: False + PtfmHvDOF: False + PtfmRDOF : False + PtfmPDOF : False + PtfmYDOF : False + HydroDyn: + WaveTMax: 1 #3630 + WvLowCOff: 0.15708 + WvHiCOff: 3.2 + WaveSeed1: 123456789 + WaveSeed2: 1011121314 + AddBLin3: [0.0, 0.0, 4389794.6, 0.0, 0.0, 0.0] + ROSCO: + flag: True + +openfast: + file_management: + FAST_namingOut: IEA15 # Name of the OpenFAST output files + FAST_runDirectory: temp/IEA15 # Path to folder with the OpenFAST output files + dlc_settings: + run_power_curve: True + run_IEC: True + run_blade_fatigue: False + IEC: # Currently supported: 1.1, 1.3, 1.4, 1.5, 5.1, 6.1, 6.3 + - DLC: 1.1 + U: [5., 13., 21.] + Seeds: [1] + # - DLC: 1.3 + # U: [3., 5., 7., 9., 11., 13., 15., 17., 19., 21., 23., 25.] + # Seeds: [11, 12, 13, 14, 15, 16] + # - DLC: 1.4 + # - DLC: 1.5 + # U: [3., 5., 7., 9., 11., 13., 15., 17., 19., 21., 23., 25.] + # Seeds: [11, 12, 13, 14, 15, 16] + # - DLC: 5.1 + # Seeds: [11, 12, 13, 14, 15, 16] + # - DLC: 6.1 + # Seeds: [11, 12, 13, 14, 15, 16] + # - DLC: 6.3 + # Seeds: [11, 12, 13, 14, 15, 16] + Power_Curve: + turbulent_power_curve: True # False: Steady Wind, True: Turbulent Inflow + U: [] # If running turbulent power curve, can set U:[], and analysis will default to using the IEC - DLC 1.1 results. This can be used to prevent running redundant turbulent operational cases + Seeds: [] # Used only when turbulent_power_curve=True diff --git a/setup.py b/setup.py index a45f44f45..d27a1da38 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ # Top-level setup setup( name="WISDEM", - version="3.1.0", + version="3.2.0", description="Wind-Plant Integrated System Design & Engineering Model", long_description="""WISDEM is a Python package for conducting multidisciplinary analysis and optimization of wind turbines and plants. It is built on top of NASA's OpenMDAO library.""", @@ -55,18 +55,18 @@ author="NREL WISDEM Team", author_email="systems.engineering@nrel.gov", install_requires=[ - "openmdao>=3.2", + "jsonschema", + "marmot-agents", "numpy", - "scipy", + "openmdao>=3.4", + "openpyxl", "pandas", - "simpy", - "marmot-agents", "pyside2", - "openpyxl", - "xlrd", - "jsonschema", - "pyyaml", "pytest", + "pyyaml", + "scipy", + "simpy", + "sortedcontainers", ], python_requires=">=3.7", package_data={"": ["*.yaml", "*.xlsx"], "wisdem": ["*.txt"]}, diff --git a/wisdem/ccblade/ccblade_component.py b/wisdem/ccblade/ccblade_component.py index bb2689fee..e8061c815 100644 --- a/wisdem/ccblade/ccblade_component.py +++ b/wisdem/ccblade/ccblade_component.py @@ -1,10 +1,10 @@ -from wisdem.ccblade.ccblade import CCAirfoil, CCBlade -from openmdao.api import ExplicitComponent import numpy as np import wisdem.ccblade._bem as _bem +from openmdao.api import ExplicitComponent +from scipy.interpolate import PchipInterpolator +from wisdem.ccblade.ccblade import CCBlade, CCAirfoil from wisdem.commonse.csystem import DirectionVector - cosd = lambda x: np.cos(np.deg2rad(x)) sind = lambda x: np.sin(np.deg2rad(x)) @@ -172,7 +172,7 @@ def initialize(self): self.options.declare("modeling_options") def setup(self): - rotorse_options = self.options["modeling_options"]["RotorSE"] + rotorse_options = self.options["modeling_options"]["WISDEM"]["RotorSE"] self.n_span = n_span = rotorse_options["n_span"] self.n_aoa = n_aoa = rotorse_options["n_aoa"] # Number of angle of attacks self.n_Re = n_Re = rotorse_options["n_Re"] # Number of Reynolds @@ -385,11 +385,11 @@ def initialize(self): def setup(self): modeling_options = self.options["modeling_options"] opt_options = self.options["opt_options"] - self.n_span = n_span = modeling_options["RotorSE"]["n_span"] + self.n_span = n_span = modeling_options["WISDEM"]["RotorSE"]["n_span"] # self.n_af = n_af = af_init_options['n_af'] # Number of airfoils - self.n_aoa = n_aoa = modeling_options["RotorSE"]["n_aoa"] # Number of angle of attacks - self.n_Re = n_Re = modeling_options["RotorSE"]["n_Re"] # Number of Reynolds, so far hard set at 1 - self.n_tab = n_tab = modeling_options["RotorSE"][ + self.n_aoa = n_aoa = modeling_options["WISDEM"]["RotorSE"]["n_aoa"] # Number of angle of attacks + self.n_Re = n_Re = modeling_options["WISDEM"]["RotorSE"]["n_Re"] # Number of Reynolds, so far hard set at 1 + self.n_tab = n_tab = modeling_options["WISDEM"]["RotorSE"][ "n_tab" ] # Number of tabulated data. For distributed aerodynamic control this could be > 1 n_opt_chord = opt_options["design_variables"]["blade"]["aero_shape"]["chord"]["n_opt"] @@ -532,6 +532,10 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): ) if self.options["opt_options"]["design_variables"]["blade"]["aero_shape"]["twist"]["inverse"]: + if self.options["opt_options"]["design_variables"]["blade"]["aero_shape"]["twist"]["flag"]: + raise Exception( + "Twist cannot be simultaneously optimized and set to be defined inverting the BEM equations. Please check your analysis options yaml." + ) # Find cl and cd for max efficiency cl = np.zeros(self.n_span) cd = np.zeros(self.n_span) @@ -799,7 +803,7 @@ def initialize(self): self.options.declare("modeling_options") def setup(self): - rotorse_options = self.options["modeling_options"]["RotorSE"] + rotorse_options = self.options["modeling_options"]["WISDEM"]["RotorSE"] n_blades = self.options["modeling_options"]["assembly"]["number_of_blades"] self.n_span = n_span = rotorse_options["n_span"] @@ -1029,7 +1033,7 @@ def initialize(self): self.options.declare("modeling_options") def setup(self): - rotorse_init_options = self.options["modeling_options"]["RotorSE"] + rotorse_init_options = self.options["modeling_options"]["WISDEM"]["RotorSE"] self.n_span = n_span = rotorse_init_options["n_span"] self.n_aoa = n_aoa = rotorse_init_options["n_aoa"] # Number of angle of attacks self.n_Re = n_Re = rotorse_init_options["n_Re"] # Number of Reynolds diff --git a/wisdem/commonse/environment.py b/wisdem/commonse/environment.py index 11c0d5d3a..8c1272ab8 100644 --- a/wisdem/commonse/environment.py +++ b/wisdem/commonse/environment.py @@ -9,15 +9,10 @@ from __future__ import print_function -import sys -import math - import numpy as np import openmdao.api as om from scipy.optimize import brentq - -from .constants import gravity -from .utilities import hstack, vstack +from wisdem.commonse.constants import gravity # TODO CHECK @@ -379,10 +374,10 @@ def compute(self, inputs, outputs): h = inputs["Hsig_wave"] # circular frequency - omega = 2.0 * math.pi / inputs["Tsig_wave"] + omega = 2.0 * np.pi / inputs["Tsig_wave"] # compute wave number from dispersion relationship - k = brentq(lambda k: omega ** 2 - gravity * k * math.tanh(d * k), 0, 1e3 * omega ** 2 / gravity) + k = brentq(lambda k: omega ** 2 - gravity * k * np.tanh(d * k), 0, 1e3 * omega ** 2 / gravity) self.k = k outputs["phase_speed"] = omega / k @@ -427,7 +422,7 @@ def compute_partials(self, inputs, J): z = inputs["z"] d = inputs["z_surface"] - z_floor h = inputs["Hsig_wave"] - omega = 2.0 * math.pi / inputs["Tsig_wave"] + omega = 2.0 * np.pi / inputs["Tsig_wave"] k = self.k z_rel = z - inputs["z_surface"] @@ -497,19 +492,23 @@ class TowerSoil(om.ExplicitComponent): Spring stiffness (x, theta_x, y, theta_y, z, theta_z) """ + def initialize(self): + self.options.declare("npts", default=1) + def setup(self): - super(TowerSoil, self).setup() + npts = self.options["npts"] # variable self.add_input("d0", 1.0, units="m") - self.add_input("depth", 1.0, units="m") + self.add_input("depth", 0.0, units="m") # inputeter self.add_input("G", 140e6, units="Pa") self.add_input("nu", 0.4) self.add_input("k_usr", -1 * np.ones(6), units="N/m") - self.add_output("k", np.zeros(6), units="N/m") + self.add_output("z_k", np.zeros(npts), units="N/m") + self.add_output("k", np.zeros((npts, 6)), units="N/m") self.declare_partials("k", ["d0", "depth"]) @@ -517,7 +516,7 @@ def compute(self, inputs, outputs): G = inputs["G"] nu = inputs["nu"] - h = inputs["depth"] + h = np.linspace(inputs["depth"], 0.0, self.options["npts"]) r0 = 0.5 * inputs["d0"] # vertical @@ -533,17 +532,18 @@ def compute(self, inputs, outputs): k_thetax = 8.0 * G * r0 ** 3 * eta / (3.0 * (1.0 - nu)) # torsional - k_phi = 16.0 * G * r0 ** 3 / 3.0 + k_phi = 16.0 * G * r0 ** 3 * np.ones(h.size) / 3.0 - outputs["k"] = np.array([k_x, k_thetax, k_x, k_thetax, k_z, k_phi]).flatten() + outputs["k"] = np.c_[k_x, k_thetax, k_x, k_thetax, k_z, k_phi] + outputs["z_k"] = -h ind = np.nonzero(inputs["k_usr"] >= 0.0)[0] - outputs["k"][ind] = inputs["k_usr"][ind] + outputs["k"][:, ind] = inputs["k_usr"][np.newaxis, ind] def compute_partials(self, inputs, J): G = inputs["G"] nu = inputs["nu"] - h = inputs["depth"] + h = np.linspace(inputs["depth"], 0.0, self.options["npts"]) r0 = 0.5 * inputs["d0"] # vertical @@ -574,13 +574,13 @@ def compute_partials(self, inputs, J): dkphi_dr0 = 16.0 * G * 3 * r0 ** 2 / 3.0 dkphi_dh = 0.0 - dk_dr0 = np.array([dkx_dr0, dkthetax_dr0, dkx_dr0, dkthetax_dr0, dkz_dr0, dkphi_dr0]) + dk_dr0 = np.c_[dkx_dr0, dkthetax_dr0, dkx_dr0, dkthetax_dr0, dkz_dr0, dkphi_dr0] # dk_dr0[inputs['rigid']] = 0.0 - dk_dh = np.array([dkx_dh, dkthetax_dh, dkx_dh, dkthetax_dh, dkz_dh, dkphi_dh]) + dk_dh = np.c_[dkx_dh, dkthetax_dh, dkx_dh, dkthetax_dh, dkz_dh, dkphi_dh] # dk_dh[inputs['rigid']] = 0.0 J["k", "d0"] = 0.5 * dk_dr0 J["k", "depth"] = dk_dh ind = np.nonzero(inputs["k_usr"] >= 0.0)[0] - J["k", "d0"][ind] = 0.0 - J["k", "depth"][ind] = 0.0 + J["k", "d0"][:, ind] = 0.0 + J["k", "depth"][:, ind] = 0.0 diff --git a/wisdem/commonse/fileIO.py b/wisdem/commonse/fileIO.py index 5159940da..060f26640 100644 --- a/wisdem/commonse/fileIO.py +++ b/wisdem/commonse/fileIO.py @@ -78,6 +78,7 @@ def save_data(fname, prob, npz_file=True, mat_file=True, xls_file=True): data["description"].append(var_dict[k][1]["desc"]) df = pd.DataFrame(data) df.to_excel(froot + ".xlsx", index=False) + df.to_csv(froot + ".csv", index=False) def load_data(fname, prob): diff --git a/wisdem/commonse/turbine_constraints.py b/wisdem/commonse/turbine_constraints.py index f3988d76c..f86c09073 100644 --- a/wisdem/commonse/turbine_constraints.py +++ b/wisdem/commonse/turbine_constraints.py @@ -116,8 +116,8 @@ def initialize(self): def setup(self): modeling_options = self.options["modeling_options"] - n_span = modeling_options["RotorSE"]["n_span"] - n_height_tow = modeling_options["TowerSE"]["n_height_tower"] + n_span = modeling_options["WISDEM"]["RotorSE"]["n_span"] + n_height_tow = modeling_options["WISDEM"]["TowerSE"]["n_height_tower"] self.add_discrete_input("rotor_orientation", val="upwind") self.add_input("tip_deflection", val=0.0, units="m") @@ -193,5 +193,7 @@ def initialize(self): def setup(self): modeling_options = self.options["modeling_options"] - self.add_subsystem("modes", TowerModes(gamma=modeling_options["TowerSE"]["gamma_freq"]), promotes=["*"]) + self.add_subsystem( + "modes", TowerModes(gamma=modeling_options["WISDEM"]["TowerSE"]["gamma_freq"]), promotes=["*"] + ) self.add_subsystem("tipd", TipDeflectionConstraint(modeling_options=modeling_options), promotes=["*"]) diff --git a/wisdem/commonse/utilities.py b/wisdem/commonse/utilities.py index 0c74631c6..bcad892a4 100644 --- a/wisdem/commonse/utilities.py +++ b/wisdem/commonse/utilities.py @@ -6,28 +6,34 @@ """ from __future__ import print_function + import numpy as np from scipy.linalg import solve_banded +# from scipy.optimize import curve_fit + def mode_fit(x, c2, c3, c4, c5, c6): return c2 * x ** 2.0 + c3 * x ** 3.0 + c4 * x ** 4.0 + c5 * x ** 5.0 + c6 * x ** 6.0 -def get_modal_coefficients(x, y, deg=6): +def get_modal_coefficients(x, y, deg=[2, 3, 4, 5, 6]): # Normalize x input xn = (x - x.min()) / (x.max() - x.min()) - # Get coefficients to 6th order polynomial + # Get coefficients to 2-6th order polynomial p6 = np.polynomial.polynomial.polyfit(xn, y, deg) - # coef, pcov = curve_fit(mode_fit, xn, y) # Normalize for Elastodyn if y.ndim > 1: p6 = p6[2:, :] + # p6 = np.zeros((5, y.shape[1])) + # for k in range(y.shape[1]): + # p6[:, k], _ = curve_fit(mode_fit, xn, y[:, k]) p6 /= p6.sum(axis=0)[np.newaxis, :] else: p6 = p6[2:] + # p6, _ = curve_fit(mode_fit, xn, y) p6 /= p6.sum() return p6 @@ -39,7 +45,7 @@ def get_xy_mode_shapes(r, freqs, xdsp, ydsp, zdsp, xmpf, ympf, zmpf): # Get mode shapes in batch mpfs = np.abs(np.c_[xmpf, ympf, zmpf]) - polys = get_modal_coefficients(r, np.vstack((xdsp, ydsp)).T, 6) + polys = get_modal_coefficients(r, np.vstack((xdsp, ydsp)).T) xpolys = polys[:, :nfreq].T ypolys = polys[:, nfreq:].T @@ -506,6 +512,25 @@ def smooth_abs(x, dx=0.01): return y, dydx +def find_nearest(array, value): + return (np.abs(array - value)).argmin() + + +def closest_node(nodemat, inode): + if not nodemat.shape[1] in [2, 3]: + if nodemat.shape[0] in [2, 3]: + xyz = nodemat.T + else: + raise ValueError("Expected an m X 2/3 input node array") + else: + xyz = nodemat + + if not len(inode) in [2, 3]: + raise ValueError("Expected a size 2 or 3 node point") + + return np.sqrt(np.sum((xyz - inode[np.newaxis, :]) ** 2, axis=1)).argmin() + + def nodal2sectional(x): """Averages nodal data to be length-1 vector of sectional data diff --git a/wisdem/commonse/vertical_cylinder.py b/wisdem/commonse/vertical_cylinder.py index 6d786670b..22f2bbd52 100644 --- a/wisdem/commonse/vertical_cylinder.py +++ b/wisdem/commonse/vertical_cylinder.py @@ -1,13 +1,11 @@ import numpy as np import openmdao.api as om - -from wisdem.commonse import gravity, eps, NFREQ import wisdem.commonse.frustum as frustum -import wisdem.commonse.manufacturing as manufacture -from wisdem.commonse.utilization_constraints import hoopStressEurocode, hoopStress import wisdem.commonse.utilities as util import wisdem.pyframe3dd.pyframe3dd as pyframe3dd - +import wisdem.commonse.manufacturing as manufacture +from wisdem.commonse import NFREQ, eps, gravity +from wisdem.commonse.utilization_constraints import hoopStress, hoopStressEurocode RIGID = 1e30 NREFINE = 3 @@ -61,6 +59,7 @@ class CylinderDiscretization(om.ExplicitComponent): def initialize(self): self.options.declare("nPoints") self.options.declare("nRefine", default=NREFINE) + self.options.declare("nPin", default=0) def setup(self): nPoints = self.options["nPoints"] @@ -90,12 +89,14 @@ def compute(self, inputs, outputs): nRefine = int(np.round(self.options["nRefine"])) z_param = float(inputs["foundation_height"]) + np.r_[0.0, np.cumsum(inputs["section_height"].flatten())] + # Have to regine each element one at a time so that we preserve input nodes z_full = np.array([]) for k in range(z_param.size - 1): zref = np.linspace(z_param[k], z_param[k + 1], nRefine + 1) z_full = np.append(z_full, zref) z_full = np.unique(z_full) + outputs["z_full"] = z_full outputs["d_full"] = np.interp(z_full, z_param, inputs["diameter"]) z_section = 0.5 * (z_full[:-1] + z_full[1:]) @@ -355,8 +356,8 @@ class CylinderFrame3DD(om.ExplicitComponent): 6-degree polynomial coefficients of mode shapes in the x-direction y_mode_shapes : numpy array[NFREQ2, 5] 6-degree polynomial coefficients of mode shapes in the x-direction - top_deflection : float, [m] - Deflection of cylinder top in yaw-aligned +x direction + cylinder_deflection : numpy array[npts], [m] + Deflection of cylinder nodes in yaw-aligned +x direction Fz_out : numpy array[npts-1], [N] Axial foce in vertical z-direction in cylinder structure. Vx_out : numpy array[npts-1], [N] @@ -463,7 +464,7 @@ def setup(self): self.add_output("y_mode_shapes", val=np.zeros((NFREQ2, 5))) self.add_output("x_mode_freqs", val=np.zeros(NFREQ2)) self.add_output("y_mode_freqs", val=np.zeros(NFREQ2)) - self.add_output("top_deflection", val=0.0, units="m") + self.add_output("cylinder_deflection", val=np.zeros(npts), units="m") self.add_output("Fz_out", val=np.zeros(npts - 1), units="N") self.add_output("Vx_out", val=np.zeros(npts - 1), units="N") self.add_output("Vy_out", val=np.zeros(npts - 1), units="N") @@ -623,7 +624,9 @@ def compute(self, inputs, outputs): outputs["y_mode_shapes"] = mshapes_y[:NFREQ2, :] # deflections due to loading (from cylinder top and wind/wave loads) - outputs["top_deflection"] = displacements.dx[iCase, n - 1] # in yaw-aligned direction + outputs["cylinder_deflection"] = np.sqrt( + displacements.dx[iCase, :] ** 2 + displacements.dy[iCase, :] ** 2 + ) # in yaw-aligned direction # shear and bending, one per element (convert from local to global c.s.) Fz = forces.Nx[iCase, 1::2] @@ -635,8 +638,14 @@ def compute(self, inputs, outputs): Mxx = -forces.Mzz[iCase, 1::2] # Record total forces and moments - outputs["base_F"] = -1.0 * np.r_[-forces.Vz[iCase, 0], forces.Vy[iCase, 0], forces.Nx[iCase, 0]] - outputs["base_M"] = -1.0 * np.r_[-forces.Mzz[iCase, 0], forces.Myy[iCase, 0], forces.Txx[iCase, 0]] + base_idx = 2 * int(inputs["kidx"].max()) + outputs["base_F"] = ( + -1.0 * np.r_[-forces.Vz[iCase, base_idx], forces.Vy[iCase, base_idx], forces.Nx[iCase, base_idx]] + ) + outputs["base_M"] = ( + -1.0 * np.r_[-forces.Mzz[iCase, base_idx], forces.Myy[iCase, base_idx], forces.Txx[iCase, base_idx]] + ) + outputs["Fz_out"] = Fz outputs["Vx_out"] = Vx outputs["Vy_out"] = Vy diff --git a/wisdem/commonse/wind_wave_drag.py b/wisdem/commonse/wind_wave_drag.py index 37fce42a0..bbaf7fb12 100644 --- a/wisdem/commonse/wind_wave_drag.py +++ b/wisdem/commonse/wind_wave_drag.py @@ -1,34 +1,11 @@ -#!/usr/bin/env python -# encoding: utf-8 -""" -WindWaveDrag.py - -Created by RRD on 2015-07-13. -Copyright (c) NREL. All rights reserved. -""" - -# ------------------------------------------------------------------------------- -# Name: WindWaveDrag.py -# Purpose: It contains OpenMDAO's Components to calculate wind or wave drag -# on cylinders. -# -# Author: ANing/RRD -# -# Created: 13/07/2015 - It is based on load function calculations developed for tower and jacket. -# Reestablished elements needed by jacketSE that were removed. Changed names to vartrees. -# Copyright: (c) rdamiani 2015 -# Licence: -# ------------------------------------------------------------------------------- -from __future__ import print_function import math -import numpy as np +import numpy as np import openmdao.api as om -from wisdem.commonse.utilities import sind, cosd # , linspace_with_deriv, interp_with_deriv, hstack, vstack -from wisdem.commonse.csystem import DirectionVector - from wisdem.commonse.akima import Akima - +from wisdem.commonse.csystem import DirectionVector +from wisdem.commonse.utilities import cosd, sind # , linspace_with_deriv, interp_with_deriv, hstack, vstack +from wisdem.commonse.environment import LogWind, PowerWind, LinearWaves # ----------------- # Helper Functions @@ -588,6 +565,100 @@ def compute_partials(self, inputs, J): # ___________________________________________# +class CylinderEnvironment(om.Group): + def initialize(self): + self.options.declare("wind", default="power") + self.options.declare("nPoints") + self.options.declare("water_flag", default=True) + + def setup(self): + nPoints = self.options["nPoints"] + wind = self.options["wind"] + water_flag = self.options["water_flag"] + + self.set_input_defaults("z0", 0.0) + self.set_input_defaults("cd_usr", -1.0) + self.set_input_defaults("yaw", 0.0, units="deg") + + self.set_input_defaults("beta_wind", 0.0, units="deg") + self.set_input_defaults("rho_air", 1.225, units="kg/m**3") + self.set_input_defaults("mu_air", 1.81206e-5, units="kg/m/s") + self.set_input_defaults("shearExp", 0.2) + + if water_flag: + self.set_input_defaults("beta_wave", 0.0, units="deg") + self.set_input_defaults("rho_water", 1025.0, units="kg/m**3") + self.set_input_defaults("mu_water", 1.08e-3, units="kg/m/s") + + # Wind profile and loads + promwind = ["Uref", "zref", "z", "z0"] + if wind is None or wind.lower() in ["power", "powerwind", ""]: + self.add_subsystem("wind", PowerWind(nPoints=nPoints), promotes=promwind + ["shearExp"]) + + elif wind.lower() == "logwind": + self.add_subsystem("wind", LogWind(nPoints=nPoints), promotes=promwind) + + else: + raise ValueError("Unknown wind type, " + wind) + + self.add_subsystem( + "windLoads", + CylinderWindDrag(nPoints=nPoints), + promotes=["cd_usr", "beta_wind", "rho_air", "mu_air", "z", "d"], + ) + + # Wave profile and loads + if water_flag: + self.add_subsystem( + "wave", + LinearWaves(nPoints=nPoints), + promotes=[ + "z", + "Uc", + "Hsig_wave", + "Tsig_wave", + "rho_water", + ("z_floor", "water_depth"), + ("z_surface", "z0"), + ], + ) + + self.add_subsystem( + "waveLoads", + CylinderWaveDrag(nPoints=nPoints), + promotes=["cm", "cd_usr", "beta_wave", "rho_water", "mu_water", "z", "d"], + ) + + # Combine all loads + self.add_subsystem( + "distLoads", AeroHydroLoads(nPoints=nPoints), promotes=["Px", "Py", "Pz", "qdyn", "yaw", "z"] + ) + + # Connections + self.connect("wind.U", "windLoads.U") + if water_flag: + self.connect("wave.U", "waveLoads.U") + self.connect("wave.A", "waveLoads.A") + self.connect("wave.p", "waveLoads.p") + + self.connect("windLoads.windLoads_Px", "distLoads.windLoads_Px") + self.connect("windLoads.windLoads_Py", "distLoads.windLoads_Py") + self.connect("windLoads.windLoads_Pz", "distLoads.windLoads_Pz") + self.connect("windLoads.windLoads_qdyn", "distLoads.windLoads_qdyn") + self.connect("windLoads.windLoads_beta", "distLoads.windLoads_beta") + self.connect("windLoads.windLoads_z", "distLoads.windLoads_z") + self.connect("windLoads.windLoads_d", "distLoads.windLoads_d") + + if water_flag: + self.connect("waveLoads.waveLoads_Px", "distLoads.waveLoads_Px") + self.connect("waveLoads.waveLoads_Py", "distLoads.waveLoads_Py") + self.connect("waveLoads.waveLoads_Pz", "distLoads.waveLoads_Pz") + self.connect("waveLoads.waveLoads_pt", "distLoads.waveLoads_qdyn") + self.connect("waveLoads.waveLoads_beta", "distLoads.waveLoads_beta") + self.connect("waveLoads.waveLoads_z", "distLoads.waveLoads_z") + self.connect("waveLoads.waveLoads_d", "distLoads.waveLoads_d") + + def main(): # initialize problem U = np.array([20.0, 25.0, 30.0]) diff --git a/wisdem/drivetrainse/drive_components.py b/wisdem/drivetrainse/drive_components.py index 5a21e4515..c76f91bda 100644 --- a/wisdem/drivetrainse/drive_components.py +++ b/wisdem/drivetrainse/drive_components.py @@ -237,12 +237,16 @@ class GeneratorSimple(om.ExplicitComponent): rotor diameter machine_rating : float, [kW] machine rating of generator + L_generator : float, [m] + Generator stack width rated_torque : float, [N*m] rotor torque at rated power shaft_rpm : numpy array[n_pc], [rpm] Input shaft rotations-per-minute (rpm) generator_mass_user : float, [kg] User input override of generator mass + generator_radius_user : float, [m] + User input override of generator radius Returns ------- @@ -270,7 +274,9 @@ def setup(self): self.add_input("machine_rating", val=0.0, units="kW") self.add_input("rated_torque", 0.0, units="N*m") self.add_input("lss_rpm", np.zeros(n_pc), units="rpm") + self.add_input("L_generator", 0.0, units="m") self.add_input("generator_mass_user", 0.0, units="kg") + self.add_input("generator_radius_user", 0.0, units="m") self.add_input("generator_efficiency_user", val=np.zeros((n_pc, 2))) self.add_output("R_generator", val=0.0, units="m") @@ -289,8 +295,10 @@ def compute(self, inputs, outputs): rating = float(inputs["machine_rating"]) D_rotor = float(inputs["rotor_diameter"]) Q_rotor = float(inputs["rated_torque"]) + R_generator = float(inputs["generator_radius_user"]) mass = float(inputs["generator_mass_user"]) eff_user = inputs["generator_efficiency_user"] + length = float(inputs["L_generator"]) if mass == 0.0: if self.options["direct_drive"]: @@ -304,8 +312,8 @@ def compute(self, inputs, outputs): outputs["generator_rotor_mass"] = outputs["generator_stator_mass"] = 0.5 * mass # calculate mass properties - length = 1.8 * 0.015 * D_rotor - R_generator = 0.5 * 0.015 * D_rotor + if R_generator == 0.0: + R_generator = 0.5 * 0.015 * D_rotor outputs["R_generator"] = R_generator I = np.zeros(3) @@ -615,7 +623,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Platforms as a fraction of bedplate mass and bundling it to call it 'platforms' L_platform = 2 * D_top if direct else L_cover W_platform = 2 * D_top if direct else W_cover - t_platform = 0.05 + t_platform = 0.04 m_platform = L_platform * W_platform * t_platform * rho_castiron I_platform = ( m_platform diff --git a/wisdem/drivetrainse/drive_structure.py b/wisdem/drivetrainse/drive_structure.py index 7f184d318..775a7feb9 100644 --- a/wisdem/drivetrainse/drive_structure.py +++ b/wisdem/drivetrainse/drive_structure.py @@ -3,23 +3,17 @@ import numpy as np import openmdao.api as om - import wisdem.pyframe3dd.pyframe3dd as frame3dd -from wisdem.commonse.utilization_constraints import vonMisesStressUtilization -from wisdem.commonse.cross_sections import Tube, IBeam -from wisdem.commonse.utilities import nodal2sectional from wisdem.commonse import gravity from wisdem.commonse.csystem import DirectionVector - +from wisdem.commonse.utilities import find_nearest, nodal2sectional +from wisdem.commonse.cross_sections import Tube, IBeam +from wisdem.commonse.utilization_constraints import vonMisesStressUtilization RIGID = 1 FREE = 0 -def find_nearest(array, value): - return (np.abs(array - value)).argmin() - - def tube_prop(s, Di, ti): L = s.max() - s.min() diff --git a/wisdem/drivetrainse/drivetrain.py b/wisdem/drivetrainse/drivetrain.py index 21ced7d98..3cc24c54c 100644 --- a/wisdem/drivetrainse/drivetrain.py +++ b/wisdem/drivetrainse/drivetrain.py @@ -1,6 +1,5 @@ import numpy as np import openmdao.api as om - import wisdem.drivetrainse.layout as lay import wisdem.drivetrainse.drive_structure as ds import wisdem.drivetrainse.drive_components as dc @@ -122,11 +121,11 @@ def initialize(self): self.options.declare("n_dlcs") def setup(self): - opt = self.options["modeling_options"]["DriveSE"] + opt = self.options["modeling_options"]["WISDEM"]["DriveSE"] n_dlcs = self.options["n_dlcs"] direct = opt["direct"] dogen = self.options["modeling_options"]["flags"]["generator"] - n_pc = self.options["modeling_options"]["RotorSE"]["n_pc"] + n_pc = self.options["modeling_options"]["WISDEM"]["RotorSE"]["n_pc"] self.set_input_defaults("machine_rating", units="kW") self.set_input_defaults("planet_numbers", [3, 3, 0]) @@ -165,7 +164,7 @@ def setup(self): # Generator self.add_subsystem("rpm", dc.RPM_Input(n_pc=n_pc), promotes=["*"]) if dogen: - gentype = self.options["modeling_options"]["GeneratorSE"]["type"] + gentype = self.options["modeling_options"]["WISDEM"]["GeneratorSE"]["type"] self.add_subsystem( "generator", Generator(design=gentype, n_pc=n_pc), diff --git a/wisdem/drivetrainse/generator.py b/wisdem/drivetrainse/generator.py index 0a3676197..2c28f97b3 100644 --- a/wisdem/drivetrainse/generator.py +++ b/wisdem/drivetrainse/generator.py @@ -1,12 +1,12 @@ """generator.py -Created by Latha Sethuraman, Katherine Dykes. +Created by Latha Sethuraman, Katherine Dykes. Copyright (c) NREL. All rights reserved. Electromagnetic design based on conventional magnetic circuit laws Structural design based on McDonald's thesis """ -import openmdao.api as om import numpy as np +import openmdao.api as om import wisdem.drivetrainse.generator_models as gm # ---------------------------------------------------------------------------------------------- @@ -180,8 +180,6 @@ def compute(self, inputs, outputs): # ---------------------------------------------------------------------------------------------- - - class MofI(om.ExplicitComponent): """ Compute moments of inertia. @@ -300,10 +298,93 @@ def compute(self, inputs, outputs): C_Fe = inputs["C_Fe"] C_PM = inputs["C_PM"] - # Material cost as a function of material mass and specific cost of material - K_gen = Copper * C_Cu + Iron * C_Fe + C_PM * mass_PM #%M_pm*K_pm; # - Cost_str = C_Fes * Structural_mass - outputs["generator_cost"] = K_gen + Cost_str + # Industrial electricity rate $/kWh https://www.eia.gov/electricity/monthly/epm_table_grapher.php?t=epmt_5_6_a + k_e = 0.064 + + # Material cost ($/kg) and electricity usage cost (kWh/kg)*($/kWh) for the materials with waste fraction + K_copper = Copper * (1.26 * C_Cu + 96.2 * k_e) + K_iron = Iron * (1.21 * C_Fe + 26.9 * k_e) + K_pm = mass_PM * (1.0 * C_PM + 79.0 * k_e) + K_steel = Structural_mass * (1.21 * C_Fes + 15.9 * k_e) + # Account for capital cost and labor share from BLS MFP by NAICS + outputs["generator_cost"] = (K_copper + K_pm) / 0.619 + (K_iron + K_steel) / 0.684 + + +# ---------------------------------------------------------------------------------------------- + + +class PowerElectronicsEff(om.ExplicitComponent): + """ + Compute representative efficiency of power electronics + + Parameters + ---------- + machine_rating : float, [W] + Machine rating + shaft_rpm : numpy array[n_pc], [rpm] + rated speed of input shaft (lss for direct, hss for geared) + eandm_efficiency : numpy array[n_pc] + Generator electromagnetic efficiency values (<1) + + Returns + ------- + converter_efficiency : numpy array[n_pc] + Converter efficiency values (<1) + transformer_efficiency : numpy array[n_pc] + Transformer efficiency values (<1) + generator_efficiency : numpy array[n_pc] + Full generato and power electronics efficiency values (<1) + + """ + + def initialize(self): + self.options.declare("n_pc", default=20) + + def setup(self): + n_pc = self.options["n_pc"] + + self.add_input("machine_rating", val=0.0, units="W") + self.add_input("shaft_rpm", val=np.zeros(n_pc), units="rpm") + self.add_input("eandm_efficiency", val=np.zeros(n_pc)) + + self.add_output("converter_efficiency", val=np.zeros(n_pc)) + self.add_output("transformer_efficiency", val=np.zeros(n_pc)) + self.add_output("generator_efficiency", val=np.zeros(n_pc)) + + def compute(self, inputs, outputs): + # Unpack inputs + rating = inputs["machine_rating"] + rpmData = inputs["shaft_rpm"] + rpmRatio = rpmData / rpmData[-1] + + # This converter efficiency is from the APEEM Group in 2020 + # See Sreekant Narumanchi, Kevin Bennion, Bidzina Kekelia, Ramchandra Kotecha + # Converter constants + v_dc, v_dc0, c0, c1, c2, c3 = 6600, 6200, -2.1e-10, 1.2e-5, 1.46e-3, -2e-4 + p_ac0, p_dc0 = 0.99 * rating, rating + p_s0 = 1e-3 * p_dc0 + + # calculated parameters + a = p_dc0 * (1.0 + c1 * (v_dc - v_dc0)) + b = p_s0 * (1.0 + c2 * (v_dc - v_dc0)) + c = c0 * (1.0 + c3 * (v_dc - v_dc0)) + + # Converter efficiency + p_dc = rpmRatio * p_dc0 + p_ac = (p_ac0 / (a - b) - c * (a - b)) * (p_dc - b) + c * ((p_dc - b) ** 2) + conv_eff = p_ac / p_dc + + # Transformer loss model is P_loss = P_0 + a^2 * P_k + # a is output power/rated + p0, pk, rT = 16.0, 111.0, 5.0 / 3.0 + a = rpmRatio * (1 / rT) + # This gives loss in kW, so need to convert to efficiency + trans_eff = 1.0 - (p0 + a * a * pk) / (1e-3 * rating) + + # Store outputs + outputs["converter_efficiency"] = conv_eff + outputs["transformer_efficiency"] = trans_eff + outputs["generator_efficiency"] = conv_eff * trans_eff * inputs["eandm_efficiency"] # ---------------------------------------------------------------------------------------------- @@ -450,3 +531,4 @@ def setup(self): self.add_subsystem("mofi", MofI(), promotes=["*"]) self.add_subsystem("gen_cost", Cost(), promotes=["*"]) self.add_subsystem("constr", Constraints(), promotes=["*"]) + self.add_subsystem("eff", PowerElectronicsEff(), promotes=["*"]) diff --git a/wisdem/drivetrainse/generator_models.py b/wisdem/drivetrainse/generator_models.py index e709b3802..e2c9f50f8 100644 --- a/wisdem/drivetrainse/generator_models.py +++ b/wisdem/drivetrainse/generator_models.py @@ -368,10 +368,8 @@ class GeneratorBase(om.ExplicitComponent): Stack length ratio Losses : numpy array[n_pc], [W] Total loss - rated_efficiency : float - Generator efficiency at rated conditions generator_efficiency : numpy array[n_pc] - Generator efficiency values (<1) + Generator electromagnetic efficiency values (<1) u_ar : float, [m] Rotor radial deflection u_as : float, [m] @@ -540,7 +538,7 @@ def setup(self): # Objective functions self.add_output("K_rad", val=0.0) self.add_output("Losses", val=np.zeros(n_pc), units="W") - self.add_output("generator_efficiency", val=np.zeros(n_pc)) + self.add_output("eandm_efficiency", val=np.zeros(n_pc)) # Structural performance self.add_output("u_ar", val=0.0, units="m") @@ -1182,7 +1180,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): outputs["Mass_yoke_stator"] = Mass_yoke_stator outputs["R_out"] = R_out outputs["Losses"] = Losses - outputs["generator_efficiency"] = np.maximum(eps, gen_eff) + outputs["eandm_efficiency"] = np.maximum(eps, gen_eff) outputs["u_ar"] = u_ar outputs["u_allow_r"] = u_allow_r outputs["y_ar"] = y_ar @@ -1688,7 +1686,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): outputs["Losses"] = Losses outputs["K_rad"] = K_rad - outputs["generator_efficiency"] = np.maximum(eps, gen_eff) + outputs["eandm_efficiency"] = np.maximum(eps, gen_eff) outputs["S"] = S outputs["Slot_aspect_ratio"] = Slot_aspect_ratio outputs["Copper"] = Copper @@ -2143,7 +2141,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): outputs["J_s"] = J_s outputs["Losses"] = Losses outputs["K_rad"] = K_rad - outputs["generator_efficiency"] = np.maximum(eps, gen_eff) + outputs["eandm_efficiency"] = np.maximum(eps, gen_eff) outputs["S"] = S outputs["Slot_aspect_ratio"] = Slot_aspect_ratio outputs["Copper"] = Copper @@ -2572,7 +2570,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): outputs["K_rad"] = K_rad outputs["Losses"] = Losses - outputs["generator_efficiency"] = np.maximum(eps, gen_eff) + outputs["eandm_efficiency"] = np.maximum(eps, gen_eff) outputs["Copper"] = Copper outputs["Iron"] = Iron outputs["Structural_mass"] = Structural_mass @@ -3017,7 +3015,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): outputs["K_rad_UL"] = K_rad_UL outputs["K_rad_LL"] = K_rad_LL outputs["Losses"] = Losses - outputs["generator_efficiency"] = np.maximum(eps, gen_eff) + outputs["eandm_efficiency"] = np.maximum(eps, gen_eff) outputs["Copper"] = Copper outputs["Iron"] = Iron outputs["Structural_mass"] = Structural_mass @@ -3616,7 +3614,7 @@ def airGapFn(B, fact): outputs["n_brushes"] = n_brushes outputs["J_f"] = J_f outputs["K_rad"] = K_rad - outputs["generator_efficiency"] = np.maximum(eps, gen_eff) + outputs["eandm_efficiency"] = np.maximum(eps, gen_eff) outputs["S"] = S outputs["Slot_aspect_ratio"] = Slot_aspect_ratio diff --git a/wisdem/floatingse/column.py b/wisdem/floatingse/column.py deleted file mode 100644 index 9a200a94d..000000000 --- a/wisdem/floatingse/column.py +++ /dev/null @@ -1,1680 +0,0 @@ -import copy - -import numpy as np -import openmdao.api as om -import wisdem.commonse.frustum as frustum -import wisdem.commonse.manufacturing as manufacture -from wisdem.commonse import eps, gravity -from wisdem.commonse.utilities import assembleI, unassembleI, nodal2sectional, sectional2nodal, sectionalInterp -from wisdem.commonse.environment import PowerWind, LinearWaves -from wisdem.commonse.wind_wave_drag import AeroHydroLoads, CylinderWaveDrag, CylinderWindDrag -from wisdem.commonse.vertical_cylinder import CylinderMass, CylinderDiscretization, get_nfull -from wisdem.commonse.utilization_constraints import GeometricConstraints, shellBuckling_withStiffeners - - -def get_inner_radius(Ro, t): - # Radius varies at nodes, t varies by section - return Ro - sectional2nodal(t) - - -def I_tube(r_i, r_o, h, m): - if type(r_i) == type(np.array([])): - n = r_i.size - r_i = r_i.flatten() - r_o = r_o.flatten() - h = h.flatten() - m = m.flatten() - else: - n = 1 - Ixx = Iyy = (m / 12.0) * (3.0 * (r_i ** 2.0 + r_o ** 2.0) + h ** 2.0) - Izz = 0.5 * m * (r_i ** 2.0 + r_o ** 2.0) - return np.c_[Ixx, Iyy, Izz, np.zeros((n, 3))] - - -class DiscretizationYAML(om.ExplicitComponent): - """ - Convert the YAML inputs into more native and easy to use variables. - - Parameters - ---------- - s : numpy array[n_height_tow] - 1D array of the non-dimensional grid defined along the column axis (0-column base, - 1-column top) - layer_materials : list of strings - 1D array of the names of the materials of each layer modeled in the column - structure. - layer_thickness : numpy array[n_layers_tow, n_height_tow-1], [m] - 2D array of the thickness of the layers of the column structure. The first - dimension represents each layer, the second dimension represents each piecewise- - constant entry of the column sections. - height : float, [m] - Scalar of the column height computed along the z axis. - outer_diameter_in : numpy array[n_height_tow], [m] - cylinder diameter at corresponding locations - material_names : list of strings - 1D array of names of materials. - E_mat : numpy array[n_mat, 3], [Pa] - 2D array of the Youngs moduli of the materials. Each row represents a material, - the three columns represent E11, E22 and E33. - G_mat : numpy array[n_mat, 3], [Pa] - 2D array of the shear moduli of the materials. Each row represents a material, - the three columns represent G12, G13 and G23. - sigma_y_mat : numpy array[n_mat], [Pa] - 2D array of the yield strength of the materials. Each row represents a material, - the three columns represent Xt12, Xt13 and Xt23. - rho_mat : numpy array[n_mat], [kg/m**3] - 1D array of the density of the materials. For composites, this is the density of - the laminate. - unit_cost_mat : numpy array[n_mat], [USD/kg] - 1D array of the unit costs of the materials. - - Returns - ------- - section_height : numpy array[n_height-1], [m] - parameterized section heights along cylinder - outer_diameter : numpy array[n_height], [m] - cylinder diameter at corresponding locations - wall_thickness : numpy array[n_height-1], [m] - shell thickness at corresponding locations - E : numpy array[n_height-1], [Pa] - Isotropic Youngs modulus of the materials along the column sections. - G : numpy array[n_height-1], [Pa] - Isotropic shear modulus of the materials along the column sections. - sigma_y : numpy array[n_height-1], [Pa] - Isotropic yield strength of the materials along the column sections. - rho : numpy array[n_height-1], [kg/m**3] - Density of the materials along the column sections. - unit_cost : numpy array[n_height-1], [USD/kg] - Unit costs of the materials along the column sections. - - """ - - def initialize(self): - self.options.declare("n_height") - self.options.declare("n_layers") - self.options.declare("n_mat") - - def setup(self): - n_height = self.options["n_height"] - n_layers = self.options["n_layers"] - n_mat = self.options["n_mat"] - - # TODO: Use reference axis and curvature, s, instead of assuming everything is vertical on z - self.add_input("s", val=np.zeros(n_height)) - self.add_discrete_input("layer_materials", val=n_layers * [""]) - self.add_input("layer_thickness", val=np.zeros((n_layers, n_height - 1)), units="m") - self.add_input("height", val=0.0, units="m") - self.add_input("outer_diameter_in", np.zeros(n_height), units="m") - self.add_discrete_input("material_names", val=n_mat * [""]) - self.add_input("E_mat", val=np.zeros([n_mat, 3]), units="Pa") - self.add_input("G_mat", val=np.zeros([n_mat, 3]), units="Pa") - self.add_input("sigma_y_mat", val=np.zeros(n_mat), units="Pa") - self.add_input("rho_mat", val=np.zeros(n_mat), units="kg/m**3") - self.add_input("unit_cost_mat", val=np.zeros(n_mat), units="USD/kg") - - self.add_output("section_height", val=np.zeros(n_height - 1), units="m") - self.add_output("outer_diameter", val=np.zeros(n_height), units="m") - self.add_output("wall_thickness", val=np.zeros(n_height - 1), units="m") - self.add_output("E", val=np.zeros(n_height - 1), units="Pa") - self.add_output("G", val=np.zeros(n_height - 1), units="Pa") - self.add_output("sigma_y", val=np.zeros(n_height - 1), units="Pa") - self.add_output("rho", val=np.zeros(n_height - 1), units="kg/m**3") - self.add_output("unit_cost", val=np.zeros(n_height - 1), units="USD/kg") - - # self.declare_partials('*', '*', method='fd') - - def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - # Unpack dimensions - n_height = self.options["n_height"] - - # Unpack values - h_col = inputs["height"] - - outputs["section_height"] = np.diff(h_col * inputs["s"]) - outputs["wall_thickness"] = np.sum(inputs["layer_thickness"], axis=0) - outputs["outer_diameter"] = inputs["outer_diameter_in"] - twall = inputs["layer_thickness"] - layer_mat = copy.copy(discrete_inputs["layer_materials"]) - - # Check to make sure we have good values - if np.any(outputs["section_height"] <= 0.0): - raise ValueError("Section height values must be greater than zero, " + str(outputs["section_height"])) - if np.any(outputs["wall_thickness"] <= 0.0): - raise ValueError("Wall thickness values must be greater than zero, " + str(outputs["wall_thickness"])) - if np.any(outputs["outer_diameter"] <= 0.0): - raise ValueError("Diameter values must be greater than zero, " + str(outputs["outer_diameter"])) - - # DETERMINE MATERIAL PROPERTIES IN EACH SECTION - # Convert to isotropic material - E = np.mean(inputs["E_mat"], axis=1) - G = np.mean(inputs["G_mat"], axis=1) - sigy = inputs["sigma_y_mat"] - rho = inputs["rho_mat"] - cost = inputs["unit_cost_mat"] - mat_names = discrete_inputs["material_names"] - - # Initialize sectional data - E_param = np.zeros(twall.shape) - G_param = np.zeros(twall.shape) - sigy_param = np.zeros(twall.shape) - rho_param = np.zeros(n_height - 1) - cost_param = np.zeros(n_height - 1) - - # Loop over materials and associate it with its thickness - for k in range(len(layer_mat)): - # Get the material name for this layer - iname = layer_mat[k] - - # Get the index into the material list - imat = mat_names.index(iname) - - # For density, take mass weighted layer - rho_param += rho[imat] * twall[k, :] - - # For cost, take mass weighted layer - cost_param += rho[imat] * twall[k, :] * cost[imat] - - # Store the value associated with this thickness - E_param[k, :] = E[imat] - G_param[k, :] = G[imat] - sigy_param[k, :] = sigy[imat] - - # Mass weighted cost (should really weight by radius too) - cost_param /= rho_param - - # Thickness weighted density (should really weight by radius too) - rho_param /= twall.sum(axis=0) - - # Mixtures of material properties: https://en.wikipedia.org/wiki/Rule_of_mixtures - - # Volume fraction - vol_frac = twall / twall.sum(axis=0)[np.newaxis, :] - - # Average of upper and lower bounds - E_param = 0.5 * np.sum(vol_frac * E_param, axis=0) + 0.5 / np.sum(vol_frac / E_param, axis=0) - G_param = 0.5 * np.sum(vol_frac * G_param, axis=0) + 0.5 / np.sum(vol_frac / G_param, axis=0) - sigy_param = 0.5 * np.sum(vol_frac * sigy_param, axis=0) + 0.5 / np.sum(vol_frac / sigy_param, axis=0) - - # Store values - outputs["E"] = E_param - outputs["G"] = G_param - outputs["rho"] = rho_param - outputs["sigma_y"] = sigy_param - outputs["unit_cost"] = cost_param - - -class ColumnGeometry(om.ExplicitComponent): - """ - Compute geometric properties for vertical columns in substructure - for floating offshore wind turbines. - - Parameters - ---------- - water_depth : float, [m] - water depth - Hsig_wave : float, [m] - significant wave height - freeboard : float, [m] - Length of column above water line - max_draft : float, [m] - Maxmimum length of column below water line - z_full_in : numpy array[n_full], [m] - z-coordinates of section nodes (length = nsection+1) - z_param_in : numpy array[n_height], [m] - z-coordinates of section nodes (length = nsection+1) - stiffener_web_height : numpy array[n_sect], [m] - height of stiffener web (base of T) within each section bottom to top - (length = nsection) - stiffener_web_thickness : numpy array[n_sect], [m] - thickness of stiffener web (base of T) within each section bottom to top - (length = nsection) - stiffener_flange_width : numpy array[n_sect], [m] - height of stiffener flange (top of T) within each section bottom to top - (length = nsection) - stiffener_flange_thickness : numpy array[n_sect], [m] - thickness of stiffener flange (top of T) within each section bottom to top - (length = nsection) - stiffener_spacing : numpy array[n_sect], [m] - Axial distance from one ring stiffener to another within each section bottom to - top (length = nsection) - E : numpy array[n_height-1], [Pa] - Isotropic Youngs modulus of the materials along the column sections. - G : numpy array[n_height-1], [Pa] - Isotropic shear modulus of the materials along the column sections. - sigma_y : numpy array[n_height-1], [Pa] - Isotropic yield strength of the materials along the column sections. - rho : numpy array[n_height-1], [kg/m**3] - Density of the materials along the column sections. - unit_cost : numpy array[n_height-1], [USD/kg] - Unit costs of the materials along the column sections. - - Returns - ------- - z_full : numpy array[n_full], [m] - z-coordinates of section nodes (length = nsection+1) - z_param : numpy array[n_height], [m] - z-coordinates of section nodes (length = nsection+1) - draft : float, [m] - Column draft (length of body under water) - h_web : numpy array[n_full-1], [m] - height of stiffener web (base of T) within each section bottom to top - t_web : numpy array[n_full-1], [m] - thickness of stiffener web (base of T) within each section bottom to top - w_flange : numpy array[n_full-1], [m] - height of stiffener flange (top of T) within each section bottom to top - t_flange : numpy array[n_full-1], [m] - thickness of stiffener flange (top of T) within each section bottom to top - L_stiffener : numpy array[n_full-1], [m] - Axial distance from one ring stiffener to another within each section bottom to - top - E_full : numpy array[n_full-1], [Pa] - Isotropic Youngs modulus of the materials along the column sections. - G_full : numpy array[n_full-1], [Pa] - Isotropic shear modulus of the materials along the column sections. - sigma_y_full : numpy array[n_full-1], [Pa] - Isotropic yield strength of the materials along the column sections. - rho_full : numpy array[n_full-1], [kg/m**3] - Density of the materials along the column sections. - unit_cost_full : numpy array[n_full-1], [USD/kg] - Unit costs of the materials along the column sections. - nu_full : numpy array[n_full-1] - Poisson's ratio assuming isotropic material - draft_margin : float - Ratio of draft to water depth - wave_height_freeboard_ratio : float - Ratio of maximum wave height (avg of top 1%) to freeboard - - """ - - def initialize(self): - self.options.declare("n_height") - - def setup(self): - n_height = self.options["n_height"] - n_sect = n_height - 1 - n_full = get_nfull(n_height) - - self.add_input("water_depth", 0.0, units="m") - self.add_input("Hsig_wave", 0.0, units="m") - self.add_input("freeboard", 0.0, units="m") - self.add_input("max_draft", 0.0, units="m") - self.add_input("z_full_in", np.zeros(n_full), units="m") - self.add_input("z_param_in", np.zeros(n_height), units="m") - self.add_input("stiffener_web_height", np.zeros(n_sect), units="m") - self.add_input("stiffener_web_thickness", np.zeros(n_sect), units="m") - self.add_input("stiffener_flange_width", np.zeros(n_sect), units="m") - self.add_input("stiffener_flange_thickness", np.zeros(n_sect), units="m") - self.add_input("stiffener_spacing", np.zeros(n_sect), units="m") - self.add_input("E", val=np.zeros(n_sect), units="Pa") - self.add_input("G", val=np.zeros(n_sect), units="Pa") - self.add_input("sigma_y", val=np.zeros(n_sect), units="Pa") - self.add_input("rho", val=np.zeros(n_sect), units="kg/m**3") - self.add_input("unit_cost", val=np.zeros(n_sect), units="USD/kg") - - self.add_output("z_full", np.zeros(n_full), units="m") - self.add_output("z_param", np.zeros(n_height), units="m") - self.add_output("draft", 0.0, units="m") - self.add_output("h_web", np.zeros(n_full - 1), units="m") - self.add_output("t_web", np.zeros(n_full - 1), units="m") - self.add_output("w_flange", np.zeros(n_full - 1), units="m") - self.add_output("t_flange", np.zeros(n_full - 1), units="m") - self.add_output("L_stiffener", np.zeros(n_full - 1), units="m") - self.add_output("draft_margin", 0.0) - self.add_output("wave_height_freeboard_ratio", 0.0) - self.add_output("E_full", val=np.zeros(n_full - 1), units="Pa") - self.add_output("G_full", val=np.zeros(n_full - 1), units="Pa") - self.add_output("nu_full", val=np.zeros(n_full - 1)) - self.add_output("sigma_y_full", val=np.zeros(n_full - 1), units="Pa") - self.add_output("rho_full", val=np.zeros(n_full - 1), units="kg/m**3") - self.add_output("unit_cost_full", val=np.zeros(n_full - 1), units="USD/kg") - - self.declare_partials("*", "*", method="fd", form="central", step=1e-6) - - def compute(self, inputs, outputs): - # Unpack variables - freeboard = inputs["freeboard"] - - # With waterline at z=0, set the z-position of section nodes - # Note sections and nodes start at bottom of column and move up - draft = inputs["z_param_in"][-1] - freeboard - z_full = inputs["z_full_in"] - draft - z_param = inputs["z_param_in"] - draft - outputs["draft"] = draft - outputs["z_full"] = z_full - outputs["z_param"] = z_param - - # Create constraint output that draft is less than water depth - outputs["draft_margin"] = draft / inputs["max_draft"] - - # Make sure freeboard is more than 20% of Hsig_wave (DNV-OS-J101) - outputs["wave_height_freeboard_ratio"] = inputs["Hsig_wave"] / (np.abs(freeboard) + eps) - - # Material properties - z_section, _ = nodal2sectional(z_full) - outputs["rho_full"] = sectionalInterp(z_section, z_param, inputs["rho"]) - outputs["E_full"] = sectionalInterp(z_section, z_param, inputs["E"]) - outputs["G_full"] = sectionalInterp(z_section, z_param, inputs["G"]) - outputs["nu_full"] = 0.5 * outputs["E_full"] / outputs["G_full"] - 1.0 - outputs["sigma_y_full"] = sectionalInterp(z_section, z_param, inputs["sigma_y"]) - outputs["unit_cost_full"] = sectionalInterp(z_section, z_param, inputs["unit_cost"]) - - # Sectional stiffener properties - outputs["t_web"] = sectionalInterp(z_section, z_param, inputs["stiffener_web_thickness"]) - outputs["t_flange"] = sectionalInterp(z_section, z_param, inputs["stiffener_flange_thickness"]) - outputs["h_web"] = sectionalInterp(z_section, z_param, inputs["stiffener_web_height"]) - outputs["w_flange"] = sectionalInterp(z_section, z_param, inputs["stiffener_flange_width"]) - outputs["L_stiffener"] = sectionalInterp(z_section, z_param, inputs["stiffener_spacing"]) - - -class BulkheadProperties(om.ExplicitComponent): - """ - Compute bulkhead properties - - Parameters - ---------- - z_full : numpy array[n_full], [m] - z-coordinates of section nodes - d_full : numpy array[n_full], [m] - cylinder diameter at corresponding locations - t_full : numpy array[n_full-1], [m] - shell thickness at corresponding locations - rho_full : numpy array[n_full-1], [kg/m**3] - material density - bulkhead_locations : numpy array[n_bulkhead] - Vector of non-dimensional values (from 0.0 at the bottom bottom to 1.0 at the top) indicating the center locations of the bulkheads - bulkhead_thickness : numpy array[n_bulkhead], [m] - Vector of thicknesses of the bulkheads at the locations specified (length = n_bulkhead) - shell_mass : numpy array[n_full-1], [kg] - mass of column shell - unit_cost_full : numpy array[n_full-1], [USD/kg] - Raw material cost rate: steel $1.1/kg, aluminum $3.5/kg - labor_cost_rate : float, [USD/min] - Labor cost rate - painting_cost_rate : float, [USD/m/m] - Painting / surface finishing cost rate - - Returns - ------- - bulkhead_mass : numpy array[n_full-1], [kg] - mass of column bulkheads - bulkhead_z_cg : float, [m] - z-coordinate of center of gravity for all bulkheads - bulkhead_cost : float, [USD] - cost of column bulkheads - bulkhead_I_keel : numpy array[6], [kg*m**2] - Moments of inertia of bulkheads relative to keel point - - """ - - def initialize(self): - self.options.declare("n_height") - self.options.declare("n_bulkhead") - - def setup(self): - n_bulk = self.options["n_bulkhead"] - n_height = self.options["n_height"] - n_sect = n_height - 1 - n_full = get_nfull(n_height) - - self.bulk_full = np.zeros(n_full, dtype=np.int_) - - self.add_input("z_full", np.zeros(n_full), units="m") - self.add_input("d_full", np.zeros(n_full), units="m") - self.add_input("t_full", np.zeros(n_full - 1), units="m") - self.add_input("rho_full", np.zeros(n_full - 1), units="kg/m**3") - self.add_input("bulkhead_thickness", np.zeros(n_bulk), units="m") - self.add_input("bulkhead_locations", np.zeros(n_bulk)) - self.add_input("shell_mass", np.zeros(n_full - 1), units="kg") - self.add_input("unit_cost_full", np.zeros(n_full - 1), units="USD/kg") - self.add_input("labor_cost_rate", 0.0, units="USD/min") - self.add_input("painting_cost_rate", 0.0, units="USD/m/m") - - self.add_output("bulkhead_mass", np.zeros(n_full - 1), units="kg") - self.add_output("bulkhead_z_cg", 0.0, units="m") - self.add_output("bulkhead_cost", 0.0, units="USD") - self.add_output("bulkhead_I_keel", np.zeros(6), units="kg*m**2") - - self.declare_partials("*", "*", method="fd", form="central", step=1e-6) - - def compute(self, inputs, outputs): - # Unpack variables - z_full = inputs["z_full"] # at section nodes - R_od = 0.5 * inputs["d_full"] # at section nodes - twall = inputs["t_full"] # at section nodes - R_id = get_inner_radius(R_od, twall) - s_bulk = inputs["bulkhead_locations"] - t_bulk = inputs["bulkhead_thickness"] - nbulk = s_bulk.size - - # Get z and R_id values of bulkhead locations - s_full = (z_full - z_full[0]) / (z_full[-1] - z_full[0]) - z_bulk = np.interp(s_bulk, s_full, z_full) - R_id_bulk = np.interp(s_bulk, s_full, R_id) - rho = sectionalInterp(s_bulk, s_full, inputs["rho_full"]) - - # Compute bulkhead mass - m_bulk = rho * np.pi * R_id_bulk ** 2 * t_bulk - z_cg = 0.0 if m_bulk.sum() == 0.0 else np.dot(z_bulk, m_bulk) / m_bulk.sum() - - # Find sectional index for each bulkhead and assign appropriate mass - m_bulk_sec = np.zeros(z_full.size - 1) - ibulk = [] - for k in range(nbulk): - idx = np.where(s_bulk[k] >= s_full[:-1])[0][-1] - ibulk.append(idx) - m_bulk_sec[idx] += m_bulk[k] - - # Compute moments of inertia at keel - # Assume bulkheads are just simple thin discs with radius R_od-t_wall and mass already computed - Izz = 0.5 * m_bulk * R_id_bulk ** 2 - Ixx = Iyy = 0.5 * Izz - dz = z_bulk - z_full[0] - I_keel = np.zeros((3, 3)) - for k in range(nbulk): - R = np.array([0.0, 0.0, dz[k]]) - Icg = assembleI([Ixx[k], Iyy[k], Izz[k], 0.0, 0.0, 0.0]) - I_keel += Icg + m_bulk[k] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) - - # Compute costs based on "Optimum Design of Steel Structures" by Farkas and Jarmai - # All dimensions for correlations based on mm, not meters. - k_m = sectionalInterp(s_bulk, s_full, inputs["unit_cost_full"]) - k_f = inputs["labor_cost_rate"] # 1.0 # USD / min labor - k_p = inputs["painting_cost_rate"] # USD / m^2 painting - m_shell = inputs["shell_mass"].sum() - - # Cost Step 1) Cutting flat plates using plasma cutter - cutLengths = 2.0 * np.pi * R_id_bulk - # Cost Step 2) Fillet welds with GMAW-C (gas metal arc welding with CO2) of bulkheads to shell - theta_w = 3.0 # Difficulty factor - - # Labor-based expenses - K_f = k_f * ( - manufacture.steel_cutting_plasma_time(cutLengths, t_bulk) - + manufacture.steel_filett_welding_time(theta_w, nbulk, m_bulk + m_shell, 2 * np.pi * R_id_bulk, t_bulk) - ) - - # Cost Step 3) Painting (two sided) - theta_p = 1.0 - K_p = k_p * theta_p * 2 * (np.pi * R_id_bulk ** 2.0).sum() - - # Material cost, without outfitting - K_m = np.sum(k_m * m_bulk) - - # Total cost - c_bulk = K_m + K_f + K_p - - # Store results - outputs["bulkhead_I_keel"] = unassembleI(I_keel) - outputs["bulkhead_mass"] = m_bulk_sec - outputs["bulkhead_z_cg"] = z_cg - outputs["bulkhead_cost"] = c_bulk - - -class BuoyancyTankProperties(om.ExplicitComponent): - """ - Compute buoyancy tank properties - - Parameters - ---------- - d_full : numpy array[n_full], [m] - cylinder diameter at corresponding locations - z_full : numpy array[n_full], [m] - z-coordinates of section nodes - rho_full : numpy array[n_full-1], [kg/m**3] - material density - shell_mass : numpy array[n_full-1], [kg] - mass of column shell - unit_cost_full : numpy array[n_full-1], [USD/kg] - Raw material cost: steel $1.1/kg, aluminum $3.5/kg - labor_cost_rate : float, [USD/min] - Labor cost - painting_cost_rate : float, [USD/m/m] - Painting / surface finishing cost rate - buoyancy_tank_diameter : float, [m] - Radius of heave plate at bottom of column - buoyancy_tank_height : float, [m] - Radius of heave plate at bottom of column - buoyancy_tank_location : float - Radius of heave plate at bottom of column - - Returns - ------- - buoyancy_tank_mass : float, [kg] - mass of buoyancy tank - buoyancy_tank_cost : float, [USD] - cost of buoyancy tank - buoyancy_tank_cg : float, [m] - z-coordinate of center of mass for buoyancy tank - buoyancy_tank_displacement : float, [m**3] - volume of water displaced by buoyancy tank - buoyancy_tank_I_keel : numpy array[6], [kg*m**2] - Moments of inertia of heave plate relative to keel point - - """ - - def initialize(self): - self.options.declare("n_height") - - def setup(self): - n_height = self.options["n_height"] - n_full = get_nfull(n_height) - - self.add_input("d_full", np.zeros(n_full), units="m") - self.add_input("z_full", np.zeros(n_full), units="m") - self.add_input("rho_full", np.zeros(n_full - 1), units="kg/m**3") - self.add_input("shell_mass", np.zeros(n_full - 1), units="kg") - self.add_input("unit_cost_full", np.zeros(n_full - 1), units="USD/kg") - self.add_input("labor_cost_rate", 0.0, units="USD/min") - self.add_input("painting_cost_rate", 0.0, units="USD/m/m") - self.add_input("buoyancy_tank_diameter", 0.0, units="m") - self.add_input("buoyancy_tank_height", 0.0, units="m") - self.add_input("buoyancy_tank_location", 0.0) - - self.add_output("buoyancy_tank_mass", 0.0, units="kg") - self.add_output("buoyancy_tank_cost", 0.0, units="USD") - self.add_output("buoyancy_tank_cg", 0.0, units="m") - self.add_output("buoyancy_tank_displacement", 0.0, units="m**3") - self.add_output("buoyancy_tank_I_keel", np.zeros(6), units="kg*m**2") - - self.declare_partials("*", "*", method="fd", form="central", step=1e-6) - - def compute(self, inputs, outputs): - # Unpack variables - z_full = inputs["z_full"] - - R_od = 0.5 * inputs["d_full"] - R_plate = 0.5 * float(inputs["buoyancy_tank_diameter"]) - h_box = float(inputs["buoyancy_tank_height"]) - - location = float(inputs["buoyancy_tank_location"]) - - # Current hard-coded, coarse specification of shell thickness - t_plate = R_plate / 50.0 - - # Z-locations of buoyancy tank - z_lower = location * (z_full[-1] - z_full[0]) + z_full[0] - z_cg = z_lower + 0.5 * h_box - z_upper = z_lower + h_box - - # Mass and volume properties that subtract out central column contributions for no double-counting - rho = sectionalInterp(z_lower, z_full, inputs["rho_full"]) - R_col = np.interp([z_lower, z_upper], z_full, R_od) - if not np.any(R_plate > R_col): - R_plate = 0.0 - A_plate = np.maximum(0.0, np.pi * (R_plate ** 2.0 - R_col ** 2.0)) - m_plate = rho * t_plate * A_plate - A_box = A_plate.sum() + 2.0 * np.pi * R_plate * h_box - m_box = rho * t_plate * A_box - - # Compute displcement for buoyancy calculations, but check for what is submerged - V_box = np.pi * R_plate ** 2.0 * h_box - V_box -= frustum.frustumVol(R_col[0], R_col[1], h_box) - if z_lower >= 0.0: - V_box = 0.0 - elif z_upper >= 0.0: - V_box *= -z_lower / h_box - V_box = np.maximum(0.0, V_box) - - # Now do moments of inertia - # First find MoI at cg of all components - R_plate += eps - Ixx_box = frustum.frustumShellIxx(R_plate, R_plate, t_plate, h_box) - Izz_box = frustum.frustumShellIzz(R_plate, R_plate, t_plate, h_box) - I_plateL = 0.25 * m_plate[0] * (R_plate ** 2.0 - R_col[0] ** 2.0) * np.array([1.0, 1.0, 2.0, 0.0, 0.0, 0.0]) - I_plateU = 0.25 * m_plate[1] * (R_plate ** 2.0 - R_col[1] ** 2.0) * np.array([1.0, 1.0, 2.0, 0.0, 0.0, 0.0]) - - # Move to keel for consistency - I_keel = np.zeros((3, 3)) - if R_plate > eps: - # Add in lower plate - r = np.array([0.0, 0.0, z_lower]) - Icg = assembleI(I_plateL) - I_keel += Icg + m_plate[0] * (np.dot(r, r) * np.eye(3) - np.outer(r, r)) - # Add in upper plate - r = np.array([0.0, 0.0, z_upper]) - Icg = assembleI(I_plateU) - I_keel += Icg + m_plate[1] * (np.dot(r, r) * np.eye(3) - np.outer(r, r)) - # Add in box cylinder - r = np.array([0.0, 0.0, z_cg]) - Icg = assembleI([Ixx_box, Ixx_box, Izz_box, 0.0, 0.0, 0.0]) - I_keel += Icg + m_plate[1] * (np.dot(r, r) * np.eye(3) - np.outer(r, r)) - - # Compute costs based on "Optimum Design of Steel Structures" by Farkas and Jarmai - # All dimensions for correlations based on mm, not meters. - k_m = sectionalInterp(z_lower, z_full, inputs["unit_cost_full"]) - k_f = inputs["labor_cost_rate"] # 1.0 # USD / min labor - k_p = inputs["painting_cost_rate"] # USD / m^2 painting - m_shell = inputs["shell_mass"].sum() - - # Cost Step 1) Cutting flat plates using plasma cutter into box plate sizes - cutLengths = 2.0 * np.pi * (3.0 * R_plate + R_col.sum()) # x3 for two plates + side wall - # Cost Step 2) Welding box plates together GMAW-C (gas metal arc welding with CO2) fillet welds - theta_w = 3.0 # Difficulty factor - # Cost Step 3) Welding box to shell GMAW-C (gas metal arc welding with CO2) fillet welds - - # Labor-based expenses - K_f = k_f * ( - manufacture.steel_cutting_plasma_time(cutLengths, t_plate) - + manufacture.steel_filett_welding_time(theta_w, 3.0, m_box, 2 * np.pi * R_plate, t_plate) - + manufacture.steel_filett_welding_time(theta_w, 2.0, m_box + m_shell, 2 * np.pi * R_col, t_plate) - ) - - # Cost Step 4) Painting - theta_p = 1.5 - K_p = k_p * theta_p * 2.0 * A_box - - # Material cost, without outfitting - K_m = k_m * m_box - - # Total cost - c_box = K_m + K_f + K_p - - # Store outputs - outputs["buoyancy_tank_cost"] = c_box - outputs["buoyancy_tank_mass"] = m_box - outputs["buoyancy_tank_cg"] = z_cg - outputs["buoyancy_tank_displacement"] = V_box - outputs["buoyancy_tank_I_keel"] = unassembleI(I_keel) - - -class StiffenerProperties(om.ExplicitComponent): - """ - Computes column stiffener properties by section. - - Stiffener being the ring of T-cross section members placed periodically along column - Assumes constant stiffener spacing along the column, but allows for varying stiffener geometry - Slicing the column lengthwise would reveal the stiffener T-geometry as: - | | - | | - | | | | - |---- ----| - | | | | - | | - | | - - Parameters - ---------- - d_full : numpy array[n_full], [m] - cylinder diameter at corresponding locations - t_full : numpy array[n_full-1], [m] - shell thickness at corresponding locations - z_full : numpy array[n_full], [m] - z-coordinates of section nodes - rho_full : numpy array[n_full-1], [kg/m**3] - material density - shell_mass : numpy array[n_full-1], [kg] - mass of column shell - unit_cost_full : numpy array[n_full-1], [USD/kg] - Raw material cost: steel $1.1/kg, aluminum $3.5/kg - labor_cost_rate : float, [USD/min] - Labor cost - painting_cost_rate : float, [USD/m/m] - Painting / surface finishing cost rate - h_web : numpy array[n_full-1], [m] - height of stiffener web (base of T) within each section bottom to top - t_web : numpy array[n_full-1], [m] - thickness of stiffener web (base of T) within each section bottom to top - w_flange : numpy array[n_full-1], [m] - height of stiffener flange (top of T) within each section bottom to top - t_flange : numpy array[n_full-1], [m] - thickness of stiffener flange (top of T) within each section bottom to top - L_stiffener : numpy array[n_full-1], [m] - Axial distance from one ring stiffener to another within each section bottom to - top - - Returns - ------- - stiffener_mass : numpy array[n_full-1], [kg] - mass of column stiffeners - stiffener_cost : float, [USD] - cost of column stiffeners - stiffener_I_keel : numpy array[6], [kg*m**2] - Moments of inertia of stiffeners relative to keel point - number_of_stiffeners : numpy array[n_sect, dtype] - number of stiffeners in each section - flange_spacing_ratio : numpy array[n_full-1] - ratio between flange and stiffener spacing - stiffener_radius_ratio : numpy array[n_full-1] - ratio between stiffener height and radius - - """ - - def initialize(self): - self.options.declare("n_height") - - def setup(self): - n_height = self.options["n_height"] - n_sect = n_height - 1 - n_full = get_nfull(n_height) - - self.add_input("d_full", np.zeros(n_full), units="m") - self.add_input("t_full", np.zeros(n_full - 1), units="m") - self.add_input("z_full", np.zeros(n_full), units="m") - self.add_input("rho_full", np.zeros(n_full - 1), units="kg/m**3") - self.add_input("shell_mass", np.zeros(n_full - 1), units="kg") - self.add_input("unit_cost_full", np.zeros(n_full - 1), units="USD/kg") - self.add_input("labor_cost_rate", 0.0, units="USD/min") - self.add_input("painting_cost_rate", 0.0, units="USD/m/m") - self.add_input("h_web", np.zeros(n_full - 1), units="m") - self.add_input("t_web", np.zeros(n_full - 1), units="m") - self.add_input("w_flange", np.zeros(n_full - 1), units="m") - self.add_input("t_flange", np.zeros(n_full - 1), units="m") - self.add_input("L_stiffener", np.zeros(n_full - 1), units="m") - - self.add_output("stiffener_mass", np.zeros(n_full - 1), units="kg") - self.add_output("stiffener_cost", 0.0, units="USD") - self.add_output("stiffener_I_keel", np.zeros(6), units="kg*m**2") - self.add_output("number_of_stiffeners", np.zeros(n_sect, dtype=np.int_)) - self.add_output("flange_spacing_ratio", np.zeros(n_full - 1)) - self.add_output("stiffener_radius_ratio", np.zeros(n_full - 1)) - - self.declare_partials("*", "*", method="fd", form="central", step=1e-6) - - def compute(self, inputs, outputs): - # Unpack variables - R_od = 0.5 * inputs["d_full"] - t_wall = inputs["t_full"] - z_full = inputs["z_full"] # at section nodes - h_section = np.diff(z_full) - V_shell = frustum.frustumShellVol(R_od[:-1], R_od[1:], t_wall, h_section) - R_od, _ = nodal2sectional(R_od) # at section nodes - - t_web = inputs["t_web"] - t_flange = inputs["t_flange"] - h_web = inputs["h_web"] - w_flange = inputs["w_flange"] - L_stiffener = inputs["L_stiffener"] - - rho = inputs["rho_full"] - - # Outer and inner radius of web by section - R_wo = R_od - t_wall - R_wi = R_wo - h_web - # Outer and inner radius of flange by section - R_fo = R_wi - R_fi = R_fo - t_flange - - # Material volumes by section - V_web = np.pi * (R_wo ** 2 - R_wi ** 2) * t_web - V_flange = np.pi * (R_fo ** 2 - R_fi ** 2) * w_flange - - # Ring mass by volume by section - m_web = rho * V_web - m_flange = rho * V_flange - m_ring = m_web + m_flange - n_stiff = np.zeros(h_web.shape, dtype=np.int_) - - # Compute moments of inertia for stiffeners (lumped by section for simplicity) at keel - I_web = I_tube(R_wi, R_wo, t_web, m_web) - I_flange = I_tube(R_fi, R_fo, w_flange, m_flange) - I_ring = I_web + I_flange - I_keel = np.zeros((3, 3)) - - # Now march up the column, adding stiffeners at correct spacing until we are done - z_stiff = [] - isection = 0 - epsilon = 1e-6 - while True: - if len(z_stiff) == 0: - z_march = np.minimum(z_full[isection + 1], z_full[0] + 0.5 * L_stiffener[isection]) + epsilon - else: - z_march = np.minimum(z_full[isection + 1], z_stiff[-1] + L_stiffener[isection]) + epsilon - if z_march >= z_full[-1]: - break - - isection = np.searchsorted(z_full, z_march) - 1 - - if len(z_stiff) == 0: - add_stiff = (z_march - z_full[0]) >= 0.5 * L_stiffener[isection] - else: - add_stiff = (z_march - z_stiff[-1]) >= L_stiffener[isection] - - if add_stiff: - z_stiff.append(z_march) - n_stiff[isection] += 1 - - R = np.array([0.0, 0.0, (z_march - z_full[0])]) - Icg = assembleI(I_ring[isection, :]) - I_keel += Icg + m_ring[isection] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) - - # Number of stiffener rings per section (height of section divided by spacing) - outputs["stiffener_mass"] = n_stiff * m_ring - - # Find total number of stiffeners in each original section - n_sect = self.options["n_height"] - 1 - npts_per = int(h_web.size / n_sect) - n_stiff_sec = np.zeros(n_sect) - for k in range(npts_per): - n_stiff_sec += n_stiff[k::npts_per] - outputs["number_of_stiffeners"] = n_stiff_sec - - # Compute costs based on "Optimum Design of Steel Structures" by Farkas and Jarmai - # All dimensions for correlations based on mm, not meters. - k_m = inputs["unit_cost_full"] # 1.1 # USD / kg carbon steel plate - k_f = inputs["labor_cost_rate"] # 1.0 # USD / min labor - k_p = inputs["painting_cost_rate"] # USD / m^2 painting - m_shell = inputs["shell_mass"].sum() - - # Cost Step 1) Cutting stiffener strips from flat plates using plasma cutter - cutLengths_w = 2.0 * np.pi * 0.5 * (R_wo + R_wi) - cutLengths_f = 2.0 * np.pi * R_fo - # Cost Step 2) Welding T-stiffeners together GMAW-C (gas metal arc welding with CO2) fillet welds - theta_w = 3.0 # Difficulty factor - # Cost Step 3) Welding stiffeners to shell GMAW-C (gas metal arc welding with CO2) fillet welds - # Will likely fillet weld twice (top & bottom), so factor of 2 on second welding terms - - # Labor-based expenses - K_f = k_f * ( - manufacture.steel_cutting_plasma_time(n_stiff * cutLengths_w, t_web) - + manufacture.steel_cutting_plasma_time(n_stiff * cutLengths_f, t_flange) - + manufacture.steel_filett_welding_time(theta_w, n_stiff, m_ring, 2 * np.pi * R_fo, t_web) - + manufacture.steel_filett_welding_time(theta_w, n_stiff, m_ring + m_shell, 2 * np.pi * R_wo, t_web) - ) - - # Cost Step 4) Painting - theta_p = 2.0 - K_p = ( - k_p - * theta_p - * ( - n_stiff - * ( - 2 * np.pi * (R_wo ** 2.0 - R_wi ** 2.0) - + 2 * np.pi * 0.5 * (R_fo + R_fi) * (2 * w_flange + 2 * t_flange) - - 2 * np.pi * R_fo * t_web - ) - ).sum() - ) - - # Material cost, without outfitting - K_m = np.sum(k_m * outputs["stiffener_mass"]) - - # Total cost - c_ring = K_m + K_f + K_p - - # Store results - outputs["stiffener_cost"] = c_ring - outputs["stiffener_I_keel"] = unassembleI(I_keel) - - # Create some constraints for reasonable stiffener designs for an optimizer - outputs["flange_spacing_ratio"] = w_flange / (0.5 * L_stiffener) - outputs["stiffener_radius_ratio"] = (h_web + t_flange + t_wall) / R_od - - -class BallastProperties(om.ExplicitComponent): - """ - Compute ballast properties - - Parameters - ---------- - rho_water : float, [kg/m**3] - density of water - d_full : numpy array[n_full], [m] - cylinder diameter at corresponding locations - t_full : numpy array[n_full-1], [m] - shell thickness at corresponding locations - z_full : numpy array[n_full], [m] - z-coordinates of section nodes - permanent_ballast_density : float, [kg/m**3] - density of permanent ballast - permanent_ballast_height : float, [m] - height of permanent ballast - ballast_cost_rate : float, [USD/kg] - Cost per unit mass of ballast - - Returns - ------- - ballast_cost : float, [USD] - cost of permanent ballast - ballast_mass : numpy array[n_full-1], [kg] - mass of permanent ballast - ballast_z_cg : float, [m] - z-coordinate or permanent ballast center of gravity - ballast_I_keel : numpy array[6], [kg*m**2] - Moments of inertia of permanent ballast relative to keel point - variable_ballast_interp_zpts : numpy array[n_full], [m] - z-points of potential ballast mass - variable_ballast_interp_radius : numpy array[n_full], [m] - inner radius of column at potential ballast mass - - """ - - def initialize(self): - self.options.declare("n_height") - - def setup(self): - n_height = self.options["n_height"] - n_full = get_nfull(n_height) - - self.add_input("rho_water", 0.0, units="kg/m**3") - self.add_input("d_full", np.zeros(n_full), units="m") - self.add_input("t_full", np.zeros(n_full - 1), units="m") - self.add_input("z_full", np.zeros(n_full), units="m") - self.add_input("permanent_ballast_density", 0.0, units="kg/m**3") - self.add_input("permanent_ballast_height", 0.0, units="m") - self.add_input("ballast_cost_rate", 0.0, units="USD/kg") - - self.add_output("ballast_cost", 0.0, units="USD") - self.add_output("ballast_mass", np.zeros(n_full - 1), units="kg") - self.add_output("ballast_z_cg", 0.0, units="m") - self.add_output("ballast_I_keel", np.zeros(6), units="kg*m**2") - self.add_output("variable_ballast_interp_zpts", np.zeros(n_full), units="m") - self.add_output("variable_ballast_interp_radius", np.zeros(n_full), units="m") - - def compute(self, inputs, outputs): - # Unpack variables - R_od = 0.5 * inputs["d_full"] - t_wall = inputs["t_full"] - z_nodes = inputs["z_full"] - h_ballast = float(inputs["permanent_ballast_height"]) - rho_ballast = float(inputs["permanent_ballast_density"]) - rho_water = float(inputs["rho_water"]) - R_id_orig = get_inner_radius(R_od, t_wall) - - npts = R_od.size - section_mass = np.zeros(npts - 1) - - # Geometry of the column in our coordinate system (z=0 at waterline) - z_draft = z_nodes[0] - - # Fixed and total ballast mass and cg - # Assume they are bottled in columns a the keel of the column- first the permanent then the fixed - zpts = np.linspace(z_draft, z_draft + h_ballast, npts) - R_id = np.interp(zpts, z_nodes, R_id_orig) - V_perm = np.pi * np.trapz(R_id ** 2, zpts) - m_perm = rho_ballast * V_perm - z_cg_perm = rho_ballast * np.pi * np.trapz(zpts * R_id ** 2, zpts) / m_perm if m_perm > 0.0 else 0.0 - for k in range(npts - 1): - ind = np.logical_and(zpts >= z_nodes[k], zpts <= z_nodes[k + 1]) - section_mass[k] += rho_ballast * np.pi * np.trapz(R_id[ind] ** 2, zpts[ind]) - - Ixx = Iyy = frustum.frustumIxx(R_id[:-1], R_id[1:], np.diff(zpts)) - Izz = frustum.frustumIzz(R_id[:-1], R_id[1:], np.diff(zpts)) - V_slice = frustum.frustumVol(R_id[:-1], R_id[1:], np.diff(zpts)) - I_keel = np.zeros((3, 3)) - dz = frustum.frustumCG(R_id[:-1], R_id[1:], np.diff(zpts)) + zpts[:-1] - z_draft - for k in range(V_slice.size): - R = np.array([0.0, 0.0, dz[k]]) - Icg = assembleI([Ixx[k], Iyy[k], Izz[k], 0.0, 0.0, 0.0]) - I_keel += Icg + V_slice[k] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) - I_keel = rho_ballast * unassembleI(I_keel) - - # Water ballast will start at top of fixed ballast - z_water_start = z_draft + h_ballast - # z_water_start = z_water_start + inputs['variable_ballast_start'] * (z_nodes[-1] - z_water_start) - - # Find height of water ballast numerically by finding the height that integrates to the mass we want - # This step is completed in column.py or semi.py because we must account for other substructure elements too - zpts = np.linspace(z_water_start, 0.0, npts) - R_id = np.interp(zpts, z_nodes, R_id_orig) - outputs["variable_ballast_interp_zpts"] = zpts - outputs["variable_ballast_interp_radius"] = R_id - - # Save permanent ballast mass and variable height - outputs["ballast_mass"] = section_mass - outputs["ballast_I_keel"] = I_keel - outputs["ballast_z_cg"] = z_cg_perm - outputs["ballast_cost"] = inputs["ballast_cost_rate"] * m_perm - - -class ColumnProperties(om.ExplicitComponent): - """ - Compute column substructure elements in floating offshore wind turbines. - - Parameters - ---------- - rho_water : float, [kg/m**3] - density of water - z_full : numpy array[n_full], [m] - z-coordinates of section nodes (length = nsection+1) - z_section : numpy array[n_full-1], [m] - z-coordinates of section centers of mass (length = nsection) - d_full : numpy array[n_full], [m] - outer diameter at each section node bottom to top (length = nsection + 1) - t_full : numpy array[n_full-1], [m] - shell wall thickness at each section node bottom to top (length = nsection + 1) - buoyancy_tank_diameter : float, [m] - Radius of heave plate at bottom of column - shell_mass : numpy array[n_full-1], [kg] - mass of column shell - stiffener_mass : numpy array[n_full-1], [kg] - mass of column stiffeners - bulkhead_mass : numpy array[n_full-1], [kg] - mass of column bulkheads - buoyancy_tank_mass : float, [kg] - mass of heave plate - ballast_mass : numpy array[n_full-1], [kg] - mass of permanent ballast - buoyancy_tank_cg : float, [m] - z-coordinate of center of mass for buoyancy tank - ballast_z_cg : float, [m] - z-coordinate or permanent ballast center of gravity - outfitting_factor : float - Mass fraction added for outfitting - shell_I_keel : numpy array[6], [kg*m**2] - Moments of inertia of outer shell relative to keel point - bulkhead_I_keel : numpy array[6], [kg*m**2] - Moments of inertia of bulkheads relative to keel point - stiffener_I_keel : numpy array[6], [kg*m**2] - Moments of inertia of stiffeners relative to keel point - buoyancy_tank_I_keel : numpy array[6], [kg*m**2] - Moments of inertia of heave plate relative to keel point - ballast_I_keel : numpy array[6], [kg*m**2] - Moments of inertia of permanent ballast relative to keel point - buoyancy_tank_displacement : float, [m**3] - volume of water displaced by buoyancy tank - shell_cost : float, [USD] - mass of column shell - stiffener_cost : float, [USD] - mass of column stiffeners - bulkhead_cost : float, [USD] - mass of column bulkheads - ballast_cost : float, [USD] - cost of permanent ballast - buoyancy_tank_cost : float, [USD] - mass of heave plate - outfitting_cost_rate : float, [USD/kg] - Cost per unit mass for outfitting column - - Returns - ------- - z_center_of_mass : float, [m] - z-position CofG of column - z_center_of_buoyancy : float, [m] - z-position CofB of column - Awater : float, [m**2] - Area of waterplace cross section - Iwater : float, [m**4] - Second moment of area of waterplace cross section - I_column : numpy array[6], [kg*m**2] - Moments of inertia of whole column relative to keel point - displaced_volume : numpy array[n_full-1], [m**3] - Volume of water displaced by column by section - hydrostatic_force : numpy array[n_full-1], [N] - Net z-force on column sections - column_structural_mass : float, [kg] - mass of column structure - column_outfitting_cost : float, [USD] - cost of outfitting the column - column_outfitting_mass : float, [kg] - cost of outfitting the column - column_added_mass : numpy array[6], [kg] - hydrodynamic added mass matrix diagonal - column_total_mass : numpy array[n_full-1], [kg] - total mass of column by section - column_total_cost : float, [USD] - total cost of column - column_structural_cost : float, [USD] - Cost of column without ballast or outfitting - tapered_column_cost_rate : float, [USD/t] - Cost rate of finished column - - """ - - def initialize(self): - self.options.declare("n_height") - - def setup(self): - n_height = self.options["n_height"] - n_full = get_nfull(n_height) - - # Variables local to the class and not OpenMDAO - self.ibox = None - - self.add_input("rho_water", 0.0, units="kg/m**3") - - # Inputs from geometry - self.add_input("z_full", np.zeros(n_full), units="m") - self.add_input("z_section", np.zeros(n_full - 1), units="m") - - # Design variables - self.add_input("d_full", np.zeros(n_full), units="m") - self.add_input("t_full", np.zeros(n_full - 1), units="m") - self.add_input("buoyancy_tank_diameter", 0.0, units="m") - - # Mass correction factors from simple rules here to real life - self.add_input("shell_mass", np.zeros(n_full - 1), units="kg") - self.add_input("stiffener_mass", np.zeros(n_full - 1), units="kg") - self.add_input("bulkhead_mass", np.zeros(n_full - 1), units="kg") - self.add_input("buoyancy_tank_mass", 0.0, units="kg") - self.add_input("ballast_mass", np.zeros(n_full - 1), units="kg") - - self.add_input("buoyancy_tank_cg", 0.0, units="m") - self.add_input("bulkhead_z_cg", 0.0, units="m") - self.add_input("ballast_z_cg", 0.0, units="m") - self.add_input("outfitting_factor", 0.0) - - # Moments of inertia - self.add_input("shell_I_keel", np.zeros(6), units="kg*m**2") - self.add_input("bulkhead_I_keel", np.zeros(6), units="kg*m**2") - self.add_input("stiffener_I_keel", np.zeros(6), units="kg*m**2") - self.add_input("buoyancy_tank_I_keel", np.zeros(6), units="kg*m**2") - self.add_input("ballast_I_keel", np.zeros(6), units="kg*m**2") - - # For buoyancy - self.add_input("buoyancy_tank_displacement", 0.0, units="m**3") - - # Costs and cost rates - self.add_input("shell_cost", 0.0, units="USD") - self.add_input("stiffener_cost", 0.0, units="USD") - self.add_input("bulkhead_cost", 0.0, units="USD") - self.add_input("ballast_cost", 0.0, units="USD") - self.add_input("buoyancy_tank_cost", 0.0, units="USD") - self.add_input("outfitting_cost_rate", 0.0, units="USD/kg") - - self.add_output("z_center_of_mass", 0.0, units="m") - self.add_output("z_center_of_buoyancy", 0.0, units="m") - self.add_output("Awater", 0.0, units="m**2") - self.add_output("Iwater", 0.0, units="m**4") - self.add_output("I_column", np.zeros(6), units="kg*m**2") - self.add_output("displaced_volume", np.zeros(n_full - 1), units="m**3") - self.add_output("hydrostatic_force", np.zeros(n_full - 1), units="N") - - self.add_output("column_structural_mass", 0.0, units="kg") - self.add_output("column_outfitting_cost", 0.0, units="USD") - self.add_output("column_outfitting_mass", 0.0, units="kg") - - self.add_output("column_added_mass", np.zeros(6), units="kg") - self.add_output("column_total_mass", np.zeros(n_full - 1), units="kg") - self.add_output("column_total_cost", 0.0, units="USD") - self.add_output("column_structural_cost", 0.0, units="USD") - self.add_output("tapered_column_cost_rate", 0.0, units="USD/t") - - self.declare_partials("*", "*", method="fd", form="central", step=1e-6) - - def compute(self, inputs, outputs): - self.compute_column_mass_cg(inputs, outputs) - self.balance_column(inputs, outputs) - self.compute_cost(inputs, outputs) - - def compute_column_mass_cg(self, inputs, outputs): - """Computes column mass from components: Shell, Stiffener rings, Bulkheads - Also computes center of mass of the shell by weighted sum of the components' position - - INPUTS: - ---------- - inputs : dictionary of input parameters - outputs : dictionary of output parameters - - OUTPUTS: - ---------- - section_mass class variable set - m_column : column mass - z_cg : center of mass along z-axis for the column - column_mass in 'outputs' dictionary set - shell_mass in 'outputs' dictionary set - stiffener_mass in 'outputs' dictionary set - bulkhead_mass in 'outputs' dictionary set - outfitting_mass in 'outputs' dictionary set - """ - # Unpack variables - out_frac = inputs["outfitting_factor"] - z_nodes = inputs["z_full"] - z_section = inputs["z_section"] - z_box = inputs["buoyancy_tank_cg"] - z_ballast = inputs["ballast_z_cg"] - z_bulkhead = inputs["bulkhead_z_cg"] - m_shell = inputs["shell_mass"] - m_stiffener = inputs["stiffener_mass"] - m_bulkhead = inputs["bulkhead_mass"] - m_box = inputs["buoyancy_tank_mass"] - m_ballast = inputs["ballast_mass"] - I_shell = inputs["shell_I_keel"] - I_stiffener = inputs["stiffener_I_keel"] - I_bulkhead = inputs["bulkhead_I_keel"] - I_box = inputs["buoyancy_tank_I_keel"] - I_ballast = inputs["ballast_I_keel"] - - # Consistency check - if out_frac < 1.0: - out_frac += 1.0 - - # Initialize summations - m_column = 0.0 - z_cg = 0.0 - - # Find mass of all of the sub-components of the column - # Masses assumed to be focused at section centroids - m_column += (m_shell + m_stiffener).sum() - z_cg += np.dot(m_shell + m_stiffener, z_section) - - # Mass with variable location - m_column += m_box + m_bulkhead.sum() - z_cg += m_box * z_box + m_bulkhead.sum() * z_bulkhead - - z_cg /= m_column - - # Now calculate outfitting mass, evenly distributed so cg doesn't change - m_outfit = (out_frac - 1.0) * m_column - - # Add in ballast - m_total = m_column + m_outfit + m_ballast.sum() - z_cg = ((m_column + m_outfit) * z_cg + m_ballast.sum() * z_ballast) / m_total - - # Find sections for ballast and buoyancy tank - ibox = 0 - try: - ibox = np.where(z_box >= z_nodes)[0][-1] - except: - print(z_box, z_ballast, z_nodes) - self.ibox = ibox - - # Now do tally by section - m_sections = (m_shell + m_stiffener + m_bulkhead) + m_ballast + m_outfit / m_shell.size - m_sections[ibox] += m_box - - # Add up moments of inertia at keel, make sure to scale mass appropriately - I_total = out_frac * (I_shell + I_stiffener + I_bulkhead + I_box) + I_ballast - - # Move moments of inertia from keel to cg - I_total -= m_total * ((z_cg - z_nodes[0]) ** 2.0) * np.r_[1.0, 1.0, np.zeros(4)] - I_total = np.maximum(I_total, 0.0) - - # Store outputs addressed so far - outputs["column_total_mass"] = m_sections - outputs["column_structural_mass"] = m_column + m_outfit - outputs["column_outfitting_mass"] = m_outfit - outputs["z_center_of_mass"] = z_cg - outputs["I_column"] = I_total - - def balance_column(self, inputs, outputs): - # Unpack variables - R_od = 0.5 * inputs["d_full"] - R_plate = 0.5 * inputs["buoyancy_tank_diameter"] - z_nodes = inputs["z_full"] - z_box = inputs["buoyancy_tank_cg"] - V_box = inputs["buoyancy_tank_displacement"] - rho_water = inputs["rho_water"] - nsection = R_od.size - 1 - - # Compute volume of each section and mass of displaced water by section - # Find the radius at the waterline so that we can compute the submerged volume as a sum of frustum sections - if z_nodes[-1] > 0.0: - r_waterline = np.interp(0.0, z_nodes, R_od) - z_under = np.r_[z_nodes[z_nodes < 0.0], 0.0] - r_under = np.r_[R_od[z_nodes < 0.0], r_waterline] - else: - r_waterline = R_od[-1] - r_under = R_od - z_under = z_nodes - - # Submerged volume (with zero-padding) - V_under = frustum.frustumVol(r_under[:-1], r_under[1:], np.diff(z_under)) - add0 = np.maximum(0, nsection - V_under.size) - outputs["displaced_volume"] = np.r_[V_under, np.zeros(add0)] - outputs["displaced_volume"][self.ibox] += V_box - - # Compute Center of Buoyancy in z-coordinates (0=waterline) - # First get z-coordinates of CG of all frustums - z_cg_under = frustum.frustumCG(r_under[:-1], r_under[1:], np.diff(z_under)) - z_cg_under += z_under[:-1] - # Now take weighted average of these CG points with volume - z_cb = ((V_box * z_box) + np.dot(V_under, z_cg_under)) / (outputs["displaced_volume"].sum() + eps) - outputs["z_center_of_buoyancy"] = z_cb - - # Find total hydrostatic force by section- sign says in which direction force acts - # Since we are working on z_under grid, need to redefine z_section, ibox, etc. - z_undersec, _ = nodal2sectional(z_under) - if z_box > 0.0 and V_box == 0.0: - ibox = 0 - else: - ibox = np.where(z_box >= z_under)[0][-1] - F_hydro = np.pi * np.diff(r_under ** 2.0) * np.maximum(0.0, -z_undersec) # cg_under)) - if F_hydro.size > 0: - F_hydro[0] += np.pi * r_under[0] ** 2 * (-z_under[0]) - if z_nodes[-1] < 0.0: - F_hydro[-1] -= np.pi * r_under[-1] ** 2 * (-z_under[-1]) - F_hydro[ibox] += V_box - F_hydro *= rho_water * gravity - outputs["hydrostatic_force"] = np.r_[F_hydro, np.zeros(add0)] - - # 2nd moment of area for circular cross section - # Note: Assuming Iwater here depends on "water displacement" cross-section - # and not actual moment of inertia type of cross section (thin hoop) - outputs["Iwater"] = 0.25 * np.pi * r_waterline ** 4.0 - outputs["Awater"] = np.pi * r_waterline ** 2.0 - - # Calculate diagonal entries of added mass matrix - # Prep for integrals too - npts = 100 * R_od.size - zpts = np.linspace(z_under[0], z_under[-1], npts) - r_under = np.interp(zpts, z_under, r_under) - m_a = np.zeros(6) - m_a[:2] = rho_water * outputs["displaced_volume"].sum() # A11 surge, A22 sway - m_a[2] = 0.5 * (8.0 / 3.0) * rho_water * np.maximum(R_plate, r_under.max()) ** 3.0 # A33 heave - m_a[3:5] = np.pi * rho_water * np.trapz((zpts - z_cb) ** 2.0 * r_under ** 2.0, zpts) # A44 roll, A55 pitch - m_a[5] = 0.0 # A66 yaw - outputs["column_added_mass"] = m_a - - def compute_cost(self, inputs, outputs): - outputs["column_structural_cost"] = ( - inputs["shell_cost"] + inputs["stiffener_cost"] + inputs["bulkhead_cost"] + inputs["buoyancy_tank_cost"] - ) - outputs["column_outfitting_cost"] = inputs["outfitting_cost_rate"] * outputs["column_outfitting_mass"] - outputs["column_total_cost"] = ( - outputs["column_structural_cost"] + outputs["column_outfitting_cost"] + inputs["ballast_cost"] - ) - outputs["tapered_column_cost_rate"] = 1e3 * outputs["column_total_cost"] / outputs["column_total_mass"].sum() - - -class ColumnBuckling(om.ExplicitComponent): - """ - Compute the applied axial and hoop stresses in a column and compare that to - limits established by the API standard. Some physcial geometry checks are also performed. - - Parameters - ---------- - stack_mass_in : float, [kg] - Weight above the cylinder column - section_mass : numpy array[n_full-1], [kg] - total mass of column by section - pressure : numpy array[n_full], [N/m**2] - Dynamic (and static)? pressure - d_full : numpy array[n_full], [m] - cylinder diameter at corresponding locations - t_full : numpy array[n_full-1], [m] - shell thickness at corresponding locations - z_full : numpy array[n_full], [m] - z-coordinates of section nodes (length = nsection+1) - h_web : numpy array[n_full-1], [m] - height of stiffener web (base of T) within each section bottom to top - t_web : numpy array[n_full-1], [m] - thickness of stiffener web (base of T) within each section bottom to top - w_flange : numpy array[n_full-1], [m] - height of stiffener flange (top of T) within each section bottom to top - t_flange : numpy array[n_full-1], [m] - thickness of stiffener flange (top of T) within each section bottom to top - L_stiffener : numpy array[n_full-1], [m] - Axial distance from one ring stiffener to another within each section bottom to - top - E : float, [Pa] - Modulus of elasticity (Youngs) of material - nu : float - poissons ratio of column material - yield_stress : float, [Pa] - yield stress of material - loading : string - Loading type in API checks [hydro/radial] - - Returns - ------- - flange_compactness : numpy array[n_full-1] - check for flange compactness - web_compactness : numpy array[n_full-1] - check for web compactness - axial_local_api : numpy array[n_full-1] - unity check for axial load with API safety factors - local buckling - axial_general_api : numpy array[n_full-1] - unity check for axial load with API safety factors- genenral instability - external_local_api : numpy array[n_full-1] - unity check for external pressure with API safety factors- local buckling - external_general_api : numpy array[n_full-1] - unity check for external pressure with API safety factors- general instability - axial_local_utilization : numpy array[n_full-1] - utilization check for axial load - local buckling - axial_general_utilization : numpy array[n_full-1] - utilization check for axial load - genenral instability - external_local_utilization : numpy array[n_full-1] - utilization check for external pressure - local buckling - external_general_utilization : numpy array[n_full-1] - utilization check for external pressure - general instability - - """ - - def initialize(self): - self.options.declare("n_height") - self.options.declare("modeling_options") - - def setup(self): - n_height = self.options["n_height"] - n_full = get_nfull(n_height) - - self.add_input("stack_mass_in", eps, units="kg") - self.add_input("section_mass", np.zeros(n_full - 1), units="kg") - self.add_input("pressure", np.zeros(n_full), units="N/m**2") - self.add_input("d_full", np.zeros(n_full), units="m") - self.add_input("t_full", np.zeros(n_full - 1), units="m") - self.add_input("z_full", np.zeros(n_full), units="m") - self.add_input("h_web", np.zeros(n_full - 1), units="m") - self.add_input("t_web", np.zeros(n_full - 1), units="m") - self.add_input("w_flange", np.zeros(n_full - 1), units="m") - self.add_input("t_flange", np.zeros(n_full - 1), units="m") - self.add_input("L_stiffener", np.zeros(n_full - 1), units="m") - self.add_input("E_full", np.zeros(n_full - 1), units="Pa") - self.add_input("nu_full", np.zeros(n_full - 1)) - self.add_input("sigma_y_full", np.zeros(n_full - 1), units="Pa") - self.add_discrete_input("loading", "hydro") - - self.add_output("flange_compactness", np.zeros(n_full - 1)) - self.add_output("web_compactness", np.zeros(n_full - 1)) - self.add_output("axial_local_api", np.zeros(n_full - 1)) - self.add_output("axial_general_api", np.zeros(n_full - 1)) - self.add_output("external_local_api", np.zeros(n_full - 1)) - self.add_output("external_general_api", np.zeros(n_full - 1)) - self.add_output("axial_local_utilization", np.zeros(n_full - 1)) - self.add_output("axial_general_utilization", np.zeros(n_full - 1)) - self.add_output("external_local_utilization", np.zeros(n_full - 1)) - self.add_output("external_general_utilization", np.zeros(n_full - 1)) - - # Derivatives - self.declare_partials("*", "*", method="fd", form="central", step=1e-6) - - def compute_applied_axial(self, inputs): - """Compute axial stress for column from z-axis loading - - INPUTS: - ---------- - inputs : dictionary of input parameters - section_mass : float (scalar/vector), mass of each column section as axial loading increases with column depth - - OUTPUTS: - ------- - stress : float (scalar/vector), axial stress - """ - # Unpack variables - R_od, _ = nodal2sectional(inputs["d_full"]) - R_od *= 0.5 - t_wall = inputs["t_full"] - section_mass = inputs["section_mass"] - m_stack = inputs["stack_mass_in"] - - # Middle radius - R_m = R_od - 0.5 * t_wall - # Add in weight of sections above it - axial_load = m_stack + np.r_[0.0, np.cumsum(section_mass[:-1])] - # Divide by shell cross sectional area to get stress - return gravity * axial_load / (2.0 * np.pi * R_m * t_wall) - - def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - # Unpack variables - R_od, _ = nodal2sectional(inputs["d_full"]) - R_od *= 0.5 - h_section = np.diff(inputs["z_full"]) - t_wall = inputs["t_full"] - - t_web = inputs["t_web"] - t_flange = inputs["t_flange"] - h_web = inputs["h_web"] - w_flange = inputs["w_flange"] - L_stiffener = inputs["L_stiffener"] - - gamma_f = self.options["modeling_options"]["gamma_f"] - gamma_b = self.options["modeling_options"]["gamma_b"] - - E = inputs["E_full"] # Young's modulus - nu = inputs["nu_full"] # Poisson ratio - sigma_y = inputs["sigma_y_full"] - loading = discrete_inputs["loading"] - nodalP, _ = nodal2sectional(inputs["pressure"]) - pressure = 1e-12 if loading in ["ax", "axial", "testing", "test"] else nodalP + 1e-12 - - # Apply quick "compactness" check on stiffener geometry - # Constraint is that these must be >= 1 - flange_compactness = 0.375 * (t_flange / (0.5 * w_flange)) * np.sqrt(E / sigma_y) - web_compactness = 1.0 * (t_web / h_web) * np.sqrt(E / sigma_y) - - # Compute applied axial stress simply, like API guidelines (as opposed to running frame3dd) - sigma_ax = self.compute_applied_axial(inputs) - ( - axial_local_api, - axial_general_api, - external_local_api, - external_general_api, - axial_local_raw, - axial_general_raw, - external_local_raw, - external_general_raw, - ) = shellBuckling_withStiffeners( - pressure, - sigma_ax, - R_od, - t_wall, - h_section, - h_web, - t_web, - w_flange, - t_flange, - L_stiffener, - E, - nu, - sigma_y, - loading, - ) - - outputs["flange_compactness"] = flange_compactness - outputs["web_compactness"] = web_compactness - - outputs["axial_local_api"] = axial_local_api - outputs["axial_general_api"] = axial_general_api - outputs["external_local_api"] = external_local_api - outputs["external_general_api"] = external_general_api - - outputs["axial_local_utilization"] = axial_local_raw * gamma_f * gamma_b - outputs["axial_general_utilization"] = axial_general_raw * gamma_f * gamma_b - outputs["external_local_utilization"] = external_local_raw * gamma_f * gamma_b - outputs["external_general_utilization"] = external_general_raw * gamma_f * gamma_b - - -class Column(om.Group): - def initialize(self): - self.options.declare("n_mat") - self.options.declare("column_options") - self.options.declare("modeling_options") - - def setup(self): - opt = self.options["modeling_options"] - colopt = self.options["column_options"] - n_layers = colopt["n_layers"] - n_height = colopt["n_height"] - n_bulk = colopt["n_bulkhead"] - n_mat = self.options["n_mat"] - n_sect = n_height - 1 - n_full = get_nfull(n_height) - - self.set_input_defaults("cd_usr", -1.0) - self.set_input_defaults("Tsig_wave", 10.0) - self.set_input_defaults("layer_materials", ["steel"]) - self.set_input_defaults("material_names", ["steel"]) - - # TODO: Use reference axis and curvature, s, instead of assuming everything is vertical on z - self.add_subsystem( - "yaml", DiscretizationYAML(n_height=n_height, n_layers=n_layers, n_mat=n_mat), promotes=["*"] - ) - - self.add_subsystem( - "gc", GeometricConstraints(nPoints=n_height, diamFlag=True), promotes=["constr_taper", "constr_d_to_t"] - ) - - self.add_subsystem( - "cyl_geom", - CylinderDiscretization(nPoints=n_height), - promotes=["section_height", "diameter", "wall_thickness", "foundation_height", "d_full", "t_full"], - ) - - self.add_subsystem("col_geom", ColumnGeometry(n_height=n_height), promotes=["*"]) - - self.add_subsystem( - "cyl_mass", - CylinderMass(nPoints=n_full), - promotes=["z_full", "d_full", "t_full", "labor_cost_rate", "painting_cost_rate"], - ) - - self.connect("rho_full", "cyl_mass.rho") - self.connect("unit_cost_full", "cyl_mass.material_cost_rate") - - self.add_subsystem("bulk", BulkheadProperties(n_height=n_height, n_bulkhead=n_bulk), promotes=["*"]) - - self.add_subsystem("stiff", StiffenerProperties(n_height=n_height), promotes=["*"]) - - self.add_subsystem("plate", BuoyancyTankProperties(n_height=n_height), promotes=["*"]) - - self.add_subsystem("ball", BallastProperties(n_height=n_height), promotes=["*"]) - - self.add_subsystem("col", ColumnProperties(n_height=n_height), promotes=["*"]) - - self.add_subsystem("wind", PowerWind(nPoints=n_full), promotes=["Uref", "zref", "shearExp", ("z0", "wind_z0")]) - self.add_subsystem( - "wave", - LinearWaves(nPoints=n_full), - promotes=[ - "Uc", - "Hsig_wave", - "Tsig_wave", - "rho_water", - ("z_floor", "water_depth"), - ("z_surface", "wave_z0"), - ], - ) - self.add_subsystem( - "windLoads", CylinderWindDrag(nPoints=n_full), promotes=["cd_usr", "beta_wind", "rho_air", "mu_air"] - ) - self.add_subsystem( - "waveLoads", - CylinderWaveDrag(nPoints=n_full), - promotes=["cm", "cd_usr", "beta_wave", "rho_water", "mu_water"], - ) - self.add_subsystem("distLoads", AeroHydroLoads(nPoints=n_full), promotes=["Px", "Py", "Pz", "qdyn", "yaw"]) - - self.add_subsystem("buck", ColumnBuckling(n_height=n_height, modeling_options=opt), promotes=["*"]) - - self.connect("outer_diameter", ["diameter", "gc.d"]) - self.connect("wall_thickness", "gc.t") - self.connect("cyl_geom.z_param", "z_param_in") - self.connect("cyl_geom.z_full", "z_full_in") - - # self.connect('cyl_mass.section_center_of_mass', 'col_geom.section_center_of_mass') - - self.connect("cyl_mass.mass", "shell_mass") - self.connect("cyl_mass.cost", "shell_cost") - self.connect("cyl_mass.I_base", "shell_I_keel") - self.connect("cyl_mass.section_center_of_mass", "z_section") - - self.connect("column_total_mass", "section_mass") - - self.connect("z_full", ["wind.z", "wave.z", "windLoads.z", "waveLoads.z", "distLoads.z"]) - self.connect("d_full", ["windLoads.d", "waveLoads.d"]) - - self.connect("wind.U", "windLoads.U") - - self.connect("wave.U", "waveLoads.U") - self.connect("wave.A", "waveLoads.A") - self.connect("wave.p", "waveLoads.p") - - # connections to distLoads1 - self.connect("windLoads.windLoads_Px", "distLoads.windLoads_Px") - self.connect("windLoads.windLoads_Py", "distLoads.windLoads_Py") - self.connect("windLoads.windLoads_Pz", "distLoads.windLoads_Pz") - self.connect("windLoads.windLoads_qdyn", "distLoads.windLoads_qdyn") - self.connect("windLoads.windLoads_beta", "distLoads.windLoads_beta") - self.connect("windLoads.windLoads_z", "distLoads.windLoads_z") - self.connect("windLoads.windLoads_d", "distLoads.windLoads_d") - - self.connect("waveLoads.waveLoads_Px", "distLoads.waveLoads_Px") - self.connect("waveLoads.waveLoads_Py", "distLoads.waveLoads_Py") - self.connect("waveLoads.waveLoads_Pz", "distLoads.waveLoads_Pz") - self.connect("waveLoads.waveLoads_pt", "distLoads.waveLoads_qdyn") - self.connect("waveLoads.waveLoads_beta", "distLoads.waveLoads_beta") - self.connect("waveLoads.waveLoads_z", "distLoads.waveLoads_z") - self.connect("waveLoads.waveLoads_d", "distLoads.waveLoads_d") - - self.connect("qdyn", "pressure") diff --git a/wisdem/floatingse/floating.py b/wisdem/floatingse/floating.py index b065cf343..502e2b223 100644 --- a/wisdem/floatingse/floating.py +++ b/wisdem/floatingse/floating.py @@ -1,9 +1,10 @@ +import numpy as np import openmdao.api as om -from wisdem.towerse.tower import TowerLeanSE -from wisdem.floatingse.column import Column -from wisdem.floatingse.loading import Loading +from wisdem.floatingse.member import Member from wisdem.floatingse.map_mooring import MapMooring -from wisdem.floatingse.substructure import Substructure, SubstructureGeometry +from wisdem.floatingse.floating_frame import FloatingFrame + +# from wisdem.floatingse.substructure import Substructure, SubstructureGeometry class FloatingSE(om.Group): @@ -11,188 +12,82 @@ def initialize(self): self.options.declare("modeling_options") def setup(self): - opt = self.options["modeling_options"]["platform"] - n_mat = self.options["modeling_options"]["materials"]["n_mat"] - n_height_main = opt["columns"]["main"]["n_height"] - n_height_off = opt["columns"]["offset"]["n_height"] - n_height_tow = self.options["modeling_options"]["TowerSE"]["n_height_tower"] - - self.set_input_defaults("mooring_type", "chain") - self.set_input_defaults("anchor_type", "SUCTIONPILE") - self.set_input_defaults("loading", "hydrostatic") - self.set_input_defaults("wave_period_range_low", 2.0, units="s") - self.set_input_defaults("wave_period_range_high", 20.0, units="s") - self.set_input_defaults("cd_usr", -1.0) - self.set_input_defaults("zref", 100.0) - self.set_input_defaults("number_of_offset_columns", 0) - self.set_input_defaults("material_names", ["steel"]) - - self.add_subsystem( - "tow", - TowerLeanSE(modeling_options=self.options["modeling_options"]), - promotes=[ - "tower_s", - "tower_height", - "tower_outer_diameter_in", - "tower_layer_thickness", - "tower_outfitting_factor", - "rna_mass", - "rna_cg", - "rna_I", - "tower_mass", - "tower_I_base", - "hub_height", - "material_names", - "labor_cost_rate", - "painting_cost_rate", - "unit_cost_mat", - "rho_mat", - "E_mat", - "G_mat", - "sigma_y_mat", - ("tower_foundation_height", "main_freeboard"), - ], - ) - - # Next do main and ballast columns - # Ballast columns are replicated from same design in the components - column_promotes = [ + opt = self.options["modeling_options"] + + # self.set_input_defaults("mooring_type", "chain") + # self.set_input_defaults("anchor_type", "SUCTIONPILE") + # self.set_input_defaults("loading", "hydrostatic") + # self.set_input_defaults("wave_period_range_low", 2.0, units="s") + # self.set_input_defaults("wave_period_range_high", 20.0, units="s") + # self.set_input_defaults("cd_usr", -1.0) + # self.set_input_defaults("zref", 100.0) + # self.set_input_defaults("number_of_offset_columns", 0) + # self.set_input_defaults("material_names", ["steel"]) + + n_member = opt["floating"]["members"]["n_members"] + mem_prom = [ "E_mat", "G_mat", "sigma_y_mat", - "rho_air", - "mu_air", - "rho_water", - "mu_water", "rho_mat", - "shearExp", - "yaw", - "Uc", - "water_depth", - "Hsig_wave", - "Tsig_wave", - "cd_usr", - "cm", - "loading", - "beta_wind", - "beta_wave", - "max_draft", - "material_names", - "permanent_ballast_density", - "outfitting_factor", - "ballast_cost_rate", + "rho_water", "unit_cost_mat", - "labor_cost_rate", + "material_names", "painting_cost_rate", - "outfitting_cost_rate", - "Uref", - "zref", - "wind_z0", + "labor_cost_rate", ] - main_column_promotes = column_promotes.copy() - main_column_promotes.append(("freeboard", "main_freeboard")) - - self.add_subsystem( - "main", - Column(modeling_options=opt, column_options=opt["columns"]["main"], n_mat=n_mat), - promotes=main_column_promotes, - ) - - off_column_promotes = column_promotes.copy() - off_column_promotes.append(("freeboard", "off_freeboard")) + # mem_prom += ["Uref", "zref", "shearExp", "z0", "cd_usr", "cm", "beta_wind", "rho_air", "mu_air", "beta_water", + # "rho_water", "mu_water", "Uc", "Hsig_wave","Tsig_wave","rho_water","water_depth"] + for k in range(n_member): + self.add_subsystem( + "member" + str(k), + Member(column_options=opt["floating"]["members"], idx=k, n_mat=opt["materials"]["n_mat"]), + promotes=mem_prom, + ) + # Next run MapMooring self.add_subsystem( - "off", - Column(modeling_options=opt, column_options=opt["columns"]["offset"], n_mat=n_mat), - promotes=off_column_promotes, - ) - - # Run Semi Geometry for interfaces - self.add_subsystem( - "sg", SubstructureGeometry(n_height_main=n_height_main, n_height_off=n_height_off), promotes=["*"] + "mm", MapMooring(options=opt["mooring"], gamma=opt["WISDEM"]["FloatingSE"]["gamma_f"]), promotes=["*"] ) - # Next run MapMooring - self.add_subsystem("mm", MapMooring(modeling_options=opt), promotes=["*"]) - # Add in the connecting truss - self.add_subsystem( - "load", - Loading( - n_height_main=n_height_main, n_height_off=n_height_off, n_height_tow=n_height_tow, modeling_options=opt - ), - promotes=["*"], - ) + self.add_subsystem("load", FloatingFrame(modeling_options=opt), promotes=["*"]) - # Run main Semi analysis - self.add_subsystem( - "subs", - Substructure(n_height_main=n_height_main, n_height_off=n_height_off, n_height_tow=n_height_tow), - promotes=["*"], - ) + # Evaluate system constraints + # self.add_subsystem("cons", FloatingConstraints(modeling_options=opt), promotes=["*"]) # Connect all input variables from all models - self.connect("tow.d_full", ["windLoads.d", "tower_d_full"]) - self.connect("tow.d_full", "tower_d_base", src_indices=[0]) - self.connect("tow.t_full", "tower_t_full") - self.connect("tow.z_full", ["loadingWind.z", "windLoads.z", "tower_z_full"]) # includes tower_z_full - self.connect("tow.E_full", "tower_E_full") - self.connect("tow.G_full", "tower_G_full") - self.connect("tow.rho_full", "tower_rho_full") - self.connect("tow.sigma_y_full", "tower_sigma_y_full") - self.connect("tow.cm.mass", "tower_mass_section") - self.connect("tow.turbine_mass", "main.stack_mass_in") - self.connect("tow.tower_center_of_mass", "tower_center_of_mass") - self.connect("tow.tower_cost", "tower_shell_cost") - - self.connect("main.z_full", ["main_z_nodes", "main_z_full"]) - self.connect("main.d_full", "main_d_full") - self.connect("main.t_full", "main_t_full") - self.connect("main.E_full", "main_E_full") - self.connect("main.G_full", "main_G_full") - self.connect("main.rho_full", "main_rho_full") - self.connect("main.sigma_y_full", "main_sigma_y_full") - - self.connect("off.z_full", ["offset_z_nodes", "offset_z_full"]) - self.connect("off.d_full", "offset_d_full") - self.connect("off.t_full", "offset_t_full") - self.connect("off.E_full", "offset_E_full") - self.connect("off.G_full", "offset_G_full") - self.connect("off.rho_full", "offset_rho_full") - self.connect("off.sigma_y_full", "offset_sigma_y_full") + mem_vars = [ + "nodes_xyz", + "nodes_r", + "transition_node", + "section_D", + "section_t", + "section_A", + "section_Asx", + "section_Asy", + "section_Ixx", + "section_Iyy", + "section_Izz", + "section_rho", + "section_E", + "section_G", + "idx_cb", + "buoyancy_force", + "displacement", + "center_of_buoyancy", + "center_of_mass", + "total_mass", + "total_cost", + "Awater", + "Iwater", + "added_mass", + ] + for k in range(n_member): + for var in mem_vars: + self.connect("member" + str(k) + "." + var, "member" + str(k) + ":" + var) + """ self.connect("max_offset_restoring_force", "mooring_surge_restoring_force") self.connect("operational_heel_restoring_force", "mooring_pitch_restoring_force") - - self.connect("main.z_center_of_mass", "main_center_of_mass") - self.connect("main.z_center_of_buoyancy", "main_center_of_buoyancy") - self.connect("main.I_column", "main_moments_of_inertia") - self.connect("main.Iwater", "main_Iwaterplane") - self.connect("main.Awater", "main_Awaterplane") - self.connect("main.displaced_volume", "main_displaced_volume") - self.connect("main.hydrostatic_force", "main_hydrostatic_force") - self.connect("main.column_added_mass", "main_added_mass") - self.connect("main.column_total_mass", "main_mass") - self.connect("main.column_total_cost", "main_cost") - self.connect("main.variable_ballast_interp_zpts", "water_ballast_zpts_vector") - self.connect("main.variable_ballast_interp_radius", "water_ballast_radius_vector") - self.connect("main.Px", "main_Px") - self.connect("main.Py", "main_Py") - self.connect("main.Pz", "main_Pz") - self.connect("main.qdyn", "main_qdyn") - - self.connect("off.z_center_of_mass", "offset_center_of_mass") - self.connect("off.z_center_of_buoyancy", "offset_center_of_buoyancy") - self.connect("off.I_column", "offset_moments_of_inertia") - self.connect("off.Iwater", "offset_Iwaterplane") - self.connect("off.Awater", "offset_Awaterplane") - self.connect("off.displaced_volume", "offset_displaced_volume") - self.connect("off.hydrostatic_force", "offset_hydrostatic_force") - self.connect("off.column_added_mass", "offset_added_mass") - self.connect("off.column_total_mass", "offset_mass") - self.connect("off.column_total_cost", "offset_cost") - self.connect("off.Px", "offset_Px") - self.connect("off.Py", "offset_Py") - self.connect("off.Pz", "offset_Pz") - self.connect("off.qdyn", "offset_qdyn") - self.connect("off.draft", "offset_draft") + """ diff --git a/wisdem/floatingse/floating_frame.py b/wisdem/floatingse/floating_frame.py new file mode 100644 index 000000000..951b70a4c --- /dev/null +++ b/wisdem/floatingse/floating_frame.py @@ -0,0 +1,677 @@ +import numpy as np +import openmdao.api as om +import wisdem.commonse.utilities as util +import wisdem.pyframe3dd.pyframe3dd as pyframe3dd +from wisdem.commonse import NFREQ, gravity +from wisdem.floatingse.member import NULL, MEMMAX, Member + +NNODES_MAX = 1000 +NELEM_MAX = 1000 +RIGID = 1e30 + +# TODO: +# - Added mass, hydro stiffness? +# - Stress or buckling? + + +class PlatformFrame(om.ExplicitComponent): + def initialize(self): + self.options.declare("options") + + def setup(self): + opt = self.options["options"] + n_member = opt["floating"]["members"]["n_members"] + + for k in range(n_member): + self.add_input("member" + str(k) + ":nodes_xyz", NULL * np.ones((MEMMAX, 3)), units="m") + self.add_input("member" + str(k) + ":nodes_r", NULL * np.ones(MEMMAX), units="m") + self.add_input("member" + str(k) + ":section_D", NULL * np.ones(MEMMAX), units="m") + self.add_input("member" + str(k) + ":section_t", NULL * np.ones(MEMMAX), units="m") + self.add_input("member" + str(k) + ":section_A", NULL * np.ones(MEMMAX), units="m**2") + self.add_input("member" + str(k) + ":section_Asx", NULL * np.ones(MEMMAX), units="m**2") + self.add_input("member" + str(k) + ":section_Asy", NULL * np.ones(MEMMAX), units="m**2") + self.add_input("member" + str(k) + ":section_Ixx", NULL * np.ones(MEMMAX), units="kg*m**2") + self.add_input("member" + str(k) + ":section_Iyy", NULL * np.ones(MEMMAX), units="kg*m**2") + self.add_input("member" + str(k) + ":section_Izz", NULL * np.ones(MEMMAX), units="kg*m**2") + self.add_input("member" + str(k) + ":section_rho", NULL * np.ones(MEMMAX), units="kg/m**3") + self.add_input("member" + str(k) + ":section_E", NULL * np.ones(MEMMAX), units="Pa") + self.add_input("member" + str(k) + ":section_G", NULL * np.ones(MEMMAX), units="Pa") + self.add_input("member" + str(k) + ":idx_cb", 0) + self.add_input("member" + str(k) + ":buoyancy_force", 0.0, units="N") + self.add_input("member" + str(k) + ":displacement", 0.0, units="m**3") + self.add_input("member" + str(k) + ":center_of_buoyancy", np.zeros(3), units="m") + self.add_input("member" + str(k) + ":center_of_mass", np.zeros(3), units="m") + self.add_input("member" + str(k) + ":total_mass", 0.0, units="kg") + self.add_input("member" + str(k) + ":total_cost", 0.0, units="USD") + self.add_input("member" + str(k) + ":Awater", 0.0, units="m**2") + self.add_input("member" + str(k) + ":Iwater", 0.0, units="m**4") + self.add_input("member" + str(k) + ":added_mass", np.zeros(6), units="kg") + self.add_input("member" + str(k) + ":transition_node", NULL * np.ones(3), units="m") + + self.add_output("transition_node", np.zeros(3), units="m") + self.add_output("platform_nodes", NULL * np.ones((NNODES_MAX, 3)), units="m") + self.add_output("platform_Fnode", NULL * np.ones((NNODES_MAX, 3)), units="N") + self.add_output("platform_Rnode", NULL * np.ones(NNODES_MAX), units="m") + self.add_output("platform_elem_n1", NULL * np.ones(NELEM_MAX, dtype=np.int_)) + self.add_output("platform_elem_n2", NULL * np.ones(NELEM_MAX, dtype=np.int_)) + self.add_output("platform_elem_D", NULL * np.ones(NELEM_MAX), units="m") + self.add_output("platform_elem_t", NULL * np.ones(NELEM_MAX), units="m") + self.add_output("platform_elem_A", NULL * np.ones(NELEM_MAX), units="m**2") + self.add_output("platform_elem_Asx", NULL * np.ones(NELEM_MAX), units="m**2") + self.add_output("platform_elem_Asy", NULL * np.ones(NELEM_MAX), units="m**2") + self.add_output("platform_elem_Ixx", NULL * np.ones(NELEM_MAX), units="kg*m**2") + self.add_output("platform_elem_Iyy", NULL * np.ones(NELEM_MAX), units="kg*m**2") + self.add_output("platform_elem_Izz", NULL * np.ones(NELEM_MAX), units="kg*m**2") + self.add_output("platform_elem_rho", NULL * np.ones(NELEM_MAX), units="kg/m**3") + self.add_output("platform_elem_E", NULL * np.ones(NELEM_MAX), units="Pa") + self.add_output("platform_elem_G", NULL * np.ones(NELEM_MAX), units="Pa") + self.add_output("platform_displacement", 0.0, units="m**3") + self.add_output("platform_center_of_buoyancy", np.zeros(3), units="m") + self.add_output("platform_center_of_mass", np.zeros(3), units="m") + self.add_output("platform_mass", 0.0, units="kg") + self.add_output("platform_cost", 0.0, units="USD") + self.add_output("platform_Awater", 0.0, units="m**2") + self.add_output("platform_Iwater", 0.0, units="m**4") + self.add_output("platform_added_mass", np.zeros(6), units="kg") + + self.node_mem2glob = {} + # self.node_glob2mem = {} + + def compute(self, inputs, outputs): + # This shouldn't change during an optimization, so save some time? + if len(self.node_mem2glob) == 0: + self.set_connectivity(inputs, outputs) + + self.set_node_props(inputs, outputs) + self.set_element_props(inputs, outputs) + + def set_connectivity(self, inputs, outputs): + # Load in number of members + opt = self.options["options"] + n_member = opt["floating"]["members"]["n_members"] + + # Initialize running lists across all members + nodes_temp = np.empty((0, 3)) + elem_n1 = np.array([], dtype=np.int_) + elem_n2 = np.array([], dtype=np.int_) + node_trans = None + + # Look over members and grab all nodes and internal connections + for k in range(n_member): + inode_xyz = inputs["member" + str(k) + ":nodes_xyz"] + inodes = np.where(inode_xyz[:, 0] == NULL)[0][0] + inode_xyz = inode_xyz[:inodes, :] + inode_range = np.arange(inodes - 1) + + n = nodes_temp.shape[0] + for ii in range(inodes): + self.node_mem2glob[(k, ii)] = n + ii + + elem_n1 = np.append(elem_n1, n + inode_range) + elem_n2 = np.append(elem_n2, n + inode_range + 1) + nodes_temp = np.append(nodes_temp, inode_xyz, axis=0) + + itrans = inputs["member" + str(k) + ":transition_node"] + if np.all(itrans != NULL): + if node_trans is None: + node_trans = itrans + else: + raise ValueError("More than one transition node is flagged") + + # Reveal connectivity by using mapping to unique node positions + nodes, idx, inv = np.unique(nodes_temp.round(4), axis=0, return_index=True, return_inverse=True) + nnode = nodes.shape[0] + outputs["platform_nodes"] = NULL * np.ones((NNODES_MAX, 3)) + outputs["platform_nodes"][:nnode, :] = nodes + + # Set transition node + if node_trans is None: + centroid = nodes[:, :2].mean(axis=0) + zmax = nodes[:, 2].max() + itrans = util.closest_node(nodes, np.r_[centroid, zmax]) + outputs["transition_node"] = nodes[itrans, :] + else: + outputs["transition_node"] = node_trans + + # Use mapping to set references to node joints + nelem = elem_n1.size + outputs["platform_elem_n1"] = NULL * np.ones(NELEM_MAX, dtype=np.int_) + outputs["platform_elem_n2"] = NULL * np.ones(NELEM_MAX, dtype=np.int_) + outputs["platform_elem_n1"][:nelem] = inv[elem_n1] + outputs["platform_elem_n2"][:nelem] = inv[elem_n2] + + # Update global 2 member mappings + for k in self.node_mem2glob.keys(): + self.node_mem2glob[k] = inv[self.node_mem2glob[k]] + + def set_node_props(self, inputs, outputs): + # Load in number of members + opt = self.options["options"] + n_member = opt["floating"]["members"]["n_members"] + + # Number of valid nodes + nnode = np.where(outputs["platform_nodes"][:, 0] == NULL)[0][0] + + # Find greatest radius of all members at node intersections + Rnode = np.zeros(nnode) + for k in range(n_member): + irnode = inputs["member" + str(k) + ":nodes_r"] + n = np.where(irnode == NULL)[0][0] + for ii in range(n): + iglob = self.node_mem2glob[(k, ii)] + Rnode[iglob] = np.array([Rnode[iglob], irnode[ii]]).max() + + # Find forces on nodes + Fnode = np.zeros((nnode, 3)) + for k in range(n_member): + icb = int(inputs["member" + str(k) + ":idx_cb"]) + iglob = self.node_mem2glob[(k, icb)] + Fnode[iglob, 2] += inputs["member" + str(k) + ":buoyancy_force"] + + # Store outputs + outputs["platform_Rnode"] = NULL * np.ones(NNODES_MAX) + outputs["platform_Rnode"][:nnode] = Rnode + outputs["platform_Fnode"] = NULL * np.ones((NNODES_MAX, 3)) + outputs["platform_Fnode"][:nnode, :] = Fnode + + def set_element_props(self, inputs, outputs): + # Load in number of members + opt = self.options["options"] + n_member = opt["floating"]["members"]["n_members"] + + # Initialize running lists across all members + elem_D = np.array([]) + elem_t = np.array([]) + elem_A = np.array([]) + elem_Asx = np.array([]) + elem_Asy = np.array([]) + elem_Ixx = np.array([]) + elem_Iyy = np.array([]) + elem_Izz = np.array([]) + elem_rho = np.array([]) + elem_E = np.array([]) + elem_G = np.array([]) + + mass = 0.0 + cost = 0.0 + volume = 0.0 + Awater = 0.0 + Iwater = 0.0 + m_added = np.zeros(6) + cg_plat = np.zeros(3) + cb_plat = np.zeros(3) + + # Append all member data + for k in range(n_member): + n = np.where(inputs["member" + str(k) + ":section_A"] == NULL)[0][0] + elem_D = np.append(elem_D, inputs["member" + str(k) + ":section_D"][:n]) + elem_t = np.append(elem_t, inputs["member" + str(k) + ":section_t"][:n]) + elem_A = np.append(elem_A, inputs["member" + str(k) + ":section_A"][:n]) + elem_Asx = np.append(elem_Asx, inputs["member" + str(k) + ":section_Asx"][:n]) + elem_Asy = np.append(elem_Asy, inputs["member" + str(k) + ":section_Asy"][:n]) + elem_Ixx = np.append(elem_Ixx, inputs["member" + str(k) + ":section_Ixx"][:n]) + elem_Iyy = np.append(elem_Iyy, inputs["member" + str(k) + ":section_Iyy"][:n]) + elem_Izz = np.append(elem_Izz, inputs["member" + str(k) + ":section_Izz"][:n]) + elem_rho = np.append(elem_rho, inputs["member" + str(k) + ":section_rho"][:n]) + elem_E = np.append(elem_E, inputs["member" + str(k) + ":section_E"][:n]) + elem_G = np.append(elem_G, inputs["member" + str(k) + ":section_G"][:n]) + + # Mass, volume, cost tallies + imass = inputs["member" + str(k) + ":total_mass"] + ivol = inputs["member" + str(k) + ":displacement"] + + mass += imass + volume += ivol + cost += inputs["member" + str(k) + ":total_cost"] + Awater += inputs["member" + str(k) + ":Awater"] + Iwater += inputs["member" + str(k) + ":Iwater"] + m_added += inputs["member" + str(k) + ":added_mass"] + + # Center of mass / buoyancy tallies + cg_plat += imass * inputs["member" + str(k) + ":center_of_mass"] + cb_plat += ivol * inputs["member" + str(k) + ":center_of_buoyancy"] + + # Store outputs + nelem = elem_A.size + outputs["platform_elem_D"] = NULL * np.ones(NELEM_MAX) + outputs["platform_elem_t"] = NULL * np.ones(NELEM_MAX) + outputs["platform_elem_A"] = NULL * np.ones(NELEM_MAX) + outputs["platform_elem_Asx"] = NULL * np.ones(NELEM_MAX) + outputs["platform_elem_Asy"] = NULL * np.ones(NELEM_MAX) + outputs["platform_elem_Ixx"] = NULL * np.ones(NELEM_MAX) + outputs["platform_elem_Iyy"] = NULL * np.ones(NELEM_MAX) + outputs["platform_elem_Izz"] = NULL * np.ones(NELEM_MAX) + outputs["platform_elem_rho"] = NULL * np.ones(NELEM_MAX) + outputs["platform_elem_E"] = NULL * np.ones(NELEM_MAX) + outputs["platform_elem_G"] = NULL * np.ones(NELEM_MAX) + + outputs["platform_elem_D"][:nelem] = elem_D + outputs["platform_elem_t"][:nelem] = elem_t + outputs["platform_elem_A"][:nelem] = elem_A + outputs["platform_elem_Asx"][:nelem] = elem_Asx + outputs["platform_elem_Asy"][:nelem] = elem_Asy + outputs["platform_elem_Ixx"][:nelem] = elem_Ixx + outputs["platform_elem_Iyy"][:nelem] = elem_Iyy + outputs["platform_elem_Izz"][:nelem] = elem_Izz + outputs["platform_elem_rho"][:nelem] = elem_rho + outputs["platform_elem_E"][:nelem] = elem_E + outputs["platform_elem_G"][:nelem] = elem_G + + outputs["platform_mass"] = mass + outputs["platform_cost"] = cost + outputs["platform_displacement"] = volume + outputs["platform_center_of_mass"] = cg_plat / mass + outputs["platform_center_of_buoyancy"] = cb_plat / volume + outputs["platform_Awater"] = Awater + outputs["platform_Iwater"] = Iwater + outputs["platform_added_mass"] = m_added + + +class TowerPreMember(om.ExplicitComponent): + def setup(self): + self.add_input("transition_node", np.zeros(3), units="m") + self.add_input("hub_height", 0.0, units="m") + self.add_input("distance_tt_hub", 0.0, units="m") + self.add_output("hub_node", np.zeros(3), units="m") + + def compute(self, inputs, outputs): + transition_node = inputs["transition_node"] + hub_node = transition_node + hub_node[2] = float(inputs["hub_height"] - inputs["distance_tt_hub"]) + outputs["hub_node"] = hub_node + + +class PlatformTowerFrame(om.ExplicitComponent): + def setup(self): + + self.add_input("platform_nodes", NULL * np.ones((NNODES_MAX, 3)), units="m") + self.add_input("platform_Fnode", NULL * np.ones((NNODES_MAX, 3)), units="N") + self.add_input("platform_Rnode", NULL * np.ones(NNODES_MAX), units="m") + self.add_input("platform_elem_n1", NULL * np.ones(NELEM_MAX, dtype=np.int_)) + self.add_input("platform_elem_n2", NULL * np.ones(NELEM_MAX, dtype=np.int_)) + self.add_input("platform_elem_D", NULL * np.ones(NELEM_MAX), units="m") + self.add_input("platform_elem_t", NULL * np.ones(NELEM_MAX), units="m") + self.add_input("platform_elem_A", NULL * np.ones(NELEM_MAX), units="m**2") + self.add_input("platform_elem_Asx", NULL * np.ones(NELEM_MAX), units="m**2") + self.add_input("platform_elem_Asy", NULL * np.ones(NELEM_MAX), units="m**2") + self.add_input("platform_elem_Ixx", NULL * np.ones(NELEM_MAX), units="kg*m**2") + self.add_input("platform_elem_Iyy", NULL * np.ones(NELEM_MAX), units="kg*m**2") + self.add_input("platform_elem_Izz", NULL * np.ones(NELEM_MAX), units="kg*m**2") + self.add_input("platform_elem_rho", NULL * np.ones(NELEM_MAX), units="kg/m**3") + self.add_input("platform_elem_E", NULL * np.ones(NELEM_MAX), units="Pa") + self.add_input("platform_elem_G", NULL * np.ones(NELEM_MAX), units="Pa") + self.add_input("platform_center_of_mass", np.zeros(3), units="m") + self.add_input("platform_mass", 0.0, units="kg") + self.add_input("platform_displacement", 0.0, units="m**3") + + self.add_input("tower_nodes", NULL * np.ones((MEMMAX, 3)), units="m") + self.add_output("tower_Fnode", copy_shape="tower_nodes", units="N") + self.add_input("tower_Rnode", NULL * np.ones(MEMMAX), units="m") + self.add_output("tower_elem_n1", copy_shape="tower_elem_A") + self.add_output("tower_elem_n2", copy_shape="tower_elem_A") + self.add_input("tower_elem_D", NULL * np.ones(MEMMAX), units="m") + self.add_input("tower_elem_t", NULL * np.ones(MEMMAX), units="m") + self.add_input("tower_elem_A", NULL * np.ones(MEMMAX), units="m**2") + self.add_input("tower_elem_Asx", NULL * np.ones(MEMMAX), units="m**2") + self.add_input("tower_elem_Asy", NULL * np.ones(MEMMAX), units="m**2") + self.add_input("tower_elem_Ixx", NULL * np.ones(MEMMAX), units="kg*m**2") + self.add_input("tower_elem_Iyy", NULL * np.ones(MEMMAX), units="kg*m**2") + self.add_input("tower_elem_Izz", NULL * np.ones(MEMMAX), units="kg*m**2") + self.add_input("tower_elem_rho", NULL * np.ones(MEMMAX), units="kg/m**3") + self.add_input("tower_elem_E", NULL * np.ones(MEMMAX), units="Pa") + self.add_input("tower_elem_G", NULL * np.ones(MEMMAX), units="Pa") + self.add_input("tower_center_of_mass", np.zeros(3), units="m") + self.add_input("tower_mass", 0.0, units="kg") + + self.add_input("rho_water", 0.0, units="kg/m**3") + self.add_input("hub_node", np.zeros(3), units="m") + self.add_input("transition_node", np.zeros(3), units="m") + self.add_input("transition_piece_mass", 0.0, units="kg") + self.add_input("rna_mass", 0.0, units="kg") + self.add_input("rna_cg", np.zeros(3), units="m") + + self.add_output("system_nodes", NULL * np.ones((NNODES_MAX, 3)), units="m") + self.add_output("system_Fnode", NULL * np.ones((NNODES_MAX, 3)), units="N") + self.add_output("system_Rnode", NULL * np.ones(NNODES_MAX), units="m") + self.add_output("system_elem_n1", NULL * np.ones(NELEM_MAX, dtype=np.int_)) + self.add_output("system_elem_n2", NULL * np.ones(NELEM_MAX, dtype=np.int_)) + self.add_output("system_elem_D", NULL * np.ones(NELEM_MAX), units="m") + self.add_output("system_elem_t", NULL * np.ones(NELEM_MAX), units="m") + self.add_output("system_elem_A", NULL * np.ones(NELEM_MAX), units="m**2") + self.add_output("system_elem_Asx", NULL * np.ones(NELEM_MAX), units="m**2") + self.add_output("system_elem_Asy", NULL * np.ones(NELEM_MAX), units="m**2") + self.add_output("system_elem_Ixx", NULL * np.ones(NELEM_MAX), units="kg*m**2") + self.add_output("system_elem_Iyy", NULL * np.ones(NELEM_MAX), units="kg*m**2") + self.add_output("system_elem_Izz", NULL * np.ones(NELEM_MAX), units="kg*m**2") + self.add_output("system_elem_rho", NULL * np.ones(NELEM_MAX), units="kg/m**3") + self.add_output("system_elem_E", NULL * np.ones(NELEM_MAX), units="Pa") + self.add_output("system_elem_G", NULL * np.ones(NELEM_MAX), units="Pa") + self.add_output("system_center_of_mass", np.zeros(3), units="m") + self.add_output("system_mass", 0.0, units="kg") + self.add_output("variable_ballast_mass", 0.0, units="kg") + self.add_output("transition_piece_I", np.zeros(6), units="kg*m**2") + + def compute(self, inputs, outputs): + # Combine nodes + node_platform = inputs["platform_nodes"] + node_tower = inputs["tower_nodes"] + + nnode_platform = np.where(node_platform[:, 0] == NULL)[0][0] + nnode_tower = np.where(node_tower[:, 0] == NULL)[0][0] + nnode_system = nnode_platform + np.maximum(1, nnode_tower) - 1 + + nelem_platform = np.where(inputs["platform_elem_A"] == NULL)[0][0] + nelem_tower = np.where(inputs["tower_elem_A"] == NULL)[0][0] + nelem_system = nelem_platform + nelem_tower + + # Combine elements indices and have tower base node point to platform transition node + outputs["tower_Fnode"] = np.zeros(node_tower.shape) + outputs["tower_elem_n1"] = NULL * np.ones(MEMMAX, dtype=np.int_) + outputs["tower_elem_n2"] = NULL * np.ones(MEMMAX, dtype=np.int_) + tower_n1 = np.arange(nelem_tower, dtype=np.int_) + tower_n2 = np.arange(nelem_tower, dtype=np.int_) + 1 + outputs["tower_elem_n1"][:nelem_tower] = tower_n1.copy() + outputs["tower_elem_n2"][:nelem_tower] = tower_n2.copy() + itrans_platform = util.closest_node(node_platform[:nnode_platform, :], inputs["transition_node"]) + tower_n1 += nnode_platform - 1 + tower_n2 += nnode_platform - 1 + tower_n1[0] = itrans_platform + + # Store all outputs + outputs["system_nodes"] = NULL * np.ones((NNODES_MAX, 3)) + outputs["system_Fnode"] = NULL * np.ones((NNODES_MAX, 3)) + outputs["system_Rnode"] = NULL * np.ones(NNODES_MAX) + outputs["system_elem_n1"] = NULL * np.ones(NELEM_MAX, dtype=np.int_) + outputs["system_elem_n2"] = NULL * np.ones(NELEM_MAX, dtype=np.int_) + + outputs["system_nodes"][:nnode_system, :] = np.vstack( + (node_platform[:nnode_platform, :], node_tower[1:nnode_tower, :]) + ) + outputs["system_Fnode"][:nnode_system, :] = np.vstack( + (inputs["platform_Fnode"][:nnode_platform, :], outputs["tower_Fnode"][1:nnode_tower, :]) + ) + outputs["system_Rnode"][:nnode_system] = np.r_[ + inputs["platform_Rnode"][:nnode_platform], inputs["tower_Rnode"][1:nnode_tower] + ] + + outputs["system_elem_n1"][:nelem_system] = np.r_[ + inputs["platform_elem_n1"][:nelem_platform], + tower_n1, + ] + outputs["system_elem_n2"][:nelem_system] = np.r_[ + inputs["platform_elem_n2"][:nelem_platform], + tower_n2, + ] + + for var in [ + "elem_D", + "elem_t", + "elem_A", + "elem_Asx", + "elem_Asy", + "elem_Ixx", + "elem_Iyy", + "elem_Izz", + "elem_rho", + "elem_E", + "elem_G", + ]: + outputs["system_" + var] = NULL * np.ones(NELEM_MAX) + outputs["system_" + var][:nelem_system] = np.r_[ + inputs["platform_" + var][:nelem_platform], inputs["tower_" + var][:nelem_tower] + ] + + # Mass summaries + outputs["system_mass"] = ( + inputs["platform_mass"] + inputs["tower_mass"] + inputs["rna_mass"] + inputs["transition_piece_mass"] + ) + outputs["system_center_of_mass"] = ( + inputs["platform_mass"] * inputs["platform_center_of_mass"] + + inputs["tower_mass"] * inputs["tower_center_of_mass"] + + inputs["rna_mass"] * (inputs["rna_cg"] + inputs["hub_node"]) + + inputs["transition_piece_mass"] * inputs["transition_node"] + ) / outputs["system_mass"] + + outputs["variable_ballast_mass"] = ( + inputs["platform_displacement"] * inputs["rho_water"] - outputs["system_mass"] + ) + + m_trans = float(inputs["transition_piece_mass"]) + r_trans = inputs["platform_Rnode"][itrans_platform] + I_trans = m_trans * r_trans ** 2.0 * np.r_[0.5, 0.5, 1.0, np.zeros(3)] + outputs["transition_piece_I"] = I_trans + + +class FrameAnalysis(om.ExplicitComponent): + def initialize(self): + self.options.declare("options") + + def setup(self): + opt = self.options["options"] + n_attach = opt["mooring"]["n_attach"] + + self.add_input("platform_mass", 0.0, units="kg") + self.add_input("platform_center_of_mass", np.zeros(3), units="m") + + self.add_input("tower_nodes", NULL * np.ones((MEMMAX, 3)), units="m") + self.add_input("tower_Fnode", NULL * np.ones((MEMMAX, 3)), units="N") + self.add_input("tower_Rnode", NULL * np.ones(MEMMAX), units="m") + self.add_input("tower_elem_n1", NULL * np.ones(MEMMAX)) + self.add_input("tower_elem_n2", NULL * np.ones(MEMMAX)) + self.add_input("tower_elem_D", NULL * np.ones(MEMMAX), units="m") + self.add_input("tower_elem_t", NULL * np.ones(MEMMAX), units="m") + self.add_input("tower_elem_A", NULL * np.ones(MEMMAX), units="m**2") + self.add_input("tower_elem_Asx", NULL * np.ones(MEMMAX), units="m**2") + self.add_input("tower_elem_Asy", NULL * np.ones(MEMMAX), units="m**2") + self.add_input("tower_elem_Ixx", NULL * np.ones(MEMMAX), units="kg*m**2") + self.add_input("tower_elem_Iyy", NULL * np.ones(MEMMAX), units="kg*m**2") + self.add_input("tower_elem_Izz", NULL * np.ones(MEMMAX), units="kg*m**2") + self.add_input("tower_elem_rho", NULL * np.ones(MEMMAX), units="kg/m**3") + self.add_input("tower_elem_E", NULL * np.ones(MEMMAX), units="Pa") + self.add_input("tower_elem_G", NULL * np.ones(MEMMAX), units="Pa") + + self.add_input("system_nodes", NULL * np.ones((NNODES_MAX, 3)), units="m") + self.add_input("system_Fnode", NULL * np.ones((NNODES_MAX, 3)), units="N") + self.add_input("system_Rnode", NULL * np.ones(NNODES_MAX), units="m") + self.add_input("system_elem_n1", NULL * np.ones(NELEM_MAX, dtype=np.int_)) + self.add_input("system_elem_n2", NULL * np.ones(NELEM_MAX, dtype=np.int_)) + self.add_input("system_elem_D", NULL * np.ones(NELEM_MAX), units="m") + self.add_input("system_elem_t", NULL * np.ones(NELEM_MAX), units="m") + self.add_input("system_elem_A", NULL * np.ones(NELEM_MAX), units="m**2") + self.add_input("system_elem_Asx", NULL * np.ones(NELEM_MAX), units="m**2") + self.add_input("system_elem_Asy", NULL * np.ones(NELEM_MAX), units="m**2") + self.add_input("system_elem_Ixx", NULL * np.ones(NELEM_MAX), units="kg*m**2") + self.add_input("system_elem_Iyy", NULL * np.ones(NELEM_MAX), units="kg*m**2") + self.add_input("system_elem_Izz", NULL * np.ones(NELEM_MAX), units="kg*m**2") + self.add_input("system_elem_rho", NULL * np.ones(NELEM_MAX), units="kg/m**3") + self.add_input("system_elem_E", NULL * np.ones(NELEM_MAX), units="Pa") + self.add_input("system_elem_G", NULL * np.ones(NELEM_MAX), units="Pa") + + self.add_input("transition_node", np.zeros(3), units="m") + self.add_input("transition_piece_mass", 0.0, units="kg") + self.add_input("transition_piece_I", np.zeros(6), units="kg*m**2") + self.add_input("rna_mass", 0.0, units="kg") + self.add_input("rna_cg", np.zeros(3), units="m") + self.add_input("rna_F", np.zeros(3), units="N") + self.add_input("rna_M", np.zeros(3), units="N*m") + self.add_input("rna_I", np.zeros(6), units="kg*m**2") + self.add_input("mooring_neutral_load", np.zeros((n_attach, 3)), units="N") + self.add_input("mooring_fairlead_joints", np.zeros((n_attach, 3)), units="m") + + NFREQ2 = int(NFREQ / 2) + self.add_output("tower_freqs", val=np.zeros(NFREQ), units="Hz") + self.add_output("tower_fore_aft_modes", val=np.zeros((NFREQ2, 5))) + self.add_output("tower_side_side_modes", val=np.zeros((NFREQ2, 5))) + self.add_output("tower_fore_aft_freqs", val=np.zeros(NFREQ2)) + self.add_output("tower_side_side_freqs", val=np.zeros(NFREQ2)) + + def compute(self, inputs, outputs): + + # Unpack variables + opt = self.options["options"] + n_attach = opt["mooring"]["n_attach"] + m_rna = float(inputs["rna_mass"]) + cg_rna = inputs["rna_cg"] + I_rna = inputs["rna_I"] + I_trans = inputs["transition_piece_I"] + + fairlead_joints = inputs["mooring_fairlead_joints"] + mooringF = inputs["mooring_neutral_load"] + + # Create frame3dd instance: nodes, elements, reactions, and options + for frame in ["tower", "system"]: + nodes = inputs[frame + "_nodes"] + nnode = np.where(nodes[:, 0] == NULL)[0][0] + nodes = nodes[:nnode, :] + rnode = np.zeros(nnode) # inputs[frame + "_Rnode"][:nnode] + Fnode = inputs[frame + "_Fnode"][:nnode, :] + Mnode = np.zeros((nnode, 3)) + ihub = np.argmax(nodes[:, 2]) - 1 + itrans = util.closest_node(nodes, inputs["transition_node"]) + + N1 = np.int_(inputs[frame + "_elem_n1"]) + nelem = np.where(N1 == NULL)[0][0] + N1 = N1[:nelem] + N2 = np.int_(inputs[frame + "_elem_n2"][:nelem]) + A = inputs[frame + "_elem_A"][:nelem] + Asx = inputs[frame + "_elem_Asx"][:nelem] + Asy = inputs[frame + "_elem_Asy"][:nelem] + Ixx = inputs[frame + "_elem_Ixx"][:nelem] + Iyy = inputs[frame + "_elem_Iyy"][:nelem] + Izz = inputs[frame + "_elem_Izz"][:nelem] + rho = inputs[frame + "_elem_rho"][:nelem] + E = inputs[frame + "_elem_E"][:nelem] + G = inputs[frame + "_elem_G"][:nelem] + roll = np.zeros(nelem) + + inodes = np.arange(nnode) + 1 + node_obj = pyframe3dd.NodeData(inodes, nodes[:, 0], nodes[:, 1], nodes[:, 2], rnode) + + ielem = np.arange(nelem) + 1 + elem_obj = pyframe3dd.ElementData(ielem, N1 + 1, N2 + 1, A, Asx, Asy, Izz, Ixx, Iyy, E, G, roll, rho) + + # TODO: Hydro_K + Mooring_K for tower (system too?) + rid = np.array([itrans]) # np.array([np.argmin(nodes[:, 2])]) + Rx = Ry = Rz = Rxx = Ryy = Rzz = np.array([RIGID]) + react_obj = pyframe3dd.ReactionData(rid + 1, Rx, Ry, Rz, Rxx, Ryy, Rzz, rigid=RIGID) + + frame3dd_opt = opt["WISDEM"]["FloatingSE"]["frame3dd"] + opt_obj = pyframe3dd.Options(frame3dd_opt["shear"], frame3dd_opt["geom"], -1.0) + + myframe = pyframe3dd.Frame(node_obj, react_obj, elem_obj, opt_obj) + + # Added mass + m_trans = float(inputs["transition_piece_mass"]) + if frame == "tower": + # TODO: Added mass and stiffness + m_trans += float(inputs["platform_mass"]) + cg_trans = inputs["transition_node"] - inputs["platform_center_of_mass"] + else: + cg_trans = np.zeros(3) + add_gravity = True + mID = np.array([itrans, ihub], dtype=np.int_).flatten() + m_add = np.array([m_trans, m_rna]).flatten() + I_add = np.c_[I_trans, I_rna] + cg_add = np.c_[cg_trans, cg_rna] + myframe.changeExtraNodeMass( + mID + 1, + m_add, + I_add[0, :], + I_add[1, :], + I_add[2, :], + I_add[3, :], + I_add[4, :], + I_add[5, :], + cg_add[0, :], + cg_add[1, :], + cg_add[2, :], + add_gravity, + ) + + # Dynamics + if frame == "tower" and frame3dd_opt["modal"]: + Mmethod = 1 + lump = 0 + shift = 0.0 + myframe.enableDynamics(2 * NFREQ, Mmethod, lump, frame3dd_opt["tol"], shift) + + # Initialize loading with gravity, mooring line forces, and buoyancy (already in nodal forces) + gx = gy = 0.0 + gz = -gravity + load_obj = pyframe3dd.StaticLoadCase(gx, gy, gz) + + if frame == "system": + for k in range(n_attach): + ind = util.closest_node(nodes, fairlead_joints[k, :]) + Fnode[ind, :] += mooringF[k, :] + Fnode[ihub, :] += inputs["rna_F"] + Mnode[ihub, :] += inputs["rna_M"] + nF = np.where(np.abs(Fnode).sum(axis=1) > 0.0)[0] + load_obj.changePointLoads( + nF + 1, Fnode[nF, 0], Fnode[nF, 1], Fnode[nF, 2], Mnode[nF, 0], Mnode[nF, 1], Mnode[nF, 2] + ) + + # Add the load case and run + myframe.addLoadCase(load_obj) + # myframe.write('temp.3dd') + displacements, forces, reactions, internalForces, mass, modal = myframe.run() + + # natural frequncies + if frame == "tower" and frame3dd_opt["modal"]: + outputs[frame + "_freqs"] = modal.freq[:NFREQ] + + # Get all mode shapes in batch + NFREQ2 = int(NFREQ / 2) + freq_x, freq_y, mshapes_x, mshapes_y = util.get_xy_mode_shapes( + nodes[:, 2], modal.freq, modal.xdsp, modal.ydsp, modal.zdsp, modal.xmpf, modal.ympf, modal.zmpf + ) + outputs[frame + "_fore_aft_freqs"] = freq_x[:NFREQ2] + outputs[frame + "_side_side_freqs"] = freq_y[:NFREQ2] + outputs[frame + "_fore_aft_modes"] = mshapes_x[:NFREQ2, :] + outputs[frame + "_side_side_modes"] = mshapes_y[:NFREQ2, :] + + # Determine forces + F_sum = -1.0 * np.array([reactions.Fx.sum(), reactions.Fy.sum(), reactions.Fz.sum()]) + M_sum = -1.0 * np.array([reactions.Mxx.sum(), reactions.Myy.sum(), reactions.Mzz.sum()]) + L = np.sqrt(np.sum((nodes[N2, :] - nodes[N1, :]) ** 2, axis=1)) + + +class FloatingFrame(om.Group): + def initialize(self): + self.options.declare("modeling_options") + + def setup(self): + opt = self.options["modeling_options"] + + self.add_subsystem("plat", PlatformFrame(options=opt), promotes=["*"]) + self.add_subsystem("pre", TowerPreMember(), promotes=["*"]) + + prom = [ + "E_mat", + "G_mat", + "sigma_y_mat", + "rho_mat", + "rho_water", + "unit_cost_mat", + "material_names", + "painting_cost_rate", + "labor_cost_rate", + ] + prom += [ + ("nodes_xyz", "tower_nodes"), + ("nodes_r", "tower_Rnode"), + ("total_mass", "tower_mass"), + ("total_cost", "tower_cost"), + ("center_of_mass", "tower_center_of_mass"), + ("joint1", "transition_node"), + ("joint2", "hub_node"), + ] + for var in ["D", "t", "A", "Asx", "Asy", "rho", "Ixx", "Iyy", "Izz", "E", "G"]: + prom += [("section_" + var, "tower_elem_" + var)] + self.add_subsystem( + "tower", + Member(column_options=opt["floating"]["tower"], idx=0, n_mat=opt["materials"]["n_mat"]), + promotes=prom, + ) + self.add_subsystem("mux", PlatformTowerFrame(), promotes=["*"]) + self.add_subsystem("frame", FrameAnalysis(options=opt), promotes=["*"]) diff --git a/wisdem/floatingse/loading.py b/wisdem/floatingse/loading.py deleted file mode 100644 index 18f83ce1a..000000000 --- a/wisdem/floatingse/loading.py +++ /dev/null @@ -1,1723 +0,0 @@ -import numpy as np -import wisdem.pyframe3dd.pyframe3dd as pyframe3dd -import openmdao.api as om -from wisdem.commonse.utilities import nodal2sectional - -from wisdem.commonse import gravity, eps, NFREQ -import wisdem.commonse.utilization_constraints as util -from wisdem.commonse.utilities import get_modal_coefficients -import wisdem.commonse.manufacturing as manufacture -from wisdem.commonse.wind_wave_drag import CylinderWindDrag -from wisdem.commonse.environment import PowerWind -from wisdem.commonse.vertical_cylinder import get_nfull, RIGID -from wisdem.commonse.cross_sections import Tube -from .map_mooring import NLINES_MAX - - -def find_nearest(array, value): - return (np.abs(array - value)).argmin() - - -def ghostNodes(x1, x2, r1, r2): - dx = x2 - x1 - L = np.sqrt(np.sum(dx ** 2)) - dr1 = (r1 / L) * dx + x1 - dr2 = (1.0 - r2 / L) * dx + x1 - return dr1, dr2 - - -class FloatingFrame(om.ExplicitComponent): - """ - Component for semisubmersible pontoon / truss structure for floating offshore wind turbines. - Should be tightly coupled with Semi and Mooring classes for full system representation. - - Parameters - ---------- - rho_water : float, [kg/m**3] - density of water - hsig_wave : float, [m] - wave significant height - main_z_full : numpy array[n_full_main], [m] - z-coordinates of section nodes (length = nsection+1) - main_d_full : numpy array[n_full_main], [m] - outer radius at each section node bottom to top (length = nsection + 1) - main_t_full : numpy array[n_full_main-1], [m] - shell wall thickness at each section node bottom to top (length = nsection + 1) - main_rho : numpy array[n_full_main-1], [kg/m**3] - density of material - main_E : numpy array[n_full_main-1], [Pa] - Modulus of elasticity (Youngs) of material - main_G : numpy array[n_full_main-1], [Pa] - Shear modulus of material - main_sigma_y : numpy array[n_full_main-1], [Pa] - yield stress of material - main_mass : numpy array[n_full_main-1], [kg] - mass of main column by section - main_displaced_volume : numpy array[n_full_main-1], [m**3] - column volume of water displaced by section - main_hydrostatic_force : numpy array[n_full_main-1], [N] - Net z-force of hydrostatic pressure by section - main_center_of_buoyancy : float, [m] - z-position of center of column buoyancy force - main_center_of_mass : float, [m] - z-position of center of column mass - main_Px : numpy array[n_full_main], [N/m] - force per unit length in x-direction on main - main_Py : numpy array[n_full_main], [N/m] - force per unit length in y-direction on main - main_Pz : numpy array[n_full_main], [N/m] - force per unit length in z-direction on main - main_qdyn : numpy array[n_full_main], [N/m**2] - dynamic pressure on main - main_pontoon_attach_upper : float - Fraction of main column for upper truss attachment on main column - main_pontoon_attach_lower : float - Fraction of main column lower truss attachment on main column - offset_z_full : numpy array[n_full_off], [m] - z-coordinates of section nodes (length = nsection+1) - offset_d_full : numpy array[n_full_off], [m] - outer radius at each section node bottom to top (length = nsection + 1) - offset_t_full : numpy array[n_full_off-1], [m] - shell wall thickness at each section node bottom to top (length = nsection + 1) - offset_rho : numpy array[n_full_off-1], [kg/m**3] - density of material - offset_E : numpy array[n_full_off-1], [Pa] - Modulus of elasticity (Youngs) of material - offset_G : numpy array[n_full_off-1], [Pa] - Shear modulus of material - offset_sigma_y : numpy array[n_full_off-1], [Pa] - yield stress of material - offset_mass : numpy array[n_full_off-1], [kg] - mass of offset column by section - offset_displaced_volume : numpy array[n_full_off-1], [m**3] - column volume of water displaced by section - offset_hydrostatic_force : numpy array[n_full_off-1], [N] - Net z-force of hydrostatic pressure by section - offset_center_of_buoyancy : float, [m] - z-position of center of column buoyancy force - offset_center_of_mass : float, [m] - z-position of center of column mass - offset_Px : numpy array[n_full_off], [N/m] - force per unit length in x-direction on offset - offset_Py : numpy array[n_full_off], [N/m] - force per unit length in y-direction on offset - offset_Pz : numpy array[n_full_off], [N/m] - force per unit length in z-direction on offset - offset_qdyn : numpy array[n_full_off], [N/m**2] - dynamic pressure on offset - tower_z_full : numpy array[n_full_tow], [m] - z-coordinates of section nodes (length = nsection+1) - tower_d_full : numpy array[n_full_tow], [m] - outer radius at each section node bottom to top (length = nsection + 1) - tower_t_full : numpy array[n_full_tow-1], [m] - shell wall thickness at each section node bottom to top (length = nsection + 1) - tower_rho : numpy array[n_full_tow-1], [kg/m**3] - density of material - tower_E : numpy array[n_full_tow-1], [Pa] - Modulus of elasticity (Youngs) of material - tower_G : numpy array[n_full_tow-1], [Pa] - Shear modulus of material - tower_sigma_y : numpy array[n_full_tow-1], [Pa] - yield stress of material - tower_mass_section : numpy array[n_full_tow-1], [kg] - mass of tower column by section - tower_center_of_mass : float, [m] - z-position of center of tower mass - tower_Px : numpy array[n_full_tow], [N/m] - force per unit length in x-direction on tower - tower_Py : numpy array[n_full_tow], [N/m] - force per unit length in y-direction on tower - tower_Pz : numpy array[n_full_tow], [N/m] - force per unit length in z-direction on tower - tower_qdyn : numpy array[n_full_tow], [N/m**2] - dynamic pressure on tower - radius_to_offset_column : float, [m] - Distance from main column centerpoint to offset column centerpoint - number_of_offset_columns : float - Number of offset columns evenly spaced around main column - pontoon_outer_diameter : float, [m] - Outer radius of tubular pontoon that connects offset or main columns - pontoon_wall_thickness : float, [m] - Inner radius of tubular pontoon that connects offset or main columns - cross_attachment_pontoons : TODO: add type by hand, could not be parsed automatically - Inclusion of pontoons that connect the bottom of the central main to the tops of - the outer offset columns - lower_attachment_pontoons : TODO: add type by hand, could not be parsed automatically - Inclusion of pontoons that connect the central main to the outer offset columns - at their bottoms - upper_attachment_pontoons : TODO: add type by hand, could not be parsed automatically - Inclusion of pontoons that connect the central main to the outer offset columns - at their tops - lower_ring_pontoons : TODO: add type by hand, could not be parsed automatically - Inclusion of pontoons that ring around outer offset columns at their bottoms - upper_ring_pontoons : TODO: add type by hand, could not be parsed automatically - Inclusion of pontoons that ring around outer offset columns at their tops - outer_cross_pontoons : TODO: add type by hand, could not be parsed automatically - Inclusion of pontoons that ring around outer offset columns at their tops - rna_mass : float, [kg] - mass of tower - rna_cg : numpy array[3], [m] - Location of RNA center of mass relative to tower top - rna_force : numpy array[3], [N] - Force in xyz-direction on turbine - rna_moment : numpy array[3], [N*m] - Moments about turbine main - rna_I : numpy array[6], [kg*m**2] - Moments about turbine main - number_of_mooring_connections : float - number of mooring connections on vessel - mooring_lines_per_connection : float - number of mooring lines per connection - mooring_neutral_load : numpy array[NLINES_MAX, 3], [N] - z-force of mooring lines on structure - mooring_stiffness : numpy array[6, 6], [N/m] - Linearized stiffness matrix of mooring system at neutral (no offset) conditions. - mooring_moments_of_inertia : numpy array[6], [kg*m**2] - mass moment of inertia of mooring system about fairlead-centerline point [xx yy - zz xy xz yz] - fairlead : float, [m] - Depth below water for mooring line attachment - fairlead_radius : float, [m] - Radius from center of structure to fairlead connection points - fairlead_support_outer_diameter : float, [m] - fairlead support outer diameter - fairlead_support_wall_thickness : float, [m] - fairlead support wall thickness - connection_ratio_max : float - Maximum ratio of pontoon outer diameter to main/offset outer diameter - material_cost_rate : float, [USD/kg] - Raw material cost rate: steel $1.1/kg, aluminum $3.5/kg - labor_cost_rate : float, [USD/min] - Labor cost rate - painting_cost_rate : float, [USD/m/m] - Painting / surface finishing cost rate - - Returns - ------- - pontoon_wave_height_depth_margin : numpy array[2], [m] - Distance between attachment point of pontoons and wave crest- both above and - below waterline - pontoon_cost : float, [USD] - Cost of pontoon elements and connecting truss - pontoon_cost_rate : float, [USD/t] - Cost rate of finished pontoon and truss - pontoon_mass : float, [kg] - Mass of pontoon elements and connecting truss - pontoon_displacement : float, [m**3] - Buoyancy force of submerged pontoon elements - pontoon_center_of_buoyancy : float, [m] - z-position of center of pontoon buoyancy force - pontoon_center_of_mass : float, [m] - z-position of center of pontoon mass - top_deflection : float, [m] - Deflection of tower top in yaw-aligned +x direction - pontoon_stress : numpy array[70, ] - Utilization (<1) of von Mises stress by yield stress and safety factor for all - pontoon elements - main_stress : numpy array[n_full_main-1] - Von Mises stress utilization along main column at specified locations. Incudes - safety factor. - main_stress:axial : numpy array[n_full_main-1] - Axial stress along main column at specified locations. - main_stress:shear : numpy array[n_full_main-1] - Shear stress along main column at specified locations. - main_stress:hoop : numpy array[n_full_main-1] - Hoop stress along main column at specified locations. - main_stress:hoopStiffen : numpy array[n_full_main-1] - Hoop stress along main column at specified locations. - main_shell_buckling : numpy array[n_full_main-1] - Shell buckling constraint. Should be < 1 for feasibility. Includes safety - factors - main_global_buckling : numpy array[n_full_main-1] - Global buckling constraint. Should be < 1 for feasibility. Includes safety - factors - offset_stress : numpy array[n_full_off-1] - Von Mises stress utilization along offset column at specified locations. Incudes - safety factor. - offset_stress:axial : numpy array[n_full_off-1] - Axial stress along offset column at specified locations. - offset_stress:shear : numpy array[n_full_off-1] - Shear stress along offset column at specified locations. - offset_stress:hoop : numpy array[n_full_off-1] - Hoop stress along offset column at specified locations. - offset_stress:hoopStiffen : numpy array[n_full_off-1] - Hoop stress along offset column at specified locations. - offset_shell_buckling : numpy array[n_full_off-1] - Shell buckling constraint. Should be < 1 for feasibility. Includes safety - factors - offset_global_buckling : numpy array[n_full_off-1] - Global buckling constraint. Should be < 1 for feasibility. Includes safety - factors - tower_stress : numpy array[n_full_tow-1] - Von Mises stress utilization along tower at specified locations. incudes safety - factor. - tower_stress:axial : numpy array[n_full_tow-1] - Axial stress along tower column at specified locations. - tower_stress:shear : numpy array[n_full_tow-1] - Shear stress along tower column at specified locations. - tower_stress:hoop : numpy array[n_full_tow-1] - Hoop stress along tower column at specified locations. - tower_stress:hoopStiffen : numpy array[n_full_tow-1] - Hoop stress along tower column at specified locations. - tower_shell_buckling : numpy array[n_full_tow-1] - Shell buckling constraint. Should be < 1 for feasibility. Includes safety - factors - tower_global_buckling : numpy array[n_full_tow-1] - Global buckling constraint. Should be < 1 for feasibility. Includes safety - factors - plot_matrix : numpy array[] - Ratio of shear stress to yield stress for all pontoon elements - main_connection_ratio : numpy array[n_full_main] - Ratio of pontoon outer diameter to main outer diameter - offset_connection_ratio : numpy array[n_full_off] - Ratio of pontoon outer diameter to main outer diameter - structural_frequencies : numpy array[NFREQ], [Hz] - First six natural frequencies - x_mode_shapes : numpy array[NFREQ/2] - 6-degree polynomial coefficients of mode shapes in the x-direction - y_mode_shapes : numpy array[NFREQ/2] - 6-degree polynomial coefficients of mode shapes in the y-direction - substructure_mass : float, [kg] - Mass of substructure elements and connecting truss - structural_mass : float, [kg] - Mass of whole turbine except for mooring lines - total_displacement : float, [m**3] - Total volume of water displaced by floating turbine (except for mooring lines) - z_center_of_buoyancy : float, [m] - z-position of center of buoyancy of whole turbine - substructure_center_of_mass : numpy array[3], [m] - xyz-position of center of gravity of substructure only - structure_center_of_mass : numpy array[3], [m] - xyz-position of center of gravity of whole turbine - total_force : numpy array[3], [N] - Net forces on turbine - total_moment : numpy array[3], [N*m] - Moments on whole turbine - - """ - - def initialize(self): - self.options.declare("n_height_main") - self.options.declare("n_height_off") - self.options.declare("n_height_tow") - self.options.declare("modeling_options") - - def setup(self): - n_height_main = self.options["n_height_main"] - n_height_off = self.options["n_height_off"] - n_height_tow = self.options["n_height_tow"] - n_full_main = get_nfull(n_height_main) - n_full_off = get_nfull(n_height_off) - n_full_tow = get_nfull(n_height_tow) - - # Keep Frame3DD data object for easy testing and debugging - self.myframe = None - - self.add_input("rho_water", val=0.0, units="kg/m**3") - self.add_input("hsig_wave", val=0.0, units="m") - - # Base column - self.add_input("main_z_full", val=np.zeros(n_full_main), units="m") - self.add_input("main_d_full", val=np.zeros(n_full_main), units="m") - self.add_input("main_t_full", val=np.zeros(n_full_main - 1), units="m") - self.add_input("main_rho_full", val=np.zeros(n_full_main - 1), units="kg/m**3") - self.add_input("main_E_full", val=np.zeros(n_full_main - 1), units="Pa") - self.add_input("main_G_full", val=np.zeros(n_full_main - 1), units="Pa") - self.add_input("main_sigma_y_full", val=np.zeros(n_full_main - 1), units="Pa") - self.add_input("main_mass", val=np.zeros(n_full_main - 1), units="kg") - self.add_input("main_displaced_volume", val=np.zeros(n_full_main - 1), units="m**3") - self.add_input("main_hydrostatic_force", val=np.zeros(n_full_main - 1), units="N") - self.add_input("main_center_of_buoyancy", val=0.0, units="m") - self.add_input("main_center_of_mass", val=0.0, units="m") - self.add_input("main_Px", np.zeros(n_full_main), units="N/m") - self.add_input("main_Py", np.zeros(n_full_main), units="N/m") - self.add_input("main_Pz", np.zeros(n_full_main), units="N/m") - self.add_input("main_qdyn", np.zeros(n_full_main), units="N/m**2") - - self.add_input("main_pontoon_attach_upper", val=0.0) - self.add_input("main_pontoon_attach_lower", val=0.0) - - # offset columns - self.add_input("offset_z_full", val=np.zeros(n_full_off), units="m") - self.add_input("offset_d_full", val=np.zeros(n_full_off), units="m") - self.add_input("offset_t_full", val=np.zeros(n_full_off - 1), units="m") - self.add_input("offset_rho_full", val=np.zeros(n_full_main - 1), units="kg/m**3") - self.add_input("offset_E_full", val=np.zeros(n_full_main - 1), units="Pa") - self.add_input("offset_G_full", val=np.zeros(n_full_main - 1), units="Pa") - self.add_input("offset_sigma_y_full", val=np.zeros(n_full_main - 1), units="Pa") - self.add_input("offset_mass", val=np.zeros(n_full_off - 1), units="kg") - self.add_input("offset_displaced_volume", val=np.zeros(n_full_off - 1), units="m**3") - self.add_input("offset_hydrostatic_force", val=np.zeros(n_full_off - 1), units="N") - self.add_input("offset_center_of_buoyancy", val=0.0, units="m") - self.add_input("offset_center_of_mass", val=0.0, units="m") - self.add_input("offset_Px", np.zeros(n_full_off), units="N/m") - self.add_input("offset_Py", np.zeros(n_full_off), units="N/m") - self.add_input("offset_Pz", np.zeros(n_full_off), units="N/m") - self.add_input("offset_qdyn", np.zeros(n_full_off), units="N/m**2") - - # Tower - self.add_input("tower_z_full", val=np.zeros(n_full_tow), units="m") - self.add_input("tower_d_full", val=np.zeros(n_full_tow), units="m") - self.add_input("tower_t_full", val=np.zeros(n_full_tow - 1), units="m") - self.add_input("tower_rho_full", val=np.zeros(n_full_main - 1), units="kg/m**3") - self.add_input("tower_E_full", val=np.zeros(n_full_main - 1), units="Pa") - self.add_input("tower_G_full", val=np.zeros(n_full_main - 1), units="Pa") - self.add_input("tower_sigma_y_full", val=np.zeros(n_full_main - 1), units="Pa") - self.add_input("tower_mass_section", val=np.zeros(n_full_tow - 1), units="kg") - self.add_input("tower_center_of_mass", val=0.0, units="m") - self.add_input("tower_Px", np.zeros(n_full_tow), units="N/m") - self.add_input("tower_Py", np.zeros(n_full_tow), units="N/m") - self.add_input("tower_Pz", np.zeros(n_full_tow), units="N/m") - self.add_input("tower_qdyn", np.zeros(n_full_tow), units="N/m**2") - - # Semi geometry - self.add_input("radius_to_offset_column", val=0.0, units="m") - self.add_input("number_of_offset_columns", val=3) - - # Pontoon properties - self.add_input("pontoon_outer_diameter", val=0.0, units="m") - self.add_input("pontoon_wall_thickness", val=0.0, units="m") - self.add_discrete_input("cross_attachment_pontoons", val=True) - self.add_discrete_input("lower_attachment_pontoons", val=True) - self.add_discrete_input("upper_attachment_pontoons", val=True) - self.add_discrete_input("lower_ring_pontoons", val=True) - self.add_discrete_input("upper_ring_pontoons", val=True) - self.add_discrete_input("outer_cross_pontoons", val=True) - - # Turbine parameters - self.add_input("rna_mass", val=0.0, units="kg") - self.add_input("rna_cg", val=np.zeros(3), units="m") - self.add_input("rna_force", val=np.zeros(3), units="N") - self.add_input("rna_moment", val=np.zeros(3), units="N*m") - self.add_input("rna_I", val=np.zeros(6), units="kg*m**2") - - # Mooring parameters for loading - self.add_input("number_of_mooring_connections", val=3) - self.add_input("mooring_lines_per_connection", val=1) - self.add_input("mooring_neutral_load", val=np.zeros((NLINES_MAX, 3)), units="N") - self.add_input("mooring_stiffness", val=np.zeros((6, 6)), units="N/m") - self.add_input("mooring_moments_of_inertia", val=np.zeros(6), units="kg*m**2") - self.add_input("fairlead", val=0.0, units="m") - self.add_input("fairlead_radius", val=0.0, units="m") - self.add_input("fairlead_support_outer_diameter", val=0.0, units="m") - self.add_input("fairlead_support_wall_thickness", val=0.0, units="m") - - # Manufacturing - self.add_input("connection_ratio_max", val=0.0) - - # Costing - self.add_input("material_cost_rate", 0.0, units="USD/kg") - self.add_input("labor_cost_rate", 0.0, units="USD/min") - self.add_input("painting_cost_rate", 0.0, units="USD/m/m") - - self.add_output("pontoon_wave_height_depth_margin", val=np.zeros(2), units="m") - self.add_output("pontoon_cost", val=0.0, units="USD") - self.add_output("pontoon_cost_rate", val=0.0, units="USD/t") - self.add_output("pontoon_mass", val=0.0, units="kg") - self.add_output("pontoon_displacement", val=0.0, units="m**3") - self.add_output("pontoon_center_of_buoyancy", val=0.0, units="m") - self.add_output("pontoon_center_of_mass", val=0.0, units="m") - - self.add_output("top_deflection", 0.0, units="m") - self.add_output("pontoon_stress", val=np.zeros(70)) - - self.add_output("main_stress", np.zeros(n_full_main - 1)) - self.add_output("main_stress:axial", np.zeros(n_full_main - 1)) - self.add_output("main_stress:shear", np.zeros(n_full_main - 1)) - self.add_output("main_stress:hoop", np.zeros(n_full_main - 1)) - self.add_output("main_stress:hoopStiffen", np.zeros(n_full_main - 1)) - self.add_output("main_shell_buckling", np.zeros(n_full_main - 1)) - self.add_output("main_global_buckling", np.zeros(n_full_main - 1)) - - self.add_output("offset_stress", np.zeros(n_full_off - 1)) - self.add_output("offset_stress:axial", np.zeros(n_full_off - 1)) - self.add_output("offset_stress:shear", np.zeros(n_full_off - 1)) - self.add_output("offset_stress:hoop", np.zeros(n_full_off - 1)) - self.add_output("offset_stress:hoopStiffen", np.zeros(n_full_off - 1)) - self.add_output("offset_shell_buckling", np.zeros(n_full_off - 1)) - self.add_output("offset_global_buckling", np.zeros(n_full_off - 1)) - - self.add_output("tower_stress", np.zeros(n_full_tow - 1)) - self.add_output("tower_stress:axial", np.zeros(n_full_tow - 1)) - self.add_output("tower_stress:shear", np.zeros(n_full_tow - 1)) - self.add_output("tower_stress:hoop", np.zeros(n_full_tow - 1)) - self.add_output("tower_stress:hoopStiffen", np.zeros(n_full_tow - 1)) - self.add_output("tower_shell_buckling", np.zeros(n_full_tow - 1)) - self.add_output("tower_global_buckling", np.zeros(n_full_tow - 1)) - - self.add_discrete_output("plot_matrix", val=np.array([])) - self.add_output("main_connection_ratio", val=np.zeros(n_full_main)) - self.add_output("offset_connection_ratio", val=np.zeros(n_full_off)) - - NFREQ2 = int(NFREQ / 2) - self.add_output("structural_frequencies", np.zeros(NFREQ), units="Hz") - self.add_output( - "x_mode_shapes", - val=np.zeros((NFREQ2, 5)), - desc="6-degree polynomial coefficients of mode shapes in the x-direction", - ) - self.add_output( - "y_mode_shapes", - val=np.zeros((NFREQ2, 5)), - desc="6-degree polynomial coefficients of mode shapes in the y-direction", - ) - self.add_output("substructure_mass", val=0.0, units="kg") - self.add_output("structural_mass", val=0.0, units="kg") - self.add_output("total_displacement", val=0.0, units="m**3") - self.add_output("z_center_of_buoyancy", val=0.0, units="m") - self.add_output("substructure_center_of_mass", val=np.zeros(3), units="m") - self.add_output("structure_center_of_mass", val=np.zeros(3), units="m") - self.add_output("total_force", val=np.zeros(3), units="N") - self.add_output("total_moment", val=np.zeros(3), units="N*m") - - self.declare_partials("*", "*", method="fd", form="central", step=1e-6) - - def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - - ncolumn = int(inputs["number_of_offset_columns"]) - crossAttachFlag = discrete_inputs["cross_attachment_pontoons"] - lowerAttachFlag = discrete_inputs["lower_attachment_pontoons"] - upperAttachFlag = discrete_inputs["upper_attachment_pontoons"] - lowerRingFlag = discrete_inputs["lower_ring_pontoons"] - upperRingFlag = discrete_inputs["upper_ring_pontoons"] - outerCrossFlag = discrete_inputs["outer_cross_pontoons"] - - R_semi = inputs["radius_to_offset_column"] if ncolumn > 0 else 0.0 - R_od_pontoon = 0.5 * inputs["pontoon_outer_diameter"] - R_od_main = 0.5 * inputs["main_d_full"] - R_od_offset = 0.5 * inputs["offset_d_full"] - R_od_tower = 0.5 * inputs["tower_d_full"] - R_od_fairlead = 0.5 * inputs["fairlead_support_outer_diameter"] - - t_wall_main = inputs["main_t_full"] - t_wall_offset = inputs["offset_t_full"] - t_wall_pontoon = inputs["pontoon_wall_thickness"] - t_wall_tower = inputs["tower_t_full"] - t_wall_fairlead = inputs["fairlead_support_wall_thickness"] - - E_main = inputs["main_E_full"] - E_offset = inputs["offset_E_full"] - E_tower = inputs["tower_E_full"] - - G_main = inputs["main_G_full"] - G_offset = inputs["offset_G_full"] - G_tower = inputs["tower_G_full"] - - rho_main = inputs["main_rho_full"] - rho_offset = inputs["offset_rho_full"] - rho_tower = inputs["tower_rho_full"] - - sigma_y_main = inputs["main_sigma_y_full"] - sigma_y_offset = inputs["offset_sigma_y_full"] - sigma_y_tower = inputs["tower_sigma_y_full"] - - z_main = inputs["main_z_full"] - z_offset = inputs["offset_z_full"] - z_tower = inputs["tower_z_full"] - z_attach_upper = inputs["main_pontoon_attach_upper"] * (z_main[-1] - z_main[0]) + z_main[0] - z_attach_lower = inputs["main_pontoon_attach_lower"] * (z_main[-1] - z_main[0]) + z_main[0] - z_fairlead = -inputs["fairlead"] - - m_main = inputs["main_mass"] - m_offset = inputs["offset_mass"] - m_tower = inputs["tower_mass_section"] - - m_rna = float(inputs["rna_mass"]) - F_rna = inputs["rna_force"] - M_rna = inputs["rna_moment"] - I_rna = inputs["rna_I"] - cg_rna = inputs["rna_cg"] - - rhoWater = float(inputs["rho_water"]) - - V_main = inputs["main_displaced_volume"] - V_offset = inputs["offset_displaced_volume"] - - F_hydro_main = inputs["main_hydrostatic_force"] - F_hydro_offset = inputs["offset_hydrostatic_force"] - - z_cb_main = inputs["main_center_of_buoyancy"] - z_cb_offset = inputs["offset_center_of_buoyancy"] - - cg_main = np.r_[0.0, 0.0, inputs["main_center_of_mass"]] - cg_offset = np.r_[0.0, 0.0, inputs["offset_center_of_mass"]] - cg_tower = np.r_[0.0, 0.0, inputs["tower_center_of_mass"]] - - n_connect = int(inputs["number_of_mooring_connections"]) - n_lines = int(inputs["mooring_lines_per_connection"]) - K_mooring = np.diag(inputs["mooring_stiffness"]) - I_mooring = inputs["mooring_moments_of_inertia"] - F_mooring = inputs["mooring_neutral_load"] - R_fairlead = inputs["fairlead_radius"] - - opt = self.options["modeling_options"] - gamma_f = opt["gamma_f"] - gamma_m = opt["gamma_m"] - gamma_n = opt["gamma_n"] - gamma_b = opt["gamma_b"] - frame3dd_opt = opt["frame3dd"] - - # Unpack variables - # Quick ratio for unknowns - outputs["main_connection_ratio"] = inputs["connection_ratio_max"] - R_od_pontoon / R_od_main - outputs["offset_connection_ratio"] = inputs["connection_ratio_max"] - R_od_pontoon / R_od_offset - outputs["pontoon_wave_height_depth_margin"] = np.abs(np.r_[z_attach_lower, z_attach_upper]) - np.abs( - inputs["hsig_wave"] - ) - - # --- INPUT CHECKS ----- - # If something fails, we have to tell the optimizer this design is no good - def bad_input(): - outputs["structural_frequencies"] = 1e30 * np.ones(NFREQ) - outputs["top_deflection"] = 1e30 - outputs["substructure_mass"] = 1e30 - outputs["structural_mass"] = 1e30 - outputs["total_displacement"] = 1e30 - outputs["z_center_of_buoyancy"] = 0.0 - outputs["substructure_center_of_mass"] = 1e30 * np.ones(3) - outputs["structure_center_of_mass"] = 1e30 * np.ones(3) - outputs["total_force"] = 1e30 * np.ones(3) - outputs["total_moment"] = 1e30 * np.ones(3) - outputs["tower_stress"] = 1e30 * np.ones(m_tower.shape) - outputs["tower_shell_buckling"] = 1e30 * np.ones(m_tower.shape) - outputs["tower_global_buckling"] = 1e30 * np.ones(m_tower.shape) - outputs["main_stress"] = 1e30 * np.ones(m_main.shape) - outputs["main_shell_buckling"] = 1e30 * np.ones(m_main.shape) - outputs["main_global_buckling"] = 1e30 * np.ones(m_main.shape) - outputs["offset_stress"] = 1e30 * np.ones(m_offset.shape) - outputs["offset_shell_buckling"] = 1e30 * np.ones(m_offset.shape) - outputs["offset_global_buckling"] = 1e30 * np.ones(m_offset.shape) - return - - # There is no truss if not offset columns - if ncolumn == 0: - crossAttachFlag = lowerAttachFlag = upperAttachFlag = False - lowerRingFlag = upperRingFlag = outerCrossFlag = False - - # Must have symmetry for the substructure to work out - if ncolumn in [1, 2] or ncolumn > 7: - bad_input() - return - - # Must have symmetry in moorning loading too - if (ncolumn > 0) and (n_connect > 0) and (ncolumn != n_connect): - bad_input() - return - - # If there are offset columns, must have attachment pontoons (only have ring pontoons doesn't make sense) - if (ncolumn > 0) and (not crossAttachFlag) and (not lowerAttachFlag) and (not upperAttachFlag): - bad_input() - return - - # Must have lower ring if have cross braces - if (ncolumn > 0) and outerCrossFlag and (not lowerRingFlag): - bad_input() - return - - # ---GEOMETRY--- - # Compute frustum angles - angle_tower = np.arctan(np.diff(R_od_tower) / np.diff(z_tower)) - angle_main = np.arctan(np.diff(R_od_main) / np.diff(z_main)) - angle_offset = np.arctan(np.diff(R_od_offset) / np.diff(z_offset)) - - # ---NODES--- - # Add nodes for main column: Using 4 nodes/3 elements per section - # Make sure there is a node at upper and lower attachment points - mainBeginID = 0 + 1 - if ncolumn > 0: - idx = find_nearest(z_main, z_attach_lower) - z_main[idx] = z_attach_lower - mainLowerID = idx + 1 - - idx = find_nearest(z_main, z_attach_upper) - z_main[idx] = z_attach_upper - mainUpperID = idx + 1 - - mainEndID = z_main.size - freeboard = z_main[-1] - - fairleadID = [] - # Need mooring attachment point if just running a spar - if ncolumn == 0: - idx = find_nearest(z_main, z_fairlead) - z_main[idx] = z_fairlead - fairleadID.append(idx + 1) - - znode = np.copy(z_main) - xnode = np.zeros(znode.shape) - ynode = np.zeros(znode.shape) - rnode = np.copy(R_od_main) - - towerBeginID = mainEndID - myz = np.zeros(len(z_tower) - 1) - xnode = np.append(xnode, myz) - ynode = np.append(ynode, myz) - znode = np.append(znode, z_tower[1:] + freeboard) - rnode = np.append(rnode, R_od_tower[1:]) - towerEndID = xnode.size - - # Create dummy node so that the tower isn't the last in a chain. - # This avoids a Frame3DD bug - dummyID = xnode.size + 1 - xnode = np.append(xnode, 0.0) - ynode = np.append(ynode, 0.0) - znode = np.append(znode, znode[-1] + 1.0) - rnode = np.append(rnode, 0.0) - - # Get x and y positions of surrounding offset columns - offsetLowerID = [] - offsetUpperID = [] - offsetx = R_semi * np.cos(np.linspace(0, 2 * np.pi, ncolumn + 1)) - offsety = R_semi * np.sin(np.linspace(0, 2 * np.pi, ncolumn + 1)) - offsetx = offsetx[:-1] - offsety = offsety[:-1] - - # Add in offset column nodes around the circle, make sure there is a node at the fairlead - idx = find_nearest(z_offset, z_fairlead) - myones = np.ones(z_offset.shape) - for k in range(ncolumn): - offsetLowerID.append(xnode.size + 1) - fairleadID.append(xnode.size + idx + 1) - xnode = np.append(xnode, offsetx[k] * myones) - ynode = np.append(ynode, offsety[k] * myones) - znode = np.append(znode, z_offset) - rnode = np.append(rnode, R_od_offset) - offsetUpperID.append(xnode.size) - - # Add nodes where mooring lines attach, which may be offset from columns - mooringx = R_fairlead * np.cos(np.linspace(0, 2 * np.pi, n_connect + 1))[:-1] - mooringy = R_fairlead * np.sin(np.linspace(0, 2 * np.pi, n_connect + 1))[:-1] - mooringID = xnode.size + 1 + np.arange(n_connect, dtype=np.int32) - xnode = np.append(xnode, mooringx) - ynode = np.append(ynode, mooringy) - znode = np.append(znode, z_fairlead * np.ones(n_connect)) - rnode = np.append(rnode, np.zeros(n_connect)) - - # Add nodes midway around outer ring for cross bracing - if outerCrossFlag and ncolumn > 0: - crossx = 0.5 * (offsetx + np.roll(offsetx, 1)) - crossy = 0.5 * (offsety + np.roll(offsety, 1)) - - crossOuterLowerID = xnode.size + np.arange(ncolumn) + 1 - crossOuterLowerID = crossOuterLowerID.tolist() - xnode = np.append(xnode, crossx) - ynode = np.append(ynode, crossy) - znode = np.append(znode, z_offset[0] * np.ones(ncolumn)) - rnode = np.append(rnode, np.zeros(ncolumn)) - - # crossOuterUpperID = xnode.size + np.arange(ncolumn) + 1 - # xnode = np.append(xnode, crossx) - # ynode = np.append(ynode, crossy) - # znode = np.append(znode, z_offset[-1]*np.ones(ncolumn)) - - # Create matrix for easy referencing - nodeMat = np.c_[xnode, ynode, znode] - - # To aid in wrap-around references - if ncolumn > 0: - offsetLowerID.append(offsetLowerID[0]) - offsetUpperID.append(offsetUpperID[0]) - if outerCrossFlag: - crossOuterLowerID.append(crossOuterLowerID[0]) - - # ---ELEMENTS / EDGES--- - # To accurately capture pontoon length and stiffness, for each connection we create 2 additional nodes, - # where the pontoon "line" intersects the main and offset shells. Highly stiff "ghost" elements are created - # from the column centerline to the shell. These are not calculated for pontoon weight. - # The actual pontoon only extends from shell boundary to shell boundary. - N1 = np.array([], dtype=np.int32) - N2 = np.array([], dtype=np.int32) - gN1 = np.array([], dtype=np.int32) - gN2 = np.array([], dtype=np.int32) - - # Lower connection from central main column to offset columns - if lowerAttachFlag: - lowerAttachEID = N1.size + 1 - for k in range(ncolumn): - tempID1 = mainLowerID - tempID2 = offsetLowerID[k] - add1, add2 = ghostNodes( - nodeMat[tempID1 - 1, :], nodeMat[tempID2 - 1, :], rnode[tempID1 - 1], rnode[tempID2 - 1] - ) - if (add1[-1] >= z_main[0]) and (add1[-1] <= z_main[-1]): - tempID1 = xnode.size + 1 - xnode = np.append(xnode, add1[0]) - ynode = np.append(ynode, add1[1]) - znode = np.append(znode, add1[2]) - gN1 = np.append(gN1, mainLowerID) - gN2 = np.append(gN2, tempID1) - if (add2[-1] >= z_offset[0]) and (add2[-1] <= z_offset[-1]): - tempID2 = xnode.size + 1 - xnode = np.append(xnode, add2[0]) - ynode = np.append(ynode, add2[1]) - znode = np.append(znode, add2[2]) - gN1 = np.append(gN1, offsetLowerID[k]) - gN2 = np.append(gN2, tempID2) - # Pontoon connection - N1 = np.append(N1, tempID1) - N2 = np.append(N2, tempID2) - - # Upper connection from central main column to offset columns - if upperAttachFlag: - upperAttachEID = N1.size + 1 - for k in range(ncolumn): - tempID1 = mainUpperID - tempID2 = offsetUpperID[k] - add1, add2 = ghostNodes( - nodeMat[tempID1 - 1, :], nodeMat[tempID2 - 1, :], rnode[tempID1 - 1], rnode[tempID2 - 1] - ) - if (add1[-1] >= z_main[0]) and (add1[-1] <= z_main[-1]): - tempID1 = xnode.size + 1 - xnode = np.append(xnode, add1[0]) - ynode = np.append(ynode, add1[1]) - znode = np.append(znode, add1[2]) - gN1 = np.append(gN1, mainUpperID) - gN2 = np.append(gN2, tempID1) - if (add2[-1] >= z_offset[0]) and (add2[-1] <= z_offset[-1]): - tempID2 = xnode.size + 1 - xnode = np.append(xnode, add2[0]) - ynode = np.append(ynode, add2[1]) - znode = np.append(znode, add2[2]) - gN1 = np.append(gN1, offsetUpperID[k]) - gN2 = np.append(gN2, tempID2) - # Pontoon connection - N1 = np.append(N1, tempID1) - N2 = np.append(N2, tempID2) - - # Cross braces from lower central main column to upper offset columns - if crossAttachFlag: - crossAttachEID = N1.size + 1 - for k in range(ncolumn): - tempID1 = mainLowerID - tempID2 = offsetUpperID[k] - add1, add2 = ghostNodes( - nodeMat[tempID1 - 1, :], nodeMat[tempID2 - 1, :], rnode[tempID1 - 1], rnode[tempID2 - 1] - ) - if (add1[-1] >= z_main[0]) and (add1[-1] <= z_main[-1]): - tempID1 = xnode.size + 1 - xnode = np.append(xnode, add1[0]) - ynode = np.append(ynode, add1[1]) - znode = np.append(znode, add1[2]) - gN1 = np.append(gN1, mainLowerID) - gN2 = np.append(gN2, tempID1) - if (add2[-1] >= z_offset[0]) and (add2[-1] <= z_offset[-1]): - tempID2 = xnode.size + 1 - xnode = np.append(xnode, add2[0]) - ynode = np.append(ynode, add2[1]) - znode = np.append(znode, add2[2]) - gN1 = np.append(gN1, offsetUpperID[k]) - gN2 = np.append(gN2, tempID2) - # Pontoon connection - N1 = np.append(N1, tempID1) - N2 = np.append(N2, tempID2) - - # Will be used later to convert from local member c.s. to global - cross_angle = np.arctan((z_attach_upper - z_attach_lower) / R_semi) - - # Lower ring around offset columns - if lowerRingFlag: - lowerRingEID = N1.size + 1 - for k in range(ncolumn): - tempID1 = offsetLowerID[k] - tempID2 = offsetLowerID[k + 1] - add1, add2 = ghostNodes( - nodeMat[tempID1 - 1, :], nodeMat[tempID2 - 1, :], rnode[tempID1 - 1], rnode[tempID2 - 1] - ) - if (add1[-1] >= z_offset[0]) and (add1[-1] <= z_offset[-1]): - tempID1 = xnode.size + 1 - xnode = np.append(xnode, add1[0]) - ynode = np.append(ynode, add1[1]) - znode = np.append(znode, add1[2]) - gN1 = np.append(gN1, offsetLowerID[k]) - gN2 = np.append(gN2, tempID1) - if (add2[-1] >= z_offset[0]) and (add2[-1] <= z_offset[-1]): - tempID2 = xnode.size + 1 - xnode = np.append(xnode, add2[0]) - ynode = np.append(ynode, add2[1]) - znode = np.append(znode, add2[2]) - gN1 = np.append(gN1, offsetLowerID[k + 1]) - gN2 = np.append(gN2, tempID2) - # Pontoon connection - N1 = np.append(N1, tempID1) - N2 = np.append(N2, tempID2) - - # Upper ring around offset columns - if upperRingFlag: - upperRingEID = N1.size + 1 - for k in range(ncolumn): - tempID1 = offsetUpperID[k] - tempID2 = offsetUpperID[k + 1] - add1, add2 = ghostNodes( - nodeMat[tempID1 - 1, :], nodeMat[tempID2 - 1, :], rnode[tempID1 - 1], rnode[tempID2 - 1] - ) - if (add1[-1] >= z_offset[0]) and (add1[-1] <= z_offset[-1]): - tempID1 = xnode.size + 1 - xnode = np.append(xnode, add1[0]) - ynode = np.append(ynode, add1[1]) - znode = np.append(znode, add1[2]) - gN1 = np.append(gN1, offsetUpperID[k]) - gN2 = np.append(gN2, tempID1) - if (add2[-1] >= z_offset[0]) and (add2[-1] <= z_offset[-1]): - tempID2 = xnode.size + 1 - xnode = np.append(xnode, add2[0]) - ynode = np.append(ynode, add2[1]) - znode = np.append(znode, add2[2]) - gN1 = np.append(gN1, offsetUpperID[k + 1]) - gN2 = np.append(gN2, tempID2) - # Pontoon connection - N1 = np.append(N1, tempID1) - N2 = np.append(N2, tempID2) - - # Outer cross braces (only one ghost node per connection) - if outerCrossFlag: - outerCrossEID = N1.size + 1 - for k in range(ncolumn): - tempID1 = crossOuterLowerID[k] - tempID2 = offsetUpperID[k] - _, add2 = ghostNodes( - nodeMat[tempID1 - 1, :], nodeMat[tempID2 - 1, :], rnode[tempID1 - 1], rnode[tempID2 - 1] - ) - if (add2[-1] >= z_offset[0]) and (add2[-1] <= z_offset[-1]): - tempID2 = xnode.size + 1 - xnode = np.append(xnode, add2[0]) - ynode = np.append(ynode, add2[1]) - znode = np.append(znode, add2[2]) - gN1 = np.append(gN1, offsetUpperID[k]) - gN2 = np.append(gN2, tempID2) - # Pontoon connection - N1 = np.append(N1, tempID1) - N2 = np.append(N2, tempID2) - - _, add2 = ghostNodes( - nodeMat[crossOuterLowerID[k + 1] - 1, :], - nodeMat[offsetUpperID[k] - 1, :], - rnode[crossOuterLowerID[k + 1] - 1], - rnode[offsetUpperID[k] - 1], - ) - tempID = xnode.size + 1 - xnode = np.append(xnode, add2[0]) - ynode = np.append(ynode, add2[1]) - znode = np.append(znode, add2[2]) - gN1 = np.append(gN1, offsetUpperID[k]) - gN2 = np.append(gN2, tempID) - N1 = np.append(N1, crossOuterLowerID[k + 1]) - N2 = np.append(N2, tempID) - - tempID1 = crossOuterLowerID[k + 1] - tempID2 = offsetUpperID[k] - _, add2 = ghostNodes( - nodeMat[tempID1 - 1, :], nodeMat[tempID2 - 1, :], rnode[tempID1 - 1], rnode[tempID2 - 1] - ) - if (add2[-1] > z_offset[0]) and (add2[-1] < z_offset[-1]): - tempID2 = xnode.size + 1 - xnode = np.append(xnode, add2[0]) - ynode = np.append(ynode, add2[1]) - znode = np.append(znode, add2[2]) - gN1 = np.append(gN1, offsetUpperID[k]) - gN2 = np.append(gN2, tempID2) - # Pontoon connection - N1 = np.append(N1, tempID1) - N2 = np.append(N2, tempID2) - - # TODO: Parameterize these for upper, lower, cross connections - # Properties for the inner connectors - # Assumes same material as main column here - mytube = Tube(2.0 * R_od_pontoon, t_wall_pontoon) - Ax = mytube.Area * np.ones(N1.shape) - As = mytube.Asx * np.ones(N1.shape) - Jx = mytube.J0 * np.ones(N1.shape) - I = mytube.Jxx * np.ones(N1.shape) - S = mytube.S * np.ones(N1.shape) - C = mytube.C * np.ones(N1.shape) - modE = E_main[0] * np.ones(N1.shape) - modG = G_main[0] * np.ones(N1.shape) - roll = 0.0 * np.ones(N1.shape) - dens = rho_main[0] * np.ones(N1.shape) - - # Add in fairlead support elements - mooringEID = N1.size + 1 - mytube = Tube(2.0 * R_od_fairlead, t_wall_fairlead) - rho_fair = rho_main[0] if ncolumn == 0 else rho_offset[0] - E_fair = E_main[0] if ncolumn == 0 else E_offset[0] - G_fair = G_main[0] if ncolumn == 0 else G_offset[0] - for k in range(n_connect): - kfair = 0 if ncolumn == 0 else k - - add1, _ = ghostNodes( - nodeMat[fairleadID[kfair] - 1, :], - nodeMat[mooringID[k] - 1, :], - rnode[fairleadID[kfair] - 1], - rnode[mooringID[k] - 1], - ) - tempID = xnode.size + 1 - xnode = np.append(xnode, add1[0]) - ynode = np.append(ynode, add1[1]) - znode = np.append(znode, add1[2]) - gN1 = np.append(gN1, fairleadID[kfair]) - gN2 = np.append(gN2, tempID) - N1 = np.append(N1, tempID) - N2 = np.append(N2, mooringID[k]) - - Ax = np.append(Ax, mytube.Area) - As = np.append(As, mytube.Asx) - Jx = np.append(Jx, mytube.J0) - I = np.append(I, mytube.Jxx) - S = np.append(S, mytube.S) - C = np.append(C, mytube.C) - modE = np.append(modE, E_fair) - modG = np.append(modG, G_fair) - roll = np.append(roll, 0.0) - dens = np.append(dens, rho_fair) - - # Now mock up cylindrical columns as truss members even though long, slender assumption breaks down - # Will set density = 0.0 so that we don't double count the mass - # First get geometry in each of the elements - R_od_main, _ = nodal2sectional(R_od_main) - R_od_offset, _ = nodal2sectional(R_od_offset) - R_od_tower, _ = nodal2sectional(R_od_tower) - - # Main column - mainEID = N1.size + 1 - mytube = Tube(2.0 * R_od_main, t_wall_main) - myrange = np.arange(R_od_main.size) - myones = np.ones(myrange.shape) - mydens = m_main / mytube.Area / np.diff(z_main) + eps # includes stiffeners, bulkheads, outfitting - N1 = np.append(N1, myrange + mainBeginID) - N2 = np.append(N2, myrange + mainBeginID + 1) - Ax = np.append(Ax, mytube.Area) - As = np.append(As, mytube.Asx) - Jx = np.append(Jx, mytube.J0) - I = np.append(I, mytube.Jxx) - S = np.append(S, mytube.S) - C = np.append(C, mytube.C) - modE = np.append(modE, E_main) - modG = np.append(modG, G_main) - roll = np.append(roll, np.zeros(myones.shape)) - dens = np.append(dens, mydens) - - # Tower column - towerEID = N1.size + 1 - mytube = Tube(2.0 * R_od_tower, t_wall_tower) - myrange = np.arange(R_od_tower.size) - myones = np.ones(myrange.shape) - mydens = m_tower / mytube.Area / np.diff(z_tower) + eps # includes stiffeners, bulkheads, outfitting - N1 = np.append(N1, myrange + towerBeginID) - N2 = np.append(N2, myrange + towerBeginID + 1) - Ax = np.append(Ax, mytube.Area) - As = np.append(As, mytube.Asx) - Jx = np.append(Jx, mytube.J0) - I = np.append(I, mytube.Jxx) - S = np.append(S, mytube.S) - C = np.append(C, mytube.C) - modE = np.append(modE, E_tower) - modG = np.append(modG, G_tower) - roll = np.append(roll, np.zeros(myones.shape)) - dens = np.append(dens, mydens) - - # Dummy element - dummyEID = N1.size + 1 - N1 = np.append(N1, towerEndID) - N2 = np.append(N2, dummyID) - Ax = np.append(Ax, Ax[-1]) - As = np.append(As, As[-1]) - Jx = np.append(Jx, Jx[-1]) - I = np.append(I, I[-1]) - S = np.append(S, S[-1]) - C = np.append(C, C[-1]) - modE = np.append(modE, 1e20) - modG = np.append(modG, 1e20) - roll = np.append(roll, 0.0) - dens = np.append(dens, 1e-6) - - # Offset column - offsetEID = [] - mytube = Tube(2.0 * R_od_offset, t_wall_offset) - myrange = np.arange(R_od_offset.size) - myones = np.ones(myrange.shape) - mydens = m_offset / mytube.Area / np.diff(z_offset) + eps # includes stiffeners, bulkheads, outfitting - for k in range(ncolumn): - offsetEID.append(N1.size + 1) - - N1 = np.append(N1, myrange + offsetLowerID[k]) - N2 = np.append(N2, myrange + offsetLowerID[k] + 1) - Ax = np.append(Ax, mytube.Area) - As = np.append(As, mytube.Asx) - Jx = np.append(Jx, mytube.J0) - I = np.append(I, mytube.Jxx) - S = np.append(S, mytube.S) - C = np.append(C, mytube.C) - modE = np.append(modE, E_offset) - modG = np.append(modG, G_offset) - roll = np.append(roll, np.zeros(myones.shape)) - dens = np.append(dens, mydens) # Mass added below - - # Ghost elements between centerline nodes and column shells - ghostEID = N1.size + 1 - myones = np.ones(gN1.shape) - N1 = np.append(N1, gN1) - N2 = np.append(N2, gN2) - Ax = np.append(Ax, 1e-1 * myones) - As = np.append(As, 1e-1 * myones) - Jx = np.append(Jx, 1e-1 * myones) - I = np.append(I, 1e-1 * myones) - S = np.append(S, 1e-1 * myones) - C = np.append(C, 1e-1 * myones) - modE = np.append(modE, 1e20 * myones) - modG = np.append(modG, 1e20 * myones) - roll = np.append(roll, 0.0 * myones) - dens = np.append(dens, 1e-6 * myones) - - # Create Node Data object - nnode = 1 + np.arange(xnode.size) - myrnode = np.zeros(xnode.shape) # z-spacing too narrow for use of rnodes - nodes = pyframe3dd.NodeData(nnode, xnode, ynode, znode, myrnode) - nodeMat = np.c_[xnode, ynode, znode] - - # Create Element Data object - nelem = 1 + np.arange(N1.size) - elements = pyframe3dd.ElementData(nelem, N1, N2, Ax, As, As, Jx, I, I, modE, modG, roll, dens) - - # Store data for plotting, also handy for operations below - plotMat = np.zeros((mainEID, 3, 2)) - myn1 = N1[:mainEID] - myn2 = N2[:mainEID] - plotMat[:, :, 0] = nodeMat[myn1 - 1, :] - plotMat[:, :, 1] = nodeMat[myn2 - 1, :] - discrete_outputs["plot_matrix"] = plotMat - - # Compute length and center of gravity for each element for use below - elemL = np.sqrt(np.sum(np.diff(plotMat, axis=2) ** 2.0, axis=1)).flatten() - elemCoG = 0.5 * np.sum(plotMat, axis=2) - # Get vertical angle as a measure of welding prep difficulty - elemAng = np.arccos(np.diff(plotMat[:, -1, :], axis=-1).flatten() / elemL) - - # ---Options object--- - other = pyframe3dd.Options(frame3dd_opt["shear"], frame3dd_opt["geom"], float(frame3dd_opt["dx"])) - - # ---LOAD CASES--- - # Extreme loading - gx = gy = 0.0 - gz = -gravity - load = pyframe3dd.StaticLoadCase(gx, gy, gz) - load0 = pyframe3dd.StaticLoadCase(gx, gy, gz) - - # Wind + Wave loading in local main / offset / tower c.s. - Px_main, Py_main, Pz_main = inputs["main_Pz"], inputs["main_Py"], -inputs["main_Px"] # switch to local c.s. - Px_offset, Py_offset, Pz_offset = ( - inputs["offset_Pz"], - inputs["offset_Py"], - -inputs["offset_Px"], - ) # switch to local c.s. - Px_tower, Py_tower, Pz_tower = ( - inputs["tower_Pz"], - inputs["tower_Py"], - -inputs["tower_Px"], - ) # switch to local c.s. - epsOff = 1e-5 - # Get mass right- offsets, stiffeners, tower, rna, etc. - # Also account for buoyancy loads - # Also apply wind/wave loading as trapezoidal on each element - # NOTE: Loading is in local element coordinates 0-L, x is along element - # Base - nrange = np.arange(R_od_main.size, dtype=np.int32) - EL = mainEID + nrange - Ux = F_hydro_main / np.diff(z_main) - x1 = np.zeros(nrange.shape) - x2 = np.diff(z_main) - epsOff # subtract small number b.c. of precision - wx1, wx2 = Px_main[:-1], Px_main[1:] - wy1, wy2 = Py_main[:-1], Py_main[1:] - wz1, wz2 = Pz_main[:-1], Pz_main[1:] - # Tower - nrange = np.arange(R_od_tower.size, dtype=np.int32) - EL = np.append(EL, towerEID + nrange) - Ux = np.append(Ux, np.zeros(nrange.shape)) - x1 = np.append(x1, np.zeros(nrange.shape)) - x2 = np.append(x2, np.diff(z_tower) - epsOff) - wx1 = np.append(wx1, Px_tower[:-1]) - wx2 = np.append(wx2, Px_tower[1:]) - wy1 = np.append(wy1, Py_tower[:-1]) - wy2 = np.append(wy2, Py_tower[1:]) - wz1 = np.append(wz1, Pz_tower[:-1]) - wz2 = np.append(wz2, Pz_tower[1:]) - # Buoyancy- offset columns - nrange = np.arange(R_od_offset.size, dtype=np.int32) - for k in range(ncolumn): - EL = np.append(EL, offsetEID[k] + nrange) - Ux = np.append(Ux, F_hydro_offset / np.diff(z_offset)) - x1 = np.append(x1, np.zeros(nrange.shape)) - x2 = np.append(x2, np.diff(z_offset) - epsOff) - wx1 = np.append(wx1, Px_offset[:-1]) - wx2 = np.append(wx2, Px_offset[1:]) - wy1 = np.append(wy1, Py_offset[:-1]) - wy2 = np.append(wy2, Py_offset[1:]) - wz1 = np.append(wz1, Pz_offset[:-1]) - wz2 = np.append(wz2, Pz_offset[1:]) - - # Add mass of main and offset columns while we've already done the element enumeration - Uz = Uy = np.zeros(Ux.shape) - xx1 = xy1 = xz1 = x1 - xx2 = xy2 = xz2 = x2 - load.changeTrapezoidalLoads(EL, xx1, xx2, wx1, wx2, xy1, xy2, wy1, wy2, xz1, xz2, wz1, wz2) - - # Buoyancy for fully submerged members - nrange = np.arange(ncolumn, dtype=np.int32) - Frange = np.pi * R_od_pontoon ** 2 * rhoWater * gravity - F_truss = 0.0 - z_cb = np.zeros(3) - if ncolumn > 0 and znode[offsetLowerID[0] - 1] < 0.0: - if lowerAttachFlag: - EL = np.append(EL, lowerAttachEID + nrange) - Ux = np.append(Ux, np.zeros(nrange.shape)) - Uy = np.append(Uy, np.zeros(nrange.shape)) - Uz = np.append(Uz, Frange * np.ones(nrange.shape)) - F_truss += Frange * elemL[lowerAttachEID - 1] * ncolumn - z_cb += Frange * elemL[lowerAttachEID - 1] * ncolumn * elemCoG[lowerAttachEID - 1, :] - if lowerRingFlag: - EL = np.append(EL, lowerRingEID + nrange) - Ux = np.append(Ux, np.zeros(nrange.shape)) - Uy = np.append(Uy, np.zeros(nrange.shape)) - Uz = np.append(Uz, Frange * np.ones(nrange.shape)) - F_truss += Frange * elemL[lowerRingEID - 1] * ncolumn - z_cb += Frange * elemL[lowerRingEID - 1] * ncolumn * elemCoG[lowerRingEID - 1] - if crossAttachFlag: - factor = np.minimum(1.0, (0.0 - z_attach_lower) / (znode[offsetUpperID[0] - 1] - z_attach_lower)) - EL = np.append(EL, crossAttachEID + nrange) - Ux = np.append(Ux, factor * Frange * np.sin(cross_angle) * np.ones(nrange.shape)) - Uy = np.append(Uy, np.zeros(nrange.shape)) - Uz = np.append(Uz, factor * Frange * np.cos(cross_angle) * np.ones(nrange.shape)) - F_truss += factor * Frange * elemL[crossAttachEID - 1] * ncolumn - z_cb += factor * Frange * elemL[crossAttachEID - 1] * ncolumn * elemCoG[crossAttachEID - 1, :] - if outerCrossFlag: - factor = np.minimum( - 1.0, (0.0 - znode[mainLowerID - 1]) / (znode[offsetUpperID[0] - 1] - znode[mainLowerID - 1]) - ) - # TODO: This one will take a little more math - # EL = np.append(EL, outerCrossEID + np.arange(2*ncolumn, dtype=np.int32)) - # Ux = np.append(Ux, np.zeros(nrange.shape)) - # Uy = np.append(Uy, np.zeros(nrange.shape)) - # Uz = np.append(Uz, factor * Frange * np.ones(nrange.shape)) - F_truss += factor * Frange * elemL[outerCrossEID - 1] * ncolumn - z_cb += factor * Frange * elemL[outerCrossEID - 1] * ncolumn * elemCoG[outerCrossEID - 1, :] - if ncolumn > 0 and znode[offsetUpperID[0] - 1] < 0.0: - if upperAttachFlag: - EL = np.append(EL, upperAttachEID + nrange) - Ux = np.append(Ux, np.zeros(nrange.shape)) - Uy = np.append(Uy, np.zeros(nrange.shape)) - Uz = np.append(Uz, Frange * np.ones(nrange.shape)) - F_truss += Frange * elemL[upperAttachEID - 1] * ncolumn - z_cb += Frange * elemL[upperAttachEID - 1] * ncolumn * elemCoG[upperAttachEID - 1, :] - if upperRingFlag: - EL = np.append(EL, upperRingEID + nrange) - Ux = np.append(Ux, np.zeros(nrange.shape)) - Uy = np.append(Uy, np.zeros(nrange.shape)) - Uz = np.append(Uz, Frange * np.ones(nrange.shape)) - F_truss += Frange * elemL[upperRingEID - 1] * ncolumn - z_cb += Frange * elemL[upperRingEID - 1] * ncolumn * elemCoG[upperRingEID - 1, :] - # Now do fairlead supports - nrange = np.arange(n_connect, dtype=np.int32) - Frange = np.pi * R_od_fairlead ** 2 * rhoWater * gravity - EL = np.append(EL, mooringEID + nrange) - Ux = np.append(Ux, np.zeros(nrange.shape)) - Uy = np.append(Uy, np.zeros(nrange.shape)) - Uz = np.append(Uz, Frange * np.ones(nrange.shape)) - F_truss += Frange * elemL[mooringEID - 1] * n_connect - z_cb += Frange * elemL[mooringEID - 1] * n_connect * elemCoG[mooringEID - 1, :] - # Finally add in all the uniform loads on buoyancy - load.changeUniformLoads(EL, Ux, Uy, Uz) - - # Point loading for rotor thrust and mooring lines - # Point loads for mooring loading - nnode_connect = len(fairleadID) - nF = np.array(fairleadID, dtype=np.int32) - Fx = np.zeros(nnode_connect) - Fy = np.zeros(nnode_connect) - Fz = np.zeros(nnode_connect) - Mxx = np.zeros(nnode_connect) - Myy = np.zeros(nnode_connect) - Mzz = np.zeros(nnode_connect) - for k in range(n_connect): - iline = 0 if nnode_connect == 1 else k - idx = k * n_lines + np.arange(n_lines) - Fx[iline] += F_mooring[idx, 0].sum() - Fy[iline] += F_mooring[idx, 1].sum() - Fz[iline] += F_mooring[idx, 2].sum() - # Note: extra momemt from mass accounted for below - nF = np.append(nF, towerEndID) - Fx = np.append(Fx, F_rna[0]) - Fy = np.append(Fy, F_rna[1]) - Fz = np.append(Fz, F_rna[2]) - Mxx = np.append(Mxx, M_rna[0]) - Myy = np.append(Myy, M_rna[1]) - Mzz = np.append(Mzz, M_rna[2]) - - # Add in all point loads - load.changePointLoads(nF, Fx, Fy, Fz, Mxx, Myy, Mzz) - - # ---MASS SUMMARIES--- - # Mass summaries now that we've tabulated all of the pontoons - m_substructure = m_main.sum() + ncolumn * m_offset.sum() - if mainEID > 1: # Have some pontoons or fairlead supports - # Buoyancy assembly from incremental calculations above - V_pontoon = F_truss / rhoWater / gravity - z_cb = z_cb[-1] / F_truss if F_truss > 0.0 else 0.0 - outputs["pontoon_displacement"] = V_pontoon - outputs["pontoon_center_of_buoyancy"] = z_cb - - # Sum up mass and compute CofG. Frame3DD does mass, but not CG - ind = mainEID - 1 - m_total = Ax[:ind] * rho_main[0] * elemL[:ind] - m_pontoon = m_total.sum() # mass.struct_mass - m_substructure += m_pontoon - cg_pontoon = np.sum(m_total[:, np.newaxis] * elemCoG[:ind, :], axis=0) / m_total.sum() - - # Compute costs based on "Optimum Design of Steel Structures" by Farkas and Jarmai - # All dimensions for correlations based on mm, not meters. - k_m = inputs["material_cost_rate"] # 1.1 # USD / kg carbon steel plate - k_f = inputs["labor_cost_rate"] # 1.0 # USD / min labor - k_p = inputs["painting_cost_rate"] # USD / m^2 painting - npont = m_total.size - - # Cost Step 1) Cutting and grinding tube ends - theta_g = 3.0 # Difficulty factor - # Cost Step 2) Fillet welds with SMAW (shielded metal arc welding) - # Multiply by 2 for both ends worth of work - theta_w = 3.0 # Difficulty factor - - # Labor-based expenses - K_f = ( - k_f - * 2 - * ( - manufacture.steel_tube_cutgrind_time(theta_g, R_od_pontoon, t_wall_pontoon, elemAng[:ind]) - + manufacture.steel_tube_welding_time( - theta_w, npont + ncolumn + 1, m_substructure, 2 * np.pi * R_od_pontoon, t_wall_pontoon - ) - ) - ) - - # Cost Step 3) Painting - theta_p = 2.0 - S_pont = 2.0 * np.pi * R_od_pontoon * elemL[:ind] - K_p = k_p * theta_p * S_pont.sum() - - # Material cost - K_m = k_m * m_pontoon - - # Total cost - c_pontoon = K_m + K_f + K_p - - outputs["pontoon_mass"] = m_pontoon - outputs["pontoon_cost"] = c_pontoon - outputs["pontoon_cost_rate"] = 1e3 * c_pontoon / m_pontoon - outputs["pontoon_center_of_mass"] = cg_pontoon[-1] - else: - V_pontoon = z_cb = m_pontoon = 0.0 - cg_pontoon = np.zeros(3) - - # Summary of mass and volumes - outputs["total_displacement"] = V_main.sum() + ncolumn * V_offset.sum() + V_pontoon - outputs["substructure_mass"] = m_substructure - outputs["substructure_center_of_mass"] = ( - ncolumn * m_offset.sum() * cg_offset + m_main.sum() * cg_main + m_pontoon * cg_pontoon - ) / outputs["substructure_mass"] - m_total = outputs["substructure_mass"] + m_rna + m_tower.sum() - outputs["structural_mass"] = m_total - outputs["structure_center_of_mass"] = ( - m_rna * cg_rna - + m_tower.sum() * cg_tower - + outputs["substructure_mass"] * outputs["substructure_center_of_mass"] - ) / m_total - - # Find cb (center of buoyancy) for whole system - z_cb = (V_main.sum() * z_cb_main + ncolumn * V_offset.sum() * z_cb_offset + V_pontoon * z_cb) / outputs[ - "total_displacement" - ] - outputs["z_center_of_buoyancy"] = z_cb - - # ---REACTIONS--- - # Will first compute unrestained mode shapes and then impose reactions to compute stress loads - # Find node closest to CG - cg_dist = np.sum((nodeMat - outputs["structure_center_of_mass"][np.newaxis, :]) ** 2, axis=1) - cg_node = np.argmin(cg_dist) - rigid = RIGID - rid = np.array([mainBeginID]) # np.array(fairleadID) #np.array([cg_node+1]) # - Rx = rigid * np.ones(rid.shape) - Ry = rigid * np.ones(rid.shape) - Rz = rigid * np.ones(rid.shape) - Rxx = rigid * np.ones(rid.shape) - Ryy = rigid * np.ones(rid.shape) - Rzz = rigid * np.ones(rid.shape) - rid = np.append(rid, fairleadID) - Rx = np.append(Rx, K_mooring[0] / nnode_connect * np.ones(nnode_connect)) - Ry = np.append(Ry, K_mooring[1] / nnode_connect * np.ones(nnode_connect)) - Rz = np.append(Rz, K_mooring[2] / nnode_connect * np.ones(nnode_connect)) - Rxx = np.append(Rxx, K_mooring[3] / nnode_connect * np.ones(nnode_connect)) - Ryy = np.append(Ryy, K_mooring[4] / nnode_connect * np.ones(nnode_connect)) - Rzz = np.append(Rzz, K_mooring[5] / nnode_connect * np.ones(nnode_connect)) - # Get reactions object from frame3dd - reactions = pyframe3dd.ReactionData(rid, Rx, Ry, Rz, Rxx, Ryy, Rzz, rigid=rigid) - R0 = np.zeros(rid.shape) - reactions0 = pyframe3dd.ReactionData([], R0, R0, R0, R0, R0, R0, rigid=1) - - # Initialize two frame3dd objects - myframe0 = pyframe3dd.Frame(nodes, reactions0, elements, other) - self.myframe = pyframe3dd.Frame(nodes, reactions, elements, other) - - # Add in extra mass of rna - inode = np.array([towerEndID], dtype=np.int32) # rna - m_extra = np.array([m_rna]) - Ixx = np.array([I_rna[0]]) - Iyy = np.array([I_rna[1]]) - Izz = np.array([I_rna[2]]) - Ixy = np.array([I_rna[3]]) - Ixz = np.array([I_rna[4]]) - Iyz = np.array([I_rna[5]]) - rhox = np.array([cg_rna[0]]) - rhoy = np.array([cg_rna[1]]) - rhoz = np.array([cg_rna[2]]) - myframe0.changeExtraNodeMass(inode, m_extra, Ixx, Iyy, Izz, Ixy, Ixz, Iyz, rhox, rhoy, rhoz, True) - self.myframe.changeExtraNodeMass(inode, m_extra, Ixx, Iyy, Izz, Ixy, Ixz, Iyz, rhox, rhoy, rhoz, True) - - # Add in load cases - myframe0.addLoadCase(load0) - self.myframe.addLoadCase(load) - - # ---RUN MODAL ANALYSIS--- - if opt["run_modal"]: - - # ---DYNAMIC ANALYSIS--- - shift = 1e1 - myframe0.enableDynamics( - 3 * NFREQ, frame3dd_opt["Mmethod"], frame3dd_opt["lump"], float(frame3dd_opt["tol"]), shift - ) - - # ---DEBUGGING--- - # myframe0.write('debug.3dd') # For debugging - - try: - _, _, _, _, _, modal = myframe0.run() - except: - bad_input() - return - - # Get mode shapes in batch - mpfs = np.abs(np.c_[modal.xmpf, modal.ympf, modal.zmpf]) - polys = get_modal_coefficients(znode, np.vstack((modal.xdsp, modal.ydsp)).T, 6) - xpolys = polys[:, : (3 * NFREQ)].T - ypolys = polys[:, (3 * NFREQ) :].T - - NFREQ2 = int(NFREQ / 2) - mshapes_x = np.zeros((NFREQ2, 5)) - mshapes_y = np.zeros((NFREQ2, 5)) - myfreqs = np.zeros(NFREQ) - ix = 0 - iy = 0 - im = 0 - for m in range(len(modal.freq)): - if mpfs[m, :].max() < 1e-11: - continue - imode = np.argmax(mpfs[m, :]) - if imode == 0 and ix < NFREQ2: - mshapes_x[ix, :] = xpolys[m, :] - myfreqs[im] = modal.freq[m] - ix += 1 - im += 1 - elif imode == 1 and iy < NFREQ2: - mshapes_y[iy, :] = ypolys[m, :] - myfreqs[im] = modal.freq[m] - iy += 1 - im += 1 - # else: - # print('Warning: Unknown mode shape') - outputs["x_mode_shapes"] = mshapes_x - outputs["y_mode_shapes"] = mshapes_y - outputs["structural_frequencies"] = myfreqs - - # ---RUN STRESS ANALYSIS--- - - # Initialize frame3dd object again with reactions and rerun with - try: - displacements, forces, reactions, internalForces, mass, _ = self.myframe.run() - except: - bad_input() - return - - # --OUTPUTS-- - nE = nelem.size - iCase = 0 - - # deflections due to loading (from cylinder top and wind/wave loads) - outputs["top_deflection"] = displacements.dx[iCase, towerEndID - 1] # in yaw-aligned direction - - # Find cg (center of gravity) for whole system - F_main = -1.0 * np.array([reactions.Fx.sum(), reactions.Fy.sum(), reactions.Fz.sum()]) - M_main = -1.0 * np.array([reactions.Mxx.sum(), reactions.Myy.sum(), reactions.Mzz.sum()]) - r_cg_main = np.array([0.0, 0.0, (znode[mainBeginID] - outputs["structure_center_of_mass"][-1])]) - delta = np.cross(r_cg_main, F_main) - - outputs["total_force"] = F_main - outputs["total_moment"] = M_main + delta - myM = np.cross(np.array([0.0, 0.0, (z_tower[-1] - outputs["structure_center_of_mass"][-1])]), F_rna) - - # shear and bending (convert from local to global c.s.) - Nx = forces.Nx[iCase, 1::2] - Vy = forces.Vy[iCase, 1::2] - Vz = forces.Vz[iCase, 1::2] - - Tx = forces.Txx[iCase, 1::2] - My = forces.Myy[iCase, 1::2] - Mz = forces.Mzz[iCase, 1::2] - - # Compute axial and shear stresses in elements given Frame3DD outputs and some geomtry data - # Method comes from Section 7.14 of Frame3DD documentation - # http://svn.code.sourceforge.net/p/frame3dd/code/trunk/doc/Frame3DD-manual.html#structuralmodeling - M = np.sqrt(My * My + Mz * Mz) - sigma_ax = Nx / Ax - M / S - sigma_sh = np.sqrt(Vy * Vy + Vz * Vz) / As + Tx / C - - # Extract pontoon for stress check - idx = range(mainEID - 1) - npon = len(idx) - if len(idx) > 0: - qdyn_pontoon = np.max(np.abs(np.r_[inputs["main_qdyn"], inputs["offset_qdyn"]])) - sigma_ax_pon = sigma_ax[idx] - sigma_sh_pon = sigma_sh[idx] - sigma_h_pon = util.hoopStress(2 * R_od_pontoon, t_wall_pontoon, qdyn_pontoon) * np.ones(sigma_ax_pon.shape) - - outputs["pontoon_stress"][:npon] = util.vonMisesStressUtilization( - sigma_ax_pon, sigma_h_pon, sigma_sh_pon, gamma_f * gamma_m * gamma_n, sigma_y_main[0] - ) - - # Extract tower for Eurocode checks - idx = towerEID - 1 + np.arange(R_od_tower.size, dtype=np.int32) - L_reinforced = opt["tower"]["buckling_length"] * np.ones(idx.shape) - sigma_ax_tower = sigma_ax[idx] - sigma_sh_tower = sigma_sh[idx] - qdyn_tower, _ = nodal2sectional(inputs["tower_qdyn"]) - sigma_h_tower = util.hoopStress(2 * R_od_tower, t_wall_tower * np.cos(angle_tower), qdyn_tower) - - outputs["tower_stress:axial"] = sigma_ax_tower - outputs["tower_stress:shear"] = sigma_sh_tower - outputs["tower_stress:hoop"] = sigma_h_tower - outputs["tower_stress:hoopStiffen"] = util.hoopStressEurocode( - z_tower, 2 * R_od_tower, t_wall_tower, L_reinforced, qdyn_tower - ) - outputs["tower_stress"] = util.vonMisesStressUtilization( - sigma_ax_tower, sigma_h_tower, sigma_sh_tower, gamma_f * gamma_m * gamma_n, sigma_y_tower - ) - - outputs["tower_shell_buckling"] = util.shellBucklingEurocode( - 2 * R_od_tower, - t_wall_tower, - sigma_ax_tower, - sigma_h_tower, - sigma_sh_tower, - L_reinforced, - E_tower, - sigma_y_tower, - gamma_f, - gamma_b, - ) - - tower_height = z_tower[-1] - z_tower[0] - outputs["tower_global_buckling"] = util.bucklingGL( - 2 * R_od_tower, t_wall_tower, Nx[idx], M[idx], tower_height, E_tower, sigma_y_tower, gamma_f, gamma_b - ) - - # Extract main column for Eurocode checks - idx = mainEID - 1 + np.arange(R_od_main.size, dtype=np.int32) - L_reinforced = opt["columns"]["main"]["buckling_length"] * np.ones(idx.shape) - sigma_ax_main = sigma_ax[idx] - sigma_sh_main = sigma_sh[idx] - qdyn_main, _ = nodal2sectional(inputs["main_qdyn"]) - sigma_h_main = util.hoopStress(2 * R_od_main, t_wall_main * np.cos(angle_main), qdyn_main) - - outputs["main_stress:axial"] = sigma_ax_main - outputs["main_stress:shear"] = sigma_sh_main - outputs["main_stress:hoop"] = sigma_h_main - outputs["main_stress:hoopStiffen"] = util.hoopStressEurocode( - z_main, 2 * R_od_main, t_wall_main, L_reinforced, qdyn_main - ) - outputs["main_stress"] = util.vonMisesStressUtilization( - sigma_ax_main, sigma_h_main, sigma_sh_main, gamma_f * gamma_m * gamma_n, sigma_y_main - ) - - outputs["main_shell_buckling"] = util.shellBucklingEurocode( - 2 * R_od_main, - t_wall_main, - sigma_ax_main, - sigma_h_main, - sigma_sh_main, - L_reinforced, - E_main, - sigma_y_main, - gamma_f, - gamma_b, - ) - - main_height = z_main[-1] - z_main[0] - outputs["main_global_buckling"] = util.bucklingGL( - 2 * R_od_main, t_wall_main, Nx[idx], M[idx], main_height, E_main, sigma_y_main, gamma_f, gamma_b - ) - - # Extract offset column for Eurocode checks - if ncolumn > 0: - idx = offsetEID[0] - 1 + np.arange(R_od_offset.size, dtype=np.int32) - L_reinforced = opt["columns"]["offset"]["buckling_length"] * np.ones(idx.shape) - sigma_ax_offset = sigma_ax[idx] - sigma_sh_offset = sigma_sh[idx] - qdyn_offset, _ = nodal2sectional(inputs["offset_qdyn"]) - sigma_h_offset = util.hoopStress(2 * R_od_offset, t_wall_offset * np.cos(angle_offset), qdyn_offset) - - outputs["offset_stress:axial"] = sigma_ax_offset - outputs["offset_stress:shear"] = sigma_sh_offset - outputs["offset_stress:hoop"] = sigma_h_offset - outputs["offset_stress:hoopStiffen"] = util.hoopStressEurocode( - z_offset, 2 * R_od_offset, t_wall_offset, L_reinforced, qdyn_offset - ) - outputs["offset_stress"] = util.vonMisesStressUtilization( - sigma_ax_offset, sigma_h_offset, sigma_sh_offset, gamma_f * gamma_m * gamma_n, sigma_y_offset - ) - - outputs["offset_shell_buckling"] = util.shellBucklingEurocode( - 2 * R_od_offset, - t_wall_offset, - sigma_ax_offset, - sigma_h_offset, - sigma_sh_offset, - L_reinforced, - E_offset, - sigma_y_offset, - gamma_f, - gamma_b, - ) - - offset_height = z_offset[-1] - z_offset[0] - outputs["offset_global_buckling"] = util.bucklingGL( - 2 * R_od_offset, - t_wall_offset, - Nx[idx], - M[idx], - offset_height, - E_offset, - sigma_y_offset, - gamma_f, - gamma_b, - ) - - -class TrussIntegerToBoolean(om.ExplicitComponent): - """ - Get booleans from truss integers - - Parameters - ---------- - cross_attachment_pontoons_int : float - Inclusion of pontoons that connect the bottom of the central main to the tops of - the outer offset columns - lower_attachment_pontoons_int : float - Inclusion of pontoons that connect the central main to the outer offset columns - at their bottoms - upper_attachment_pontoons_int : float - Inclusion of pontoons that connect the central main to the outer offset columns - at their tops - lower_ring_pontoons_int : float - Inclusion of pontoons that ring around outer offset columns at their bottoms - upper_ring_pontoons_int : float - Inclusion of pontoons that ring around outer offset columns at their tops - outer_cross_pontoons_int : float - Inclusion of pontoons that ring around outer offset columns at their tops - - Returns - ------- - cross_attachment_pontoons : boolean - Inclusion of pontoons that connect the bottom of the central main to the tops of - the outer offset columns - lower_attachment_pontoons : boolean - Inclusion of pontoons that connect the central main to the outer offset columns - at their bottoms - upper_attachment_pontoons : boolean - Inclusion of pontoons that connect the central main to the outer offset columns - at their tops - lower_ring_pontoons : boolean - Inclusion of pontoons that ring around outer offset columns at their bottoms - upper_ring_pontoons : boolean - Inclusion of pontoons that ring around outer offset columns at their tops - outer_cross_pontoons : boolean - Inclusion of pontoons that ring around outer offset columns at their tops - - """ - - def setup(self): - self.add_input("cross_attachment_pontoons_int", val=1) - self.add_input("lower_attachment_pontoons_int", val=1) - self.add_input("upper_attachment_pontoons_int", val=1) - self.add_input("lower_ring_pontoons_int", val=1) - self.add_input("upper_ring_pontoons_int", val=1) - self.add_input("outer_cross_pontoons_int", val=1) - - self.add_discrete_output("cross_attachment_pontoons", val=True) - self.add_discrete_output("lower_attachment_pontoons", val=True) - self.add_discrete_output("upper_attachment_pontoons", val=True) - self.add_discrete_output("lower_ring_pontoons", val=True) - self.add_discrete_output("upper_ring_pontoons", val=True) - self.add_discrete_output("outer_cross_pontoons", val=True) - - def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - discrete_outputs["cross_attachment_pontoons"] = int(inputs["cross_attachment_pontoons_int"]) == 1 - discrete_outputs["lower_attachment_pontoons"] = int(inputs["lower_attachment_pontoons_int"]) == 1 - discrete_outputs["upper_attachment_pontoons"] = int(inputs["upper_attachment_pontoons_int"]) == 1 - discrete_outputs["lower_ring_pontoons"] = int(inputs["lower_ring_pontoons_int"]) == 1 - discrete_outputs["upper_ring_pontoons"] = int(inputs["upper_ring_pontoons_int"]) == 1 - discrete_outputs["outer_cross_pontoons"] = int(inputs["outer_cross_pontoons_int"]) == 1 - - -# ----------------- -# Assembly -# ----------------- - - -class Loading(om.Group): - def initialize(self): - self.options.declare("n_height_main") - self.options.declare("n_height_off") - self.options.declare("n_height_tow") - self.options.declare("modeling_options") - - def setup(self): - n_height_main = self.options["n_height_main"] - n_height_off = self.options["n_height_off"] - n_height_tow = self.options["n_height_tow"] - n_full_main = get_nfull(n_height_main) - n_full_off = get_nfull(n_height_off) - n_full_tow = get_nfull(n_height_tow) - - self.set_input_defaults("outer_cross_pontoons_int", 1) - self.set_input_defaults("cross_attachment_pontoons_int", 1) - self.set_input_defaults("lower_attachment_pontoons_int", 1) - self.set_input_defaults("upper_attachment_pontoons_int", 1) - self.set_input_defaults("lower_ring_pontoons_int", 1) - self.set_input_defaults("upper_ring_pontoons_int", 1) - - # All the components - self.add_subsystem("loadingWind", PowerWind(nPoints=n_full_tow), promotes=["z0", "Uref", "shearExp", "zref"]) - self.add_subsystem( - "windLoads", CylinderWindDrag(nPoints=n_full_tow), promotes=["cd_usr", "beta_wind", "rho_air", "mu_air"] - ) - self.add_subsystem("intbool", TrussIntegerToBoolean(), promotes=["*"]) - self.add_subsystem( - "frame", - FloatingFrame( - n_height_main=n_height_main, - n_height_off=n_height_off, - n_height_tow=n_height_tow, - modeling_options=self.options["modeling_options"], - ), - promotes=["*"], - ) - - self.connect("loadingWind.U", "windLoads.U") - self.connect("windLoads.windLoads_Px", "tower_Px") - self.connect("windLoads.windLoads_Py", "tower_Py") - self.connect("windLoads.windLoads_Pz", "tower_Pz") - self.connect("windLoads.windLoads_qdyn", "tower_qdyn") diff --git a/wisdem/floatingse/map_mooring.py b/wisdem/floatingse/map_mooring.py index 30103964d..b3c0b89a4 100644 --- a/wisdem/floatingse/map_mooring.py +++ b/wisdem/floatingse/map_mooring.py @@ -1,16 +1,23 @@ import numpy as np -import os -import sys import openmdao.api as om from wisdem.pymap import pyMAP - from wisdem.commonse import gravity -from wisdem.commonse.utilities import assembleI, unassembleI NLINES_MAX = 15 NPTS_PLOT = 20 +def lines2nodes(F, nnode): + nline = F.shape[0] + ratio = int(nline / nnode) + if ratio == 1: + return F + Fnode = np.zeros((nnode, 3)) + for k in range(nnode): + Fnode[k, :] = F[(ratio * k) : (ratio * (k + 1)), :].sum(axis=0) + return Fnode + + class MapMooring(om.ExplicitComponent): """ Sets mooring line properties then writes MAP input file and executes MAP. @@ -25,43 +32,30 @@ class MapMooring(om.ExplicitComponent): water_depth : float, [m] water depth fairlead_radius : float, [m] - Outer spar radius at fairlead depth (point of mooring attachment) + Mooring attachment distance from vessel centerline fairlead : float, [m] Depth below water for mooring line attachment - mooring_line_length : float, [m] + line_length : float, [m] Unstretched total mooring line length anchor_radius : float, [m] radius from center of spar to mooring anchor point - mooring_diameter : float, [m] + line_diameter : float, [m] diameter of mooring line - number_of_mooring_connections : float - number of mooring connections on vessel - mooring_lines_per_connection : float - number of mooring lines per connection - mooring_type : string - chain, nylon, polyester, fiber, or iwrc anchor_type : string SUCTIONPILE or DRAGEMBEDMENT - max_offset : float, [m] - X offsets in discretization + max_surge_fraction : float + Maximum allowable surge offset as a fraction of water depth (0-1) operational_heel : float, [deg] Maximum angle of heel allowable during operation - max_survival_heel : float, [deg] + survival_heel : float, [deg] max heel angle for turbine survival - gamma_f : float - Safety factor for mooring line tension - mooring_cost_factor : float - miscellaneous cost factor in percent Returns ------- - number_of_mooring_lines : float - total number of mooring lines + line_mass : float, [kg] + mass of single mooring line mooring_mass : float, [kg] total mass of mooring - mooring_moments_of_inertia : numpy array[6], [kg*m**2] - mass moment of inertia of mooring system about fairlead-centerline point [xx yy - zz xy xz yz] mooring_cost : float, [USD] total cost for anchor + legs + miscellaneous costs mooring_stiffness : numpy array[6, 6], [N/m] @@ -74,83 +68,84 @@ class MapMooring(om.ExplicitComponent): sum of forces in x direction after max offset operational_heel_restoring_force : numpy array[NLINES_MAX, 3], [N] forces for all mooring lines after operational heel + survival_heel_restoring_force : numpy array[NLINES_MAX, 3], [N] + forces for all mooring lines after max survival heel mooring_plot_matrix : numpy array[NLINES_MAX, NPTS_PLOT, 3], [m] data matrix for plotting - axial_unity : float, [m] + constr_axial_load : float, [m] range of damaged mooring - mooring_length_max : float + constr_mooring_length : float mooring line length ratio to nodal distance """ def initialize(self): - self.options.declare("modeling_options") + self.options.declare("options") + self.options.declare("gamma") def setup(self): + n_lines = self.options["options"]["n_anchors"] + n_attach = self.options["options"]["n_attach"] # Variables local to the class and not OpenMDAO - self.min_break_load = None - self.wet_mass_per_length = None - self.axial_stiffness = None - self.area = None - self.cost_per_length = None self.finput = None self.tlpFlag = False self.add_input("rho_water", 0.0, units="kg/m**3") self.add_input("water_depth", 0.0, units="m") - self.add_input("fairlead_radius", 0.0, units="m") # Design variables + self.add_input("fairlead_radius", 0.0, units="m") self.add_input("fairlead", 0.0, units="m") - self.add_input("mooring_line_length", 0.0, units="m") + self.add_input("line_length", 0.0, units="m") + self.add_input("line_diameter", 0.0, units="m") self.add_input("anchor_radius", 0.0, units="m") - self.add_input("mooring_diameter", 0.0, units="m") + self.add_input("anchor_cost", 0.0, units="USD") + + self.add_input("line_mass_density_coeff", 0.0, units="kg/m**3") + self.add_input("line_stiffness_coeff", 0.0, units="N/m**2") + self.add_input("line_breaking_load_coeff", 0.0, units="N/m**2") + self.add_input("line_cost_rate_coeff", 0.0, units="USD/m**3") # User inputs (could be design variables) - self.add_input("number_of_mooring_connections", 3) - self.add_input("mooring_lines_per_connection", 1) - self.add_discrete_input("mooring_type", "CHAIN") - self.add_discrete_input("anchor_type", "DRAGEMBEDMENT") - self.add_input("max_offset", 0.0, units="m") + # self.add_discrete_input("mooring_type", "CHAIN") + # self.add_discrete_input("anchor_type", "DRAGEMBEDMENT") + self.add_input("max_surge_fraction", 0.1) self.add_input("operational_heel", 0.0, units="deg") - self.add_input("max_survival_heel", 0.0, units="deg") - - # Cost rates - self.add_input("mooring_cost_factor", 0.0) + self.add_input("survival_heel", 0.0, units="deg") - self.add_output("number_of_mooring_lines", 0) + self.add_output("line_mass", 0.0, units="kg") self.add_output("mooring_mass", 0.0, units="kg") - self.add_output("mooring_moments_of_inertia", np.zeros(6), units="kg*m**2") self.add_output("mooring_cost", 0.0, units="USD") self.add_output("mooring_stiffness", np.zeros((6, 6)), units="N/m") - self.add_output("anchor_cost", 0.0, units="USD") - self.add_output("mooring_neutral_load", np.zeros((NLINES_MAX, 3)), units="N") - self.add_output("max_offset_restoring_force", 0.0, units="N") - self.add_output("operational_heel_restoring_force", np.zeros((NLINES_MAX, 3)), units="N") - self.add_output("mooring_plot_matrix", np.zeros((NLINES_MAX, NPTS_PLOT, 3)), units="m") + self.add_output("mooring_neutral_load", np.zeros((n_attach, 3)), units="N") + self.add_output("max_surge_restoring_force", 0.0, units="N") + self.add_output("operational_heel_restoring_force", np.zeros((n_lines, 3)), units="N") + self.add_output("survival_heel_restoring_force", np.zeros((n_lines, 3)), units="N") + self.add_output("mooring_plot_matrix", np.zeros((n_lines, NPTS_PLOT, 3)), units="m") # Constraints - self.add_output("axial_unity", 0.0, units="m") - self.add_output("mooring_length_max", 0.0) + self.add_output("constr_axial_load", 0.0, units="m") + self.add_output("constr_mooring_length", 0.0) - self.declare_partials("*", "*", method="fd", form="central", step=1e-6) - - def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + def compute(self, inputs, outputs): # Set characteristics based on regressions / empirical data - self.set_properties(inputs, discrete_inputs) + # self.set_properties(inputs, discrete_inputs) # Set geometry profile self.set_geometry(inputs, outputs) # Write MAP input file and analyze the system at every angle - self.runMAP(inputs, discrete_inputs, outputs) + self.runMAP(inputs, outputs) # Compute costs for the system - self.compute_cost(inputs, discrete_inputs, outputs) + self.compute_cost(inputs, outputs) - def set_properties(self, inputs, discrete_inputs): - """Sets mooring line properties: Minimum Breaking Load, Mass per Length, Axial Stiffness, Cross-Sectional Area, Cost-per-Length. + def set_properties(self, inputs): + """ + THIS IS NOT USED BUT REMAINS FOR REFERENCE + Sets mooring line properties: Minimum Breaking Load, Mass per Length, + Axial Stiffness, Cross-Sectional Area, Cost-per-Length. INPUTS: ---------- @@ -172,7 +167,7 @@ def set_properties(self, inputs, discrete_inputs): """ # Unpack variables - Dmooring = inputs["mooring_diameter"] + Dmooring = inputs["line_diameter"] lineType = discrete_inputs["mooring_type"].upper() # Set parameters based on regressions for different mooring line type @@ -225,23 +220,23 @@ def set_geometry(self, inputs, outputs): R_fairlead = inputs["fairlead_radius"] R_anchor = inputs["anchor_radius"] waterDepth = inputs["water_depth"] - L_mooring = inputs["mooring_line_length"] - max_heel = inputs["max_survival_heel"] - gamma = self.options["modeling_options"]["gamma_f"] + L_mooring = inputs["line_length"] + max_heel = inputs["survival_heel"] + gamma = self.options["gamma"] if L_mooring > (waterDepth - fairleadDepth): self.tlpFlag = False # Create constraint that line isn't too long that there is no catenary hang - outputs["mooring_length_max"] = L_mooring / (0.95 * (R_anchor + waterDepth - fairleadDepth)) + outputs["constr_mooring_length"] = L_mooring / (0.95 * (R_anchor + waterDepth - fairleadDepth)) else: self.tlpFlag = True # Create constraint that we don't lose line tension - outputs["mooring_length_max"] = L_mooring / ( + outputs["constr_mooring_length"] = L_mooring / ( (waterDepth - fairleadDepth - gamma * R_fairlead * np.sin(np.deg2rad(max_heel))) ) - def write_line_dictionary(self, inputs, discrete_inputs, cable_sea_friction_coefficient=0.65): + def write_line_dictionary(self, inputs, cable_sea_friction_coefficient=0.65): """Writes LINE DICTIONARY section of input.map file INPUTS: @@ -252,16 +247,16 @@ def write_line_dictionary(self, inputs, discrete_inputs, cable_sea_friction_coef OUTPUTS : none """ # Unpack variables - rhoWater = inputs["rho_water"] - lineType = discrete_inputs["mooring_type"].lower() - Dmooring = inputs["mooring_diameter"] + Dmooring = inputs["line_diameter"] + wet_mass_per_length = inputs["line_mass_density_coeff"] * Dmooring ** 2 + axial_stiffness = inputs["line_stiffness_coeff"] * Dmooring ** 2 self.finput.append("---------------------- LINE DICTIONARY ---------------------------------------") self.finput.append("LineType Diam MassDenInAir EA CB CIntDamp Ca Cdn Cdt") self.finput.append("(-) (m) (kg/m) (N) (-) (Pa-s) (-) (-) (-)") self.finput.append( - "%s %.5f %.5f %.5f %.5f 1.0E8 0.6 -1.0 0.05" - % (lineType, Dmooring, self.wet_mass_per_length, self.axial_stiffness, cable_sea_friction_coefficient) + "myline %.5f %.5f %.5f %.5f 1.0E8 0.6 -1.0 0.05" + % (Dmooring, wet_mass_per_length, axial_stiffness, cable_sea_friction_coefficient) ) def write_node_properties_header(self): @@ -337,12 +332,12 @@ def write_node_properties( line += forceStr self.finput.append(line) - def write_line_properties(self, inputs, discrete_inputs, line_number=1, anchor_node=2, fairlead_node=1, flags=""): + def write_line_properties(self, inputs, line_number=1, anchor_node=2, fairlead_node=1, flags=""): """Writes LINE PROPERTIES section of input.map file that connects multiple nodes INPUTS: ---------- - inputs : dictionary of input parameters- only 'mooring_type' is used + inputs : dictionary of input parameters line_number : Line ID number (defaults to 1) anchor_node : Node number corresponding to anchor (defaults to 1) fairlead_node : Node number corresponding to fairlead (vessel) node (defaults to 2) @@ -357,17 +352,18 @@ def write_line_properties(self, inputs, discrete_inputs, line_number=1, anchor_n self.finput.append("---------------------- LINE PROPERTIES ---------------------------------------") self.finput.append("Line LineType UnstrLen NodeAnch NodeFair Flags") self.finput.append("(-) (-) (m) (-) (-) (-)") - self.finput.append( - "%d %s %f %d %d %s" - % ( - line_number, - discrete_inputs["mooring_type"], - inputs["mooring_line_length"], - anchor_node, - fairlead_node, - flags, + for k in range(len(line_number)): + self.finput.append( + "%d %s %f %d %d %s" + % ( + line_number[k], + "myline", + inputs["line_length"], + anchor_node[k], + fairlead_node[k], + flags, + ) ) - ) def write_solver_options(self, inputs): """Writes SOLVER OPTIONS section of input.map file, @@ -380,7 +376,7 @@ def write_solver_options(self, inputs): OUTPUTS : none """ # Unpack variables - n_connect = max(1, int(inputs["number_of_mooring_connections"])) + n_attach = self.options["options"]["n_attach"] self.finput.append("---------------------- SOLVER OPTIONS-----------------------------------------") self.finput.append("Option") @@ -401,7 +397,7 @@ def write_solver_options(self, inputs): self.finput.append(" inner_max_its 200") self.finput.append(" outer_max_its 600") # Repeat the details for the one mooring line multiple times - angles = np.linspace(0, 360, n_connect + 1)[1:-1] + angles = np.linspace(0, 360, n_attach + 1)[1:-1] line = "repeat" for degree in angles: line += " %d" % degree @@ -409,7 +405,7 @@ def write_solver_options(self, inputs): self.finput.append(" krylov_accelerator 3") self.finput.append(" ref_position 0.0 0.0 0.0") - def write_input_file(self, inputs, discrete_inputs): + def write_input_file(self, inputs): """Writes SOLVER OPTIONS section of input.map file, which includes repeating node/line arrangement in even angular spacing around structure. @@ -420,41 +416,43 @@ def write_input_file(self, inputs, discrete_inputs): OUTPUTS : none """ # Unpack variables - fairleadDepth = inputs["fairlead"] - R_fairlead = inputs["fairlead_radius"] - R_anchor = inputs["anchor_radius"] - n_connect = int(inputs["number_of_mooring_connections"]) - n_lines = int(inputs["mooring_lines_per_connection"]) - ntotal = n_connect * n_lines + fairleadDepth = float(inputs["fairlead"]) + R_fairlead = float(inputs["fairlead_radius"]) + R_anchor = float(inputs["anchor_radius"]) + n_attach = self.options["options"]["n_attach"] + n_anchors = self.options["options"]["n_anchors"] + ratio = int(n_anchors / n_attach) # Open the map input file self.finput = [] # Write the "Line Dictionary" section - self.write_line_dictionary(inputs, discrete_inputs) + self.write_line_dictionary(inputs) # Write the "Node Properties" section self.write_node_properties_header() # One end on sea floor the other at fairlead self.write_node_properties(1, "VESSEL", R_fairlead, 0, -fairleadDepth) - if n_lines > 1: - angles = np.linspace(0, 2 * np.pi, ntotal + 1)[:n_lines] + if ratio > 1: + angles = np.linspace(0, 2 * np.pi, n_anchors + 1)[:ratio] angles -= np.mean(angles) anchorx = R_anchor * np.cos(angles) anchory = R_anchor * np.sin(angles) - for k in range(n_lines): + for k in range(ratio): self.write_node_properties(k + 2, "FIX", anchorx[k], anchory[k], None) else: self.write_node_properties(2, "FIX", R_anchor, 0, None) # Write the "Line Properties" section - for k in range(n_lines): - self.write_line_properties(inputs, discrete_inputs, line_number=k + 1, anchor_node=k + 2, fairlead_node=1) + iline = np.arange(ratio) + 1 + ianch = np.arange(ratio) + 2 + ifair = np.ones(iline.shape) + self.write_line_properties(inputs, line_number=iline, anchor_node=ianch, fairlead_node=ifair) # Write the "Solve Options" section self.write_solver_options(inputs) - def runMAP(self, inputs, discrete_inputs, outputs): + def runMAP(self, inputs, outputs): """Writes MAP input file, executes, and then queries MAP to find maximum loading and displacement from vessel displacement around all 360 degrees @@ -469,16 +467,17 @@ def runMAP(self, inputs, discrete_inputs, outputs): rhoWater = float(inputs["rho_water"]) waterDepth = float(inputs["water_depth"]) fairleadDepth = float(inputs["fairlead"]) - Dmooring = float(inputs["mooring_diameter"]) - offset = float(inputs["max_offset"]) heel = float(inputs["operational_heel"]) - gamma = self.options["modeling_options"]["gamma_f"] - n_connect = int(inputs["number_of_mooring_connections"]) - n_lines = int(inputs["mooring_lines_per_connection"]) - ntotal = n_connect * n_lines + max_heel = inputs["survival_heel"] + d = inputs["line_diameter"] + min_break_load = inputs["line_breaking_load_coeff"] * d ** 2 + gamma = self.options["gamma"] + n_attach = self.options["options"]["n_attach"] + n_lines = self.options["options"]["n_anchors"] + offset = float(inputs["max_surge_fraction"]) * waterDepth # Write the mooring system input file for this design - self.write_input_file(inputs, discrete_inputs) + self.write_input_file(inputs) # Initiate MAP++ for this design mymap = pyMAP() @@ -498,72 +497,50 @@ def runMAP(self, inputs, discrete_inputs, outputs): mymap.update_states(0.0, 0) # Get the vertical load on the structure and plotting data - F_neutral = np.zeros((NLINES_MAX, 3)) - plotMat = np.zeros((NLINES_MAX, NPTS_PLOT, 3)) - nptsMOI = 100 - xyzpts = np.zeros((ntotal, nptsMOI, 3)) # For MOI calculation - for k in range(ntotal): + F_neutral = np.zeros((n_lines, 3)) + plotMat = np.zeros((n_lines, NPTS_PLOT, 3)) + for k in range(n_lines): (F_neutral[k, 0], F_neutral[k, 1], F_neutral[k, 2]) = mymap.get_fairlead_force_3d(k) plotMat[k, :, 0] = mymap.plot_x(k, NPTS_PLOT) plotMat[k, :, 1] = mymap.plot_y(k, NPTS_PLOT) plotMat[k, :, 2] = mymap.plot_z(k, NPTS_PLOT) - xyzpts[k, :, 0] = mymap.plot_x(k, nptsMOI) - xyzpts[k, :, 1] = mymap.plot_y(k, nptsMOI) - xyzpts[k, :, 2] = mymap.plot_z(k, nptsMOI) if self.tlpFlag: # Seems to be a bug in the plot arrays from MAP++ for plotting output with taut lines plotMat[k, :, 2] = np.linspace(-fairleadDepth, -waterDepth, NPTS_PLOT) - xyzpts[k, :, 2] = np.linspace(-fairleadDepth, -waterDepth, nptsMOI) - outputs["mooring_neutral_load"] = F_neutral + outputs["mooring_neutral_load"] = lines2nodes(F_neutral, n_attach) outputs["mooring_plot_matrix"] = plotMat - # Fine line segment length, ds = sqrt(dx^2 + dy^2 + dz^2) - xyzpts_dx = np.gradient(xyzpts[:, :, 0], axis=1) - xyzpts_dy = np.gradient(xyzpts[:, :, 1], axis=1) - xyzpts_dz = np.gradient(xyzpts[:, :, 2], axis=1) - xyzpts_ds = np.sqrt(xyzpts_dx ** 2 + xyzpts_dy ** 2 + xyzpts_dz ** 2) - - # Initialize inertia tensor integrands in https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor - # Taking MOI relative to body centerline at fairlead depth - r0 = np.array([0.0, 0.0, -fairleadDepth]) - R = np.zeros((ntotal, nptsMOI, 6)) - for ii in range(nptsMOI): - for k in range(ntotal): - r = xyzpts[k, ii, :] - r0 - R[k, ii, :] = unassembleI(np.dot(r, r) * np.eye(3) - np.outer(r, r)) - Imat = self.wet_mass_per_length * np.trapz(R, x=xyzpts_ds[:, :, np.newaxis], axis=1) - outputs["mooring_moments_of_inertia"] = np.abs(Imat.sum(axis=0)) - # Get the restoring moment at maximum angle of heel # Since we don't know the substucture CG, have to just get the forces of the lines now and do the cross product later # We also want to allow for arbitraty wind direction and yaw of rotor relative to mooring lines, so we will compare # pitch and roll forces as extremes # TODO: This still isgn't quite the same as clocking the mooring lines in different directions, # which is what we want to do, but that requires multiple input files and solutions - Fh = np.zeros((NLINES_MAX, 3)) + Fh = np.zeros((n_lines, 3)) mymap.displace_vessel(0, 0, 0, 0, heel, 0) mymap.update_states(0.0, 0) - for k in range(ntotal): - Fh[k][0], Fh[k][1], Fh[k][2] = mymap.get_fairlead_force_3d(k) + for k in range(n_lines): + Fh[k, 0], Fh[k, 1], Fh[k, 2] = mymap.get_fairlead_force_3d(k) + outputs["operational_heel_restoring_force"] = lines2nodes(Fh, n_attach) - outputs["operational_heel_restoring_force"] = Fh + mymap.displace_vessel(0, 0, 0, 0, max_heel, 0) + mymap.update_states(0.0, 0) + for k in range(n_lines): + Fh[k, 0], Fh[k, 1], Fh[k, 2] = mymap.get_fairlead_force_3d(k) + outputs["survival_heel_restoring_force"] = lines2nodes(Fh, n_attach) # Get angles by which to find the weakest line - dangle = 2.0 + dangle = 5.0 angles = np.deg2rad(np.arange(0.0, 360.0, dangle)) - nangles = len(angles) + nangles = angles.size # Get restoring force at weakest line at maximum allowable offset # Will global minimum always be along mooring angle? - max_tension = 0.0 - max_angle = None - min_angle = None - F_max_tension = None - F_min = np.inf - T = np.zeros((NLINES_MAX,)) - F = np.zeros((NLINES_MAX,)) + Frestore = np.zeros(nangles) + Tmax = np.zeros(nangles) + Fa = np.zeros((n_lines, 3)) # Loop around all angles to find weakest point - for a in angles: + for ia, a in enumerate(angles): # Unit vector and offset in x-y components idir = np.array([np.cos(a), np.sin(a)]) surge = offset * idir[0] @@ -572,35 +549,21 @@ def runMAP(self, inputs, discrete_inputs, outputs): # Get restoring force of offset at this angle mymap.displace_vessel(surge, sway, 0, 0, 0, 0) # 0s for z, angles mymap.update_states(0.0, 0) - for k in range(ntotal): - # Force in x-y-z coordinates - fx, fy, fz = mymap.get_fairlead_force_3d(k) - T[k] = np.sqrt(fx * fx + fy * fy + fz * fz) - # Total restoring force - F[k] = np.dot([fx, fy], idir) - - # Check if this is the weakest direction (highest tension) - tempMax = T.max() - if tempMax > max_tension: - max_tension = tempMax - F_max_tension = F.sum() - max_angle = a - if F.sum() < F_min: - F_min = F.sum() - min_angle = a + for k in range(n_lines): + Fa[k, 0], Fa[k, 1], Fa[k, 2] = mymap.get_fairlead_force_3d(k) + + Frestore[ia] = np.dot(Fa[:, :2].sum(axis=0), idir) + Tmax[ia] = np.sqrt(np.sum(Fa ** 2, axis=1)).max() # Store the weakest restoring force when the vessel is offset the maximum amount - outputs["max_offset_restoring_force"] = F_min + outputs["max_surge_restoring_force"] = Frestore.min() # Check for good convergence - if (plotMat[0, -1, -1] + fairleadDepth) > 1.0: - outputs["axial_unity"] = 1e30 - else: - outputs["axial_unity"] = gamma * max_tension / self.min_break_load + outputs["constr_axial_load"] = gamma * Tmax.max() / min_break_load mymap.end() - def compute_cost(self, inputs, discrete_inputs, outputs): + def compute_cost(self, inputs, outputs): """Computes cost, based on mass scaling, of mooring system. INPUTS: @@ -611,28 +574,28 @@ def compute_cost(self, inputs, discrete_inputs, outputs): OUTPUTS : none (mooring_cost/mass unknown dictionary values set) """ # Unpack variables - rhoWater = inputs["rho_water"] - L_mooring = inputs["mooring_line_length"] - anchorType = discrete_inputs["anchor_type"] - costFact = inputs["mooring_cost_factor"] - n_connect = int(inputs["number_of_mooring_connections"]) - n_lines = int(inputs["mooring_lines_per_connection"]) - ntotal = n_connect * n_lines + L_mooring = float(inputs["line_length"]) + # anchorType = discrete_inputs["anchor_type"] + d = float(inputs["line_diameter"]) + cost_per_length = float(inputs["line_cost_rate_coeff"]) * d ** 2 + # min_break_load = inputs['line_breaking_load_coeff'] * d**2 + wet_mass_per_length = float(inputs["line_mass_density_coeff"]) * d ** 2 + anchor_rate = float(inputs["anchor_cost"]) + n_anchors = n_lines = self.options["options"]["n_anchors"] # Cost of anchors - if anchorType.upper() == "DRAGEMBEDMENT": - anchor_rate = 1e-3 * self.min_break_load / gravity / 20 * 2000 - elif anchorType.upper() == "SUCTIONPILE": - anchor_rate = 150000.0 * np.sqrt(1e-3 * self.min_break_load / gravity / 1250.0) - else: - raise ValueError("Anchor Type must be DRAGEMBEDMENT or SUCTIONPILE") - anchor_total = anchor_rate * ntotal + # if anchorType.upper() == "DRAGEMBEDMENT": + # anchor_rate = 1e-3 * min_break_load / gravity / 20 * 2000 + # elif anchorType.upper() == "SUCTIONPILE": + # anchor_rate = 150000.0 * np.sqrt(1e-3 * min_break_load / gravity / 1250.0) + # else: + # raise ValueError("Anchor Type must be DRAGEMBEDMENT or SUCTIONPILE") + anchor_total = anchor_rate * n_anchors # Cost of all of the mooring lines - legs_total = ntotal * self.cost_per_length * L_mooring + legs_total = n_lines * cost_per_length * L_mooring # Total summations - outputs["anchor_cost"] = anchor_total - outputs["mooring_cost"] = costFact * (legs_total + anchor_total) - outputs["mooring_mass"] = self.wet_mass_per_length * L_mooring * ntotal - outputs["number_of_mooring_lines"] = ntotal + outputs["mooring_cost"] = legs_total + anchor_total + outputs["line_mass"] = wet_mass_per_length * L_mooring + outputs["mooring_mass"] = wet_mass_per_length * L_mooring * n_lines diff --git a/wisdem/floatingse/member.py b/wisdem/floatingse/member.py new file mode 100644 index 000000000..27e189248 --- /dev/null +++ b/wisdem/floatingse/member.py @@ -0,0 +1,1539 @@ +import copy + +import numpy as np +import openmdao.api as om +import wisdem.commonse.frustum as frustum +import wisdem.commonse.utilities as util +import wisdem.commonse.manufacturing as manufacture +import wisdem.commonse.cross_sections as cs +from wisdem.commonse import eps, gravity +from sortedcontainers import SortedDict +from wisdem.commonse.wind_wave_drag import CylinderEnvironment +from wisdem.commonse.utilization_constraints import GeometricConstraints + +NULL = -9999 +MEMMAX = 200 +NREFINE = 1 + + +class CrossSection(object): + def __init__(self, D=0.0, t=0.0, A=0.0, Asx=0.0, Asy=0.0, Ixx=0.0, Iyy=0.0, Izz=0.0, E=0.0, G=0.0, rho=0.0): + self.D, self.t = D, t # Needed for OpenFAST + self.A, self.Asx, self.Asy = A, Asx, Asy + self.Ixx, self.Iyy, self.Izz = Ixx, Iyy, Izz + self.E, self.G, self.rho = E, G, rho + + +def get_nfull(npts, nref=NREFINE): + n_full = int(1 + nref * (npts - 1)) + return n_full + + +def I_cyl(r_i, r_o, h, m): + if type(r_i) == type(np.array([])): + n = r_i.size + r_i = r_i.flatten() + r_o = r_o.flatten() + else: + n = 1 + Ixx = Iyy = (m / 12.0) * (3.0 * (r_i ** 2.0 + r_o ** 2.0) + h ** 2.0) + Izz = 0.5 * m * (r_i ** 2.0 + r_o ** 2.0) + return np.c_[Ixx, Iyy, Izz, np.zeros((n, 3))] + + +class DiscretizationYAML(om.ExplicitComponent): + + """ + Convert the YAML inputs into more native and easy to use variables. + + Parameters + ---------- + s : numpy array[n_height_tow] + 1D array of the non-dimensional grid defined along the member axis (0-member base, + 1-member top) + joint1 : numpy array[3], [m] + Global dimensional coordinates (x-y-z) for bottom node of member + joint2 : numpy array[3], [m] + Global dimensional coordinates (x-y-z) for top node of member + layer_materials : list of strings + 1D array of the names of the materials of each layer modeled in the member + structure. + layer_thickness : numpy array[n_layers, n_height], [m] + 2D array of the thickness of the layers of the member structure. The first + dimension represents each layer, the second dimension represents each piecewise- + constant entry of the member sections. + outer_diameter_in : numpy array[n_height_tow], [m] + cylinder diameter at corresponding locations + material_names : list of strings + 1D array of names of materials. + E_mat : numpy array[n_mat, 3], [Pa] + 2D array of the Youngs moduli of the materials. Each row represents a material, + the three members represent E11, E22 and E33. + G_mat : numpy array[n_mat, 3], [Pa] + 2D array of the shear moduli of the materials. Each row represents a material, + the three members represent G12, G13 and G23. + sigma_y_mat : numpy array[n_mat], [Pa] + 2D array of the yield strength of the materials. Each row represents a material, + the three members represent Xt12, Xt13 and Xt23. + rho_mat : numpy array[n_mat], [kg/m**3] + 1D array of the density of the materials. For composites, this is the density of + the laminate. + unit_cost_mat : numpy array[n_mat], [USD/kg] + 1D array of the unit costs of the materials. + outfitting_factor_in : float + Multiplier that accounts for secondary structure mass inside of member + rho_water : float, [kg/m**3] + density of water + + Returns + ------- + height : float, [m] + Scalar of the member height computed along the z axis. + section_height : numpy array[n_height-1], [m] + parameterized section heights along cylinder + outer_diameter : numpy array[n_height], [m] + cylinder diameter at corresponding locations + wall_thickness : numpy array[n_height-1], [m] + shell thickness at corresponding locations + E : numpy array[n_height-1], [Pa] + Isotropic Youngs modulus of the materials along the member sections. + G : numpy array[n_height-1], [Pa] + Isotropic shear modulus of the materials along the member sections. + sigma_y : numpy array[n_height-1], [Pa] + Isotropic yield strength of the materials along the member sections. + rho : numpy array[n_height-1], [kg/m**3] + Density of the materials along the member sections. + unit_cost : numpy array[n_height-1], [USD/kg] + Unit costs of the materials along the member sections. + outfitting_factor : numpy array[n_height-1] + Additional outfitting multiplier in each section + + """ + + def initialize(self): + self.options.declare("options") + self.options.declare("idx") + self.options.declare("n_mat") + + def setup(self): + n_mat = self.options["n_mat"] + opt = self.options["options"] + idx = self.options["idx"] + n_height = opt["n_height"][idx] + n_layers = opt["n_layers"][idx] + n_ballast = opt["n_ballasts"][idx] + + # TODO: Use reference axis and curvature, s, instead of assuming everything is vertical on z + self.add_input("s", val=np.zeros(n_height)) + self.add_input("joint1", val=np.zeros(3), units="m") + self.add_input("joint2", val=np.zeros(3), units="m") + self.add_discrete_input("transition_flag", val=[False, False]) + self.add_discrete_input("layer_materials", val=n_layers * [""]) + self.add_discrete_input("ballast_materials", val=n_ballast * [""]) + self.add_input("layer_thickness", val=np.zeros((n_layers, n_height)), units="m") + self.add_input("outer_diameter_in", np.zeros(n_height), units="m") + self.add_discrete_input("material_names", val=n_mat * [""]) + self.add_input("E_mat", val=np.zeros([n_mat, 3]), units="Pa") + self.add_input("G_mat", val=np.zeros([n_mat, 3]), units="Pa") + self.add_input("sigma_y_mat", val=np.zeros(n_mat), units="Pa") + self.add_input("rho_mat", val=np.zeros(n_mat), units="kg/m**3") + self.add_input("unit_cost_mat", val=np.zeros(n_mat), units="USD/kg") + self.add_input("outfitting_factor_in", val=1.0) + self.add_input("rho_water", 0.0, units="kg/m**3") + + self.add_output("height", val=0.0, units="m") + self.add_output("section_height", val=np.zeros(n_height - 1), units="m") + self.add_output("outer_diameter", val=np.zeros(n_height), units="m") + self.add_output("wall_thickness", val=np.zeros(n_height - 1), units="m") + self.add_output("E", val=np.zeros(n_height - 1), units="Pa") + self.add_output("G", val=np.zeros(n_height - 1), units="Pa") + self.add_output("sigma_y", val=np.zeros(n_height - 1), units="Pa") + self.add_output("rho", val=np.zeros(n_height - 1), units="kg/m**3") + self.add_output("unit_cost", val=np.zeros(n_height - 1), units="USD/kg") + self.add_output("outfitting_factor", val=np.ones(n_height - 1)) + self.add_output("ballast_density", val=np.zeros(n_ballast), units="kg/m**3") + self.add_output("ballast_unit_cost", val=np.zeros(n_ballast), units="USD/kg") + self.add_output("transition_node", NULL * np.ones(3), units="m") + + # Distributed Beam Properties (properties needed for ElastoDyn (OpenFAST) inputs or BModes inputs for verification purposes) + self.add_output("z_param", np.zeros(n_height), units="m") + self.add_output("sec_loc", np.zeros(n_height - 1), desc="normalized sectional location") + self.add_output("str_tw", np.zeros(n_height - 1), units="deg", desc="structural twist of section") + self.add_output("tw_iner", np.zeros(n_height - 1), units="deg", desc="inertial twist of section") + self.add_output("mass_den", np.zeros(n_height - 1), units="kg/m", desc="sectional mass per unit length") + self.add_output( + "foreaft_iner", + np.zeros(n_height - 1), + units="kg*m", + desc="sectional fore-aft intertia per unit length about the Y_G inertia axis", + ) + self.add_output( + "sideside_iner", + np.zeros(n_height - 1), + units="kg*m", + desc="sectional side-side intertia per unit length about the Y_G inertia axis", + ) + self.add_output( + "foreaft_stff", + np.zeros(n_height - 1), + units="N*m**2", + desc="sectional fore-aft bending stiffness per unit length about the Y_E elastic axis", + ) + self.add_output( + "sideside_stff", + np.zeros(n_height - 1), + units="N*m**2", + desc="sectional side-side bending stiffness per unit length about the Y_E elastic axis", + ) + self.add_output("tor_stff", np.zeros(n_height - 1), units="N*m**2", desc="sectional torsional stiffness") + self.add_output("axial_stff", np.zeros(n_height - 1), units="N", desc="sectional axial stiffness") + self.add_output("cg_offst", np.zeros(n_height - 1), units="m", desc="offset from the sectional center of mass") + self.add_output("sc_offst", np.zeros(n_height - 1), units="m", desc="offset from the sectional shear center") + self.add_output("tc_offst", np.zeros(n_height - 1), units="m", desc="offset from the sectional tension center") + + def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + # Unpack dimensions + opt = self.options["options"] + idx = self.options["idx"] + n_height = opt["n_height"][idx] + n_ballast = opt["n_ballasts"][idx] + + # Unpack values + xyz0 = inputs["joint1"] + xyz1 = inputs["joint2"] + dxyz = xyz1 - xyz0 + h_col = np.sqrt(np.sum(dxyz ** 2)) + lthick = inputs["layer_thickness"] + lthick = 0.5 * (lthick[:, :-1] + lthick[:, 1:]) + if discrete_inputs["transition_flag"][0]: + outputs["transition_node"] = xyz0 + elif discrete_inputs["transition_flag"][1]: + outputs["transition_node"] = xyz1 + else: + outputs["transition_node"] = NULL * np.ones(3) + + outputs["height"] = h_col + outputs["section_height"] = np.diff(h_col * inputs["s"]) + outputs["wall_thickness"] = np.sum(lthick, axis=0) + outputs["outer_diameter"] = inputs["outer_diameter_in"] + outputs["outfitting_factor"] = inputs["outfitting_factor_in"] * np.ones(n_height - 1) + twall = lthick + + # Check to make sure we have good values + if np.any(outputs["section_height"] <= 0.0): + raise ValueError("Section height values must be greater than zero, " + str(outputs["section_height"])) + if np.any(outputs["wall_thickness"] <= 0.0): + raise ValueError("Wall thickness values must be greater than zero, " + str(outputs["wall_thickness"])) + if np.any(outputs["outer_diameter"] <= 0.0): + raise ValueError("Diameter values must be greater than zero, " + str(outputs["outer_diameter"])) + + # DETERMINE MATERIAL PROPERTIES IN EACH SECTION + # Convert to isotropic material + E = np.mean(inputs["E_mat"], axis=1) + G = np.mean(inputs["G_mat"], axis=1) + sigy = inputs["sigma_y_mat"] + rho = inputs["rho_mat"] + cost = inputs["unit_cost_mat"] + mat_names = discrete_inputs["material_names"] + + # Initialize sectional data + E_param = np.zeros(twall.shape) + G_param = np.zeros(twall.shape) + sigy_param = np.zeros(twall.shape) + rho_param = np.zeros(n_height - 1) + cost_param = np.zeros(n_height - 1) + + # Loop over materials and associate it with its thickness + layer_mat = discrete_inputs["layer_materials"] + for k in range(len(layer_mat)): + # Get the material name for this layer + iname = layer_mat[k] + + # Get the index into the material list + imat = mat_names.index(iname) + + imass = rho[imat] * twall[k, :] + + # For density, take mass weighted layer + rho_param += imass + + # For cost, take mass weighted layer + cost_param += imass * cost[imat] + + # Store the value associated with this thickness + E_param[k, :] = E[imat] + G_param[k, :] = G[imat] + sigy_param[k, :] = sigy[imat] + + # Mass weighted cost (should really weight by radius too) + cost_param /= rho_param + + # Thickness weighted density (should really weight by radius too) + rho_param /= twall.sum(axis=0) + + # Mixtures of material properties: https://en.wikipedia.org/wiki/Rule_of_mixtures + + # Volume fraction + vol_frac = twall / twall.sum(axis=0)[np.newaxis, :] + + # Average of upper and lower bounds + E_param = 0.5 * np.sum(vol_frac * E_param, axis=0) + 0.5 / np.sum(vol_frac / E_param, axis=0) + G_param = 0.5 * np.sum(vol_frac * G_param, axis=0) + 0.5 / np.sum(vol_frac / G_param, axis=0) + sigy_param = 0.5 * np.sum(vol_frac * sigy_param, axis=0) + 0.5 / np.sum(vol_frac / sigy_param, axis=0) + + # Store values + outputs["E"] = E_param + outputs["G"] = G_param + outputs["rho"] = rho_param + outputs["sigma_y"] = sigy_param + outputs["unit_cost"] = cost_param + + # Unpack for Elastodyn + z_param = min(xyz0[2], xyz1[2]) + (h_col * inputs["s"]) + z = 0.5 * (z_param[:-1] + z_param[1:]) + D, _ = util.nodal2sectional(outputs["outer_diameter"]) + itube = cs.Tube(D, outputs["wall_thickness"]) + Az, Ixx, Iyy, Jz = itube.Area, itube.Jxx, itube.Jyy, itube.J0 + outputs["z_param"] = z_param + outputs["sec_loc"] = 0.0 if len(z) == 1 else (z - z[0]) / (z[-1] - z[0]) + outputs["mass_den"] = rho_param * Az + outputs["foreaft_iner"] = rho_param * Ixx + outputs["sideside_iner"] = rho_param * Iyy + outputs["foreaft_stff"] = E_param * Ixx + outputs["sideside_stff"] = E_param * Iyy + outputs["tor_stff"] = G_param * Jz + outputs["axial_stff"] = E_param * Az + + # Loop over materials and associate it with its thickness + rho_ballast = np.zeros(n_ballast) + cost_ballast = np.zeros(n_ballast) + ballast_mat = discrete_inputs["ballast_materials"] + for k in range(n_ballast): + # Get the material name for this layer + iname = ballast_mat[k] + + if iname.find("water") >= 0 or iname == "": + rho_ballast[k] = inputs["rho_water"] + continue + + # Get the index into the material list + imat = mat_names.index(iname) + + # Store values + rho_ballast[k] = rho[imat] + cost_ballast[k] = cost[imat] + + outputs["ballast_density"] = rho_ballast + outputs["ballast_unit_cost"] = cost_ballast + + +class MemberDiscretization(om.ExplicitComponent): + """ + Discretize geometry into finite element nodes + + Parameters + ---------- + s : numpy array[n_height_tow] + 1D array of the non-dimensional grid defined along the member axis (0-member base, + 1-member top) + outer_diameter : numpy array[n_height], [m] + cylinder diameter at corresponding locations + wall_thickness : numpy array[n_height-1], [m] + shell thickness at corresponding locations + E : numpy array[n_height-1], [Pa] + Isotropic Youngs modulus of the materials along the member sections. + G : numpy array[n_height-1], [Pa] + Isotropic shear modulus of the materials along the member sections. + sigma_y : numpy array[n_height-1], [Pa] + Isotropic yield strength of the materials along the member sections. + rho : numpy array[n_height-1], [kg/m**3] + Density of the materials along the member sections. + unit_cost : numpy array[n_height-1], [USD/kg] + Unit costs of the materials along the member sections. + outfitting_factor : numpy array[n_height-1] + Additional outfitting multiplier in each section + + Returns + ------- + s_full : numpy array[n_full] + non-dimensional locations along member + z_full : numpy array[n_full], [m] + dimensional locations along member axis + d_full : numpy array[n_full], [m] + cylinder diameter at corresponding locations + t_full : numpy array[n_full-1], [m] + shell thickness at corresponding locations + E_full : numpy array[n_full-1], [Pa] + Isotropic Youngs modulus of the materials along the member sections. + G_full : numpy array[n_full-1], [Pa] + Isotropic shear modulus of the materials along the member sections. + sigma_y_full : numpy array[n_full-1], [Pa] + Isotropic yield strength of the materials along the member sections. + rho_full : numpy array[n_full-1], [kg/m**3] + Density of the materials along the member sections. + unit_cost_full : numpy array[n_full-1], [USD/kg] + Unit costs of the materials along the member sections. + nu_full : numpy array[n_full-1] + Poisson's ratio assuming isotropic material + outfitting_full : numpy array[n_full-1] + Additional outfitting multiplier in each section + + """ + + """discretize geometry into finite element nodes""" + + def initialize(self): + self.options.declare("n_height") + self.options.declare("n_refine", default=NREFINE) + + def setup(self): + n_height = self.options["n_height"] + n_full = get_nfull(n_height, nref=self.options["n_refine"]) + + self.add_input("s", val=np.zeros(n_height)) + self.add_input("height", val=0.0, units="m") + self.add_input("outer_diameter", np.zeros(n_height), units="m") + self.add_input("wall_thickness", np.zeros(n_height - 1), units="m") + self.add_input("E", val=np.zeros(n_height - 1), units="Pa") + self.add_input("G", val=np.zeros(n_height - 1), units="Pa") + self.add_input("sigma_y", val=np.zeros(n_height - 1), units="Pa") + self.add_input("rho", val=np.zeros(n_height - 1), units="kg/m**3") + self.add_input("unit_cost", val=np.zeros(n_height - 1), units="USD/kg") + self.add_input("outfitting_factor", val=np.ones(n_height - 1)) + + self.add_output("s_full", np.zeros(n_full), units="m") + self.add_output("z_full", np.zeros(n_full), units="m") + self.add_output("d_full", np.zeros(n_full), units="m") + self.add_output("t_full", np.zeros(n_full - 1), units="m") + self.add_output("E_full", val=np.zeros(n_full - 1), units="Pa") + self.add_output("G_full", val=np.zeros(n_full - 1), units="Pa") + self.add_output("nu_full", val=np.zeros(n_full - 1)) + self.add_output("sigma_y_full", val=np.zeros(n_full - 1), units="Pa") + self.add_output("rho_full", val=np.zeros(n_full - 1), units="kg/m**3") + self.add_output("unit_cost_full", val=np.zeros(n_full - 1), units="USD/kg") + self.add_output("outfitting_full", val=np.ones(n_full - 1)) + + # self.declare_partials('*', '*', method='fd', form='central', step=1e-6) + + def compute(self, inputs, outputs): + # Unpack inputs + s_param = inputs["s"] + n_refine = int(np.round(self.options["n_refine"])) + + # TODO: Put these somewhere + # Create constraint output that draft is less than water depth + # outputs["draft_margin"] = draft / inputs["max_draft"] + + # Make sure freeboard is more than 20% of Hsig_wave (DNV-OS-J101) + # outputs["wave_height_freeboard_ratio"] = inputs["Hsig_wave"] / (np.abs(freeboard) + eps) + + # Have to regine each element one at a time so that we preserve input nodes + s_full = np.array([]) + for k in range(s_param.size - 1): + sref = np.linspace(s_param[k], s_param[k + 1], n_refine + 1) + s_full = np.append(s_full, sref) + s_full = np.unique(s_full) + s_section = 0.5 * (s_full[:-1] + s_full[1:]) + + # Assuming straight (non-curved) members, set dimensional z along the axis + outputs["s_full"] = s_full + outputs["z_full"] = s_full * inputs["height"] + + # All other parameters + outputs["d_full"] = np.interp(s_full, s_param, inputs["outer_diameter"]) + outputs["t_full"] = util.sectionalInterp(s_section, s_param, inputs["wall_thickness"]) + outputs["rho_full"] = util.sectionalInterp(s_section, s_param, inputs["rho"]) + outputs["E_full"] = util.sectionalInterp(s_section, s_param, inputs["E"]) + outputs["G_full"] = util.sectionalInterp(s_section, s_param, inputs["G"]) + outputs["sigma_y_full"] = util.sectionalInterp(s_section, s_param, inputs["sigma_y"]) + outputs["unit_cost_full"] = util.sectionalInterp(s_section, s_param, inputs["unit_cost"]) + outputs["outfitting_full"] = util.sectionalInterp(s_section, s_param, inputs["outfitting_factor"]) + outputs["nu_full"] = 0.5 * outputs["E_full"] / outputs["G_full"] - 1.0 + + +class MemberComponent(om.ExplicitComponent): + """ + Convert the YAML inputs into more native and easy to use variables. + + Parameters + ---------- + joint1 : numpy array[3], [m] + Global dimensional coordinates (x-y-z) for bottom node of member + joint2 : numpy array[3], [m] + Global dimensional coordinates (x-y-z) for top node of member + grid_axial_joints : numpy array[n_axial] + non-dimensional locations along member for named axial joints + height : float, [m] + Scalar of the member height computed along the z axis. + s_full : numpy array[n_full] + non-dimensional locations along member + z_full : numpy array[n_full], [m] + dimensional locations along member axis + d_full : numpy array[n_full], [m] + cylinder diameter at corresponding locations + t_full : numpy array[n_full-1], [m] + shell thickness at corresponding locations + E_full : numpy array[n_full-1], [Pa] + Isotropic Youngs modulus of the materials along the member sections. + G_full : numpy array[n_full-1], [Pa] + Isotropic shear modulus of the materials along the member sections. + rho_full : numpy array[n_full-1], [kg/m**3] + Density of the materials along the member sections. + unit_cost_full : numpy array[n_full-1], [USD/kg] + Unit costs of the materials along the member sections. + outfitting_full : numpy array[n_full-1] + Additional outfitting multiplier in each section + labor_cost_rate : float, [USD/min] + Labor cost rate + painting_cost_rate : float, [USD/m/m] + Painting / surface finishing cost rate + bulkhead_grid : numpy array[n_bulk] + Non-dimensional locations of the bulkheads + bulkhead_thickness : numpy array[n_bulk], [m] + Thickness of the bulkheads at the gridded locations + ring_stiffener_web_height : float, [m] + height of stiffener web (base of T) + ring_stiffener_web_thickness : float, [m] + thickness of stiffener web (base of T) + ring_stiffener_flange_width : float, [m] + height of stiffener flange (top of T) + ring_stiffener_flange_thickness : float, [m] + thickness of stiffener flange (top of T) + ring_stiffener_spacing : float, [m] + Axial distance from one ring stiffener to another + axial_stiffener_web_height : float, [m] + height of stiffener web (base of T) + axial_stiffener_web_thickness : float, [m] + thickness of stiffener web (base of T) + axial_stiffener_flange_width : float, [m] + height of stiffener flange (top of T) + axial_stiffener_flange_thickness : float, [m] + thickness of stiffener flange (top of T) + axial_stiffener_spacing : float, [rad] + Angular distance from one axial stiffener to another + ballast_grid : numpy array[n_ballast,2] + Non-dimensional start and end points for each ballast segment + ballast_density : numpy array[n_ballast], [kg/m**3] + density of ballast material + ballast_volume : numpy array[n_ballast], [m**3] + Volume of ballast segments. Should be non-zero for permanent ballast, zero for variable ballast + ballast_unit_cost : numpy array[n_ballast], [USD/kg] + Cost per unit mass of ballast + + Returns + ------- + shell_cost : float, [USD] + Outer shell cost + shell_mass : float, [kg] + Outer shell mass + shell_z_cg : float, [m] + z-position of center of mass of member shell + shell_I_base : numpy array[6], [kg*m**2] + mass moment of inertia of shell about base [xx yy zz xy xz yz] + bulkhead_mass : float, [kg] + mass of column bulkheads + bulkhead_z_cg : float, [m] + z-coordinate of center of gravity for all bulkheads + bulkhead_cost : float, [USD] + cost of column bulkheads + bulkhead_I_base : numpy array[6], [kg*m**2] + Moments of inertia of bulkheads relative to keel point + stiffener_mass : float, [kg] + mass of column stiffeners + stiffener_cost : float, [USD] + cost of column stiffeners + stiffener_z_cg : float, [m] + z-coordinate of center of gravity for all ring stiffeners + stiffener_I_base : numpy array[6], [kg*m**2] + Moments of inertia of stiffeners relative to base point + flange_spacing_ratio : float + ratio between flange and stiffener spacing + stiffener_radius_ratio : numpy array[n_full-1] + ratio between stiffener height and radius + ballast_cost : float, [USD] + cost of permanent ballast + ballast_mass : float, [kg] + mass of permanent ballast + ballast_z_cg : float, [m] + z-coordinate or permanent ballast center of gravity + ballast_I_base : numpy array[6], [kg*m**2] + Moments of inertia of permanent ballast relative to bottom point + variable_ballast_capacity : float, [m] + inner radius of column at potential ballast mass + constr_ballast_capacity : numpy array[n_ballast] + Used ballast volume relative to total capacity, must be <= 1.0 + total_mass : float, [kg] + Total mass of member, including permanent ballast, but without variable ballast + total_cost : float, [USD] + Total cost of member, including permanent ballast + structural_mass : float, [kg] + Total structural mass of member, which does NOT include ballast + structural_cost : float, [USD] + Total structural cost of member, which does NOT include ballast + z_cg : float, [m] + z-coordinate of center of gravity for the complete member, including permanent ballast but not variable ballast + I_total : numpy array[6], [kg*m**2] + Moments of inertia of member at the center of mass + s_all : numpy array[npts] + Final non-dimensional points of all internal member nodes + center_of_mass : numpy array[3], [m] + Global dimensional coordinates (x-y-z) for member center of mass / gravity + nodes_xyz : numpy array[npts,3], [m] + Global dimensional coordinates (x-y-z) for all member nodes in s_all output + section_D : numpy array[npts-1], [m] + Cross-sectional diameter of all member segments + section_t : numpy array[npts-1], [m] + Cross-sectional effective thickness of all member segments + section_A : numpy array[npts-1], [m**2] + Cross-sectional area of all member segments + section_Asx : numpy array[npts-1], [m**2] + Cross-sectional shear area in x-direction (member c.s.) of all member segments + section_Asy : numpy array[npts-1], [m**2] + Cross-sectional shear area in y-direction (member c.s.) of all member segments + section_Ixx : numpy array[npts-1], [kg*m**2] + Cross-sectional moment of inertia about x-axis in member c.s. of all member segments + section_Iyy : numpy array[npts-1], [kg*m**2] + Cross-sectional moment of inertia about y-axis in member c.s. of all member segments + section_Izz : numpy array[npts-1], [kg*m**2] + Cross-sectional moment of inertia about z-axis in member c.s. of all member segments + section_rho : numpy array[npts-1], [kg/m**3] + Cross-sectional density of all member segments + section_E : numpy array[npts-1], [Pa] + Cross-sectional Young's modulus (of elasticity) of all member segments + section_G : numpy array[npts-1], [Pa] + Cross-sectional shear modulus all member segments + + """ + + def initialize(self): + self.options.declare("options") + self.options.declare("idx") + self.options.declare("n_refine", default=NREFINE) + + def setup(self): + opt = self.options["options"] + idx = self.options["idx"] + n_height = opt["n_height"][idx] + n_full = get_nfull(n_height, nref=self.options["n_refine"]) + n_axial = opt["n_axial_joints"][idx] + n_bulk = opt["n_bulkheads"][idx] + n_ball = opt["n_ballasts"][idx] + + # Initialize dictionary that will keep our member nodes so we can convert to OpenFAST format + self.sections = SortedDict() + + # Inputs + self.add_input("joint1", val=np.zeros(3), units="m") + self.add_input("joint2", val=np.zeros(3), units="m") + self.add_input("height", val=0.0, units="m") + self.add_input("s_full", np.zeros(n_full), units="m") + self.add_input("z_full", np.zeros(n_full), units="m") + self.add_input("d_full", np.zeros(n_full), units="m") + self.add_input("t_full", np.zeros(n_full - 1), units="m") + self.add_input("E_full", val=np.zeros(n_full - 1), units="Pa") + self.add_input("G_full", val=np.zeros(n_full - 1), units="Pa") + self.add_input("rho_full", val=np.zeros(n_full - 1), units="kg/m**3") + self.add_input("unit_cost_full", val=np.zeros(n_full - 1), units="USD/kg") + self.add_input("outfitting_full", val=np.ones(n_full - 1)) + self.add_input("labor_cost_rate", 0.0, units="USD/min") + self.add_input("painting_cost_rate", 0.0, units="USD/m/m") + + self.add_input("grid_axial_joints", np.zeros(n_axial)) + + self.add_input("bulkhead_grid", np.zeros(n_bulk)) + self.add_input("bulkhead_thickness", np.zeros(n_bulk), units="m") + + self.add_input("ring_stiffener_web_height", 0.0, units="m") + self.add_input("ring_stiffener_web_thickness", 0.0, units="m") + self.add_input("ring_stiffener_flange_width", 1e-6, units="m") + self.add_input("ring_stiffener_flange_thickness", 0.0, units="m") + self.add_input("ring_stiffener_spacing", 1000.0, units="m") + + self.add_input("axial_stiffener_web_height", 0.0, units="m") + self.add_input("axial_stiffener_web_thickness", 0.0, units="m") + self.add_input("axial_stiffener_flange_width", 1e-6, units="m") + self.add_input("axial_stiffener_flange_thickness", 0.0, units="m") + self.add_input("axial_stiffener_spacing", 1000.0, units="m") + + self.add_input("ballast_grid", np.zeros((n_ball, 2))) + self.add_input("ballast_density", np.zeros(n_ball), units="kg/m**3") + self.add_input("ballast_volume", np.zeros(n_ball), units="m**3") + self.add_input("ballast_unit_cost", np.zeros(n_ball), units="USD/kg") + + self.add_input("s_ghost1", 0.0) + self.add_input("s_ghost2", 1.0) + + # Outputs + self.add_output("shell_cost", val=0.0, units="USD") + self.add_output("shell_mass", val=0.0, units="kg") + self.add_output("shell_z_cg", val=0.0, units="m") + self.add_output("shell_I_base", np.zeros(6), units="kg*m**2") + + self.add_output("bulkhead_mass", 0.0, units="kg") + self.add_output("bulkhead_z_cg", 0.0, units="m") + self.add_output("bulkhead_cost", 0.0, units="USD") + self.add_output("bulkhead_I_base", np.zeros(6), units="kg*m**2") + + self.add_output("stiffener_mass", 0.0, units="kg") + self.add_output("stiffener_z_cg", 0.0, units="m") + self.add_output("stiffener_cost", 0.0, units="USD") + self.add_output("stiffener_I_base", np.zeros(6), units="kg*m**2") + self.add_output("flange_spacing_ratio", 0.0) + self.add_output("stiffener_radius_ratio", NULL * np.ones(MEMMAX)) + + self.add_output("ballast_cost", 0.0, units="USD") + self.add_output("ballast_mass", 0.0, units="kg") + self.add_output("ballast_z_cg", 0.0, units="m") + self.add_output("ballast_I_base", np.zeros(6), units="kg*m**2") + self.add_output("variable_ballast_capacity", 0.0, units="m") + self.add_output("constr_ballast_capacity", np.zeros(n_ball), units="m") + + self.add_output("total_mass", 0.0, units="kg") + self.add_output("total_cost", 0.0, units="USD") + self.add_output("structural_mass", 0.0, units="kg") + self.add_output("structural_cost", 0.0, units="USD") + self.add_output("z_cg", 0.0, units="m") + self.add_output("I_total", np.zeros(6), units="kg*m**2") + + self.add_output("s_all", NULL * np.ones(MEMMAX)) + self.add_output("center_of_mass", np.zeros(3), units="m") + self.add_output("nodes_r", np.zeros(MEMMAX), units="m") + self.add_output("nodes_xyz", NULL * np.ones((MEMMAX, 3)), units="m") + self.add_output("section_D", NULL * np.ones(MEMMAX), units="m") + self.add_output("section_t", NULL * np.ones(MEMMAX), units="m") + self.add_output("section_A", NULL * np.ones(MEMMAX), units="m**2") + self.add_output("section_Asx", NULL * np.ones(MEMMAX), units="m**2") + self.add_output("section_Asy", NULL * np.ones(MEMMAX), units="m**2") + self.add_output("section_Ixx", NULL * np.ones(MEMMAX), units="kg*m**2") + self.add_output("section_Iyy", NULL * np.ones(MEMMAX), units="kg*m**2") + self.add_output("section_Izz", NULL * np.ones(MEMMAX), units="kg*m**2") + self.add_output("section_rho", NULL * np.ones(MEMMAX), units="kg/m**3") + self.add_output("section_E", NULL * np.ones(MEMMAX), units="Pa") + self.add_output("section_G", NULL * np.ones(MEMMAX), units="Pa") + + def add_node(self, s_new): + # Quit if node already exists + if s_new in self.sections: + # print('Node already exists,',s_new) + return + + # Find section we will be interrupting + idx = self.sections.bisect_left(s_new) - 1 + if idx < 0: + raise ValueError("Cannot insert node before start of list") + + keys_orig = self.sections.keys() + self.sections[s_new] = copy.copy(self.sections[keys_orig[idx]]) + + def insert_section(self, s0, s1, cross_section0): + + idx0 = self.sections.bisect_left(s0) + idx1 = self.sections.bisect_left(s1) + keys_orig = self.sections.keys() + + # Be sure to add new node with old section before adding new section + self.add_node(s1) + + # If we are straddling an old point, have to convert that to the new section + if idx0 != idx1: + self.sections[keys_orig[idx0]] = cross_section0 + + # Add new section + # if s0 in self.sections: + # print('Node already exists,',s0) + self.sections[s0] = cross_section0 + + def add_section(self, s0, s1, cross_section0): + self.sections[s0] = cross_section0 + self.sections[s1] = None + + def compute(self, inputs, outputs): + + self.add_main_sections(inputs, outputs) + self.add_bulkhead_sections(inputs, outputs) + self.add_ring_stiffener_sections(inputs, outputs) + self.add_ballast_sections(inputs, outputs) + self.compute_mass_properties(inputs, outputs) + self.nodal_discretization(inputs, outputs) + + def add_main_sections(self, inputs, outputs): + # Unpack inputs + s_full = inputs["s_full"] + t_full = inputs["t_full"] + d_full = inputs["d_full"] + rho = inputs["rho_full"] + Emat = inputs["E_full"] + Gmat = inputs["G_full"] + coeff = inputs["outfitting_full"] + + # Add sections for structural analysis + # TODO: Longitudinal stiffeners + d_sec, _ = util.nodal2sectional(d_full) + for k in range(len(s_full) - 1): + itube = cs.Tube(d_sec[k], t_full[k]) + iprop = CrossSection( + D=d_sec[k], + t=coeff[k] * t_full[k], + A=coeff[k] * itube.Area, + Ixx=coeff[k] * itube.Jxx, + Iyy=coeff[k] * itube.Jyy, + Izz=coeff[k] * itube.J0, + Asx=itube.Asx, + Asy=itube.Asy, + rho=rho[k], + E=Emat[k], + G=Gmat[k], + ) + self.add_section(s_full[k], s_full[k + 1], iprop) + + # Adjust for ghost sections + s_ghost1 = float(inputs["s_ghost1"]) + s_ghost2 = float(inputs["s_ghost2"]) + if s_ghost1 > 0.0: + self.add_node(s_ghost1) + for s in self.sections: + if s >= s_ghost1: + break + self.sections[s].rho = 1e-2 + self.sections[s].E *= 1e2 + self.sections[s].G *= 1e2 + if s_ghost2 < 1.0: + self.add_node(s_ghost2) + for s in self.sections: + if s < s_ghost2 or s == 1.0: + continue + self.sections[s].rho = 1e-2 + self.sections[s].E *= 1e2 + self.sections[s].G *= 1e2 + + # Shell mass properties with new interpolation in case ghost nodes were added + s_grid = np.array(list(self.sections.keys())) + s_section = 0.5 * (s_grid[:-1] + s_grid[1:]) + R = np.interp(s_grid, s_full, 0.5 * d_full) + Rb = R[:-1] + Rt = R[1:] + zz = np.interp(s_grid, s_full, inputs["z_full"]) + H = np.diff(zz) + t_full = util.sectionalInterp(s_section, s_full, inputs["t_full"]) + rho = util.sectionalInterp(s_section, s_full, inputs["rho_full"]) + rho[s_section < s_ghost1] = 0.0 + rho[s_section > s_ghost2] = 0.0 + coeff = util.sectionalInterp(s_section, s_full, coeff) + k_m = util.sectionalInterp(s_section, s_full, inputs["unit_cost_full"]) + + # Total mass of cylinder + V_shell = frustum.frustumShellVol(Rb, Rt, t_full, H) + mass = coeff * rho * V_shell + outputs["shell_mass"] = mass.sum() + + # Center of mass + cm_section = zz[:-1] + frustum.frustumShellCG(Rb, Rt, t_full, H) + outputs["shell_z_cg"] = np.dot(cm_section, mass) / mass.sum() + + # Moments of inertia + Izz_section = coeff * rho * frustum.frustumShellIzz(Rb, Rt, t_full, H) + Ixx_section = Iyy_section = coeff * rho * frustum.frustumShellIxx(Rb, Rt, t_full, H) + + # Sum up each cylinder section using parallel axis theorem + I_base = np.zeros((3, 3)) + for k in range(Izz_section.size): + R = np.array([0.0, 0.0, cm_section[k] - zz[0]]) + Icg = util.assembleI([Ixx_section[k], Iyy_section[k], Izz_section[k], 0.0, 0.0, 0.0]) + + I_base += Icg + mass[k] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) + + outputs["shell_I_base"] = util.unassembleI(I_base) + + # Compute costs based on "Optimum Design of Steel Structures" by Farkas and Jarmai + # All dimensions for correlations based on mm, not meters. + R_ave = 0.5 * (Rb + Rt) + taper = np.minimum(Rb / Rt, Rt / Rb) + nsec = t_full.size + mshell = rho * V_shell + mshell_tot = np.sum(rho * V_shell) + # k_m = inputs["unit_cost_full"] # 1.1 # USD / kg carbon steel plate + k_f = inputs["labor_cost_rate"] # 1.0 # USD / min labor + k_p = inputs["painting_cost_rate"] # USD / m^2 painting + k_e = 0.064 # Industrial electricity rate $/kWh https://www.eia.gov/electricity/monthly/epm_table_grapher.php?t=epmt_5_6_a + e_f = 15.9 # Electricity usage kWh/kg for steel + e_fo = 26.9 # Electricity usage kWh/kg for stainless steel + + # Cost Step 1) Cutting flat plates for taper using plasma cutter + cutLengths = 2.0 * np.sqrt((Rt - Rb) ** 2.0 + H ** 2.0) # Factor of 2 for both sides + # Cost Step 2) Rolling plates + # Cost Step 3) Welding rolled plates into shells (set difficulty factor based on tapering with logistic function) + theta_F = 4.0 - 3.0 / (1 + np.exp(-5.0 * (taper - 0.75))) + # Cost Step 4) Circumferential welds to join cans together + theta_A = 2.0 + + # Labor-based expenses + K_f = k_f * ( + manufacture.steel_cutting_plasma_time(cutLengths, t_full) + + manufacture.steel_rolling_time(theta_F, R_ave, t_full) + + manufacture.steel_butt_welding_time(theta_A, nsec, mshell_tot, cutLengths, t_full) + + manufacture.steel_butt_welding_time(theta_A, nsec, mshell_tot, 2 * np.pi * Rb[1:], t_full[1:]) + ) + + # Cost step 5) Painting- outside and inside + theta_p = 2 + K_p = k_p * theta_p * 2 * (2 * np.pi * R_ave * H).sum() + + # Cost step 6) Outfitting with electricity usage + K_o = np.sum(1.5 * k_m * (coeff - 1.0) * mshell) + + # Material cost with waste fraction, but without outfitting, + K_m = 1.21 * np.sum(k_m * mshell) + + # Electricity usage + K_e = np.sum(k_e * (e_f * mshell + e_fo * (coeff - 1.0) * mshell)) + + # Assemble all costs for now + tempSum = K_m + K_e + K_o + K_p + K_f + + # Capital cost share from BLS MFP by NAICS + K_c = 0.118 * tempSum / (1.0 - 0.118) + + outputs["shell_cost"] = tempSum + K_c + + def add_bulkhead_sections(self, inputs, outputs): + # Unpack variables + s_full = inputs["s_full"] + z_full = inputs["z_full"] + R_od = 0.5 * inputs["d_full"] + twall = inputs["t_full"] + rho = inputs["rho_full"] + E = inputs["E_full"] + G = inputs["G_full"] + s_bulk = inputs["bulkhead_grid"] + t_bulk = inputs["bulkhead_thickness"] + coeff = inputs["outfitting_full"] + L = inputs["height"] + s_ghost1 = float(inputs["s_ghost1"]) + s_ghost2 = float(inputs["s_ghost2"]) + nbulk = s_bulk.size + if nbulk == 0: + return + + # Make sure we are not working in ghost regions + s_bulk = np.unique(np.minimum(np.maximum(s_bulk, s_ghost1), s_ghost2)) + + # Get z and R_id values of bulkhead locations + z_bulk = np.interp(s_bulk, s_full, z_full) + twall_bulk = util.sectionalInterp(s_bulk, s_full, twall) + rho_bulk = util.sectionalInterp(s_bulk, s_full, rho) + E_bulk = util.sectionalInterp(s_bulk, s_full, E) + G_bulk = util.sectionalInterp(s_bulk, s_full, G) + coeff_bulk = util.sectionalInterp(s_bulk, s_full, coeff) + R_od_bulk = np.interp(s_bulk, s_full, R_od) + R_id_bulk = R_od_bulk - twall_bulk + + # Add bulkhead sections: assumes bulkhead and shell are made of same material! + s0 = s_bulk - 0.5 * t_bulk / L + s1 = s_bulk + 0.5 * t_bulk / L + if s0[0] < s_ghost1: + s0[0] = s_ghost1 + s1[0] = s_ghost1 + t_bulk[0] / L + if s1[-1] > s_ghost2: + s0[-1] = s_ghost2 - t_bulk[-1] / L + s1[-1] = s_ghost2 + for k in range(nbulk): + itube = cs.Tube(2 * R_od_bulk[k], R_od_bulk[k]) # thickness=radius for solid disk + iprop = CrossSection( + D=2 * R_od_bulk[k], + t=R_od_bulk[k], + A=itube.Area, + Ixx=itube.Jxx, + Iyy=itube.Jyy, + Izz=itube.J0, + Asx=itube.Asx, + Asy=itube.Asy, + rho=coeff_bulk[k] * rho_bulk[k], + E=E_bulk[k], + G=G_bulk[k], + ) + self.insert_section(s0[k], s1[k], iprop) + + # Compute bulkhead mass independent of shell + m_bulk = coeff_bulk * rho_bulk * np.pi * R_id_bulk ** 2 * t_bulk + outputs["bulkhead_mass"] = m_bulk.sum() + + z_cg = 0.0 if outputs["bulkhead_mass"] == 0.0 else np.dot(z_bulk, m_bulk) / m_bulk.sum() + outputs["bulkhead_z_cg"] = z_cg + + # Compute moments of inertia at keel + # Assume bulkheads are just simple thin discs with radius R_od-t_wall and mass already computed + Izz = 0.5 * m_bulk * R_id_bulk ** 2 + Ixx = Iyy = 0.5 * Izz + dz = z_bulk - z_full[0] + I_keel = np.zeros((3, 3)) + for k in range(nbulk): + R = np.array([0.0, 0.0, dz[k]]) + Icg = util.assembleI([Ixx[k], Iyy[k], Izz[k], 0.0, 0.0, 0.0]) + I_keel += Icg + m_bulk[k] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) + + outputs["bulkhead_I_base"] = util.unassembleI(I_keel) + + # Compute costs based on "Optimum Design of Steel Structures" by Farkas and Jarmai + # All dimensions for correlations based on mm, not meters. + k_m = util.sectionalInterp(s_bulk, s_full, inputs["unit_cost_full"]) + k_f = inputs["labor_cost_rate"] # 1.0 # USD / min labor + k_p = inputs["painting_cost_rate"] # USD / m^2 painting + m_shell = outputs["shell_mass"] + + # Cost Step 1) Cutting flat plates using plasma cutter + cutLengths = 2.0 * np.pi * R_id_bulk + # Cost Step 2) Fillet welds with GMAW-C (gas metal arc welding with CO2) of bulkheads to shell + theta_w = 3.0 # Difficulty factor + + # Labor-based expenses + K_f = k_f * ( + manufacture.steel_cutting_plasma_time(cutLengths, t_bulk) + + manufacture.steel_filett_welding_time(theta_w, nbulk, m_bulk + m_shell, 2 * np.pi * R_id_bulk, t_bulk) + ) + + # Cost Step 3) Painting (two sided) + theta_p = 1.0 + K_p = k_p * theta_p * 2 * (np.pi * R_id_bulk ** 2.0).sum() + + # Material cost, without outfitting + K_m = np.sum(k_m * m_bulk) + + # Total cost + c_bulk = K_m + K_f + K_p + outputs["bulkhead_cost"] = c_bulk + + def add_ring_stiffener_sections(self, inputs, outputs): + # Unpack variables + s_full = inputs["s_full"] + z_full = inputs["z_full"] + L = inputs["height"] + R_od = 0.5 * inputs["d_full"] + twall = inputs["t_full"] + rho = inputs["rho_full"] + E = inputs["E_full"] + G = inputs["G_full"] + coeff = inputs["outfitting_full"] + s_bulk = inputs["bulkhead_grid"] + s_ghost1 = float(inputs["s_ghost1"]) + s_ghost2 = float(inputs["s_ghost2"]) + + t_web = inputs["ring_stiffener_web_thickness"] + t_flange = inputs["ring_stiffener_flange_thickness"] + h_web = inputs["ring_stiffener_web_height"] + w_flange = inputs["ring_stiffener_flange_width"] + L_stiffener = inputs["ring_stiffener_spacing"] + + n_stiff = 0 if L_stiffener == 0.0 else int(np.floor(L / L_stiffener)) + if n_stiff == 0: + return + + web_frac = t_web / w_flange + + # Calculate stiffener spots along the member axis and deconflict with bulkheads + s_stiff = (np.arange(1, n_stiff + 0.1) - 0.5) * (L_stiffener / L) + + # Make sure we are not working in ghost regions + s_stiff = s_stiff[s_stiff > s_ghost1] + s_stiff = s_stiff[s_stiff < s_ghost2] + n_stiff = s_stiff.size + + tol = w_flange / L + for k, s in enumerate(s_stiff): + while np.any(np.abs(s_bulk - s) <= tol) and s > tol: + s -= tol + s_stiff[k] = s + + s0 = s_stiff - 0.5 * w_flange / L + s1 = s_stiff + 0.5 * w_flange / L + if s0[0] < 0.0: + s0[0] = 0.0 + s1[0] = w_flange / L + if s1[-1] > 1.0: + s0[-1] = 1 - w_flange / L + s1[-1] = 1.0 + + # Get z and R_id values of bulkhead locations + z_stiff = np.interp(s_stiff, s_full, z_full) + twall_stiff = util.sectionalInterp(s_stiff, s_full, twall) + rho_stiff = util.sectionalInterp(s_stiff, s_full, rho) + E_stiff = util.sectionalInterp(s_stiff, s_full, E) + G_stiff = util.sectionalInterp(s_stiff, s_full, G) + coeff_stiff = util.sectionalInterp(s_stiff, s_full, coeff) + R_od_stiff = np.interp(s_stiff, s_full, R_od) + R_id_stiff = R_od_stiff - twall_stiff + + # Create some constraints for reasonable stiffener designs for an optimizer + outputs["flange_spacing_ratio"] = w_flange / (0.5 * L_stiffener) + outputs["stiffener_radius_ratio"] = NULL * np.ones(MEMMAX) + outputs["stiffener_radius_ratio"][:n_stiff] = (h_web + t_flange + twall_stiff) / R_od_stiff + + # Outer and inner radius of web by section + R_wo = R_id_stiff + R_wi = R_wo - h_web + # Outer and inner radius of flange by section + R_fo = R_wi + R_fi = R_fo - t_flange + + # Make stiffener sections + for k in range(n_stiff): + ishell = cs.Tube(2 * R_od_stiff[k], twall_stiff[k]) + iweb = cs.Tube(2 * R_wo[k], h_web) + iflange = cs.Tube(2 * R_fo[k], t_flange) + Ak = coeff_stiff[k] * ishell.Area + iflange.Area + iweb.Area * web_frac + # Find effective thickness for OpenFAST + t_eff = R_od_stiff[k] - np.sqrt(R_od_stiff[k] ** 2 - Ak / np.pi) + iprop = CrossSection( + D=2 * R_od_stiff[k], + t=t_eff, + A=Ak, + Ixx=coeff_stiff[k] * ishell.Jxx + iflange.Jxx + iweb.Jxx * web_frac, + Iyy=coeff_stiff[k] * ishell.Jyy + iflange.Jyy + iweb.Jyy * web_frac, + Izz=coeff_stiff[k] * ishell.J0 + iflange.J0 + iweb.J0 * web_frac, + Asx=ishell.Asx + iflange.Asx + iweb.Asx * web_frac, + Asy=ishell.Asy + iflange.Asy + iweb.Asy * web_frac, + rho=rho_stiff[k], + E=E_stiff[k], + G=G_stiff[k], + ) + self.insert_section(s0[k], s1[k], iprop) + + # Material volumes by section + V_web = np.pi * (R_wo ** 2 - R_wi ** 2) * t_web + V_flange = np.pi * (R_fo ** 2 - R_fi ** 2) * w_flange + + # Ring mass by volume by section + m_web = rho_stiff * V_web + m_flange = rho_stiff * V_flange + m_ring = m_web + m_flange + outputs["stiffener_mass"] = m_ring.sum() + outputs["stiffener_z_cg"] = np.dot(z_stiff, m_ring) / m_ring.sum() + + # Compute moments of inertia for stiffeners (lumped by section for simplicity) at keel + I_web = I_cyl(R_wi, R_wo, t_web, m_web) + I_flange = I_cyl(R_fi, R_fo, w_flange, m_flange) + I_keel = np.zeros((3, 3)) + for k in range(n_stiff): + R = np.array([0.0, 0.0, (z_stiff[k] - z_full[0])]) + I_ring = util.assembleI(I_web[k, :] + I_flange[k, :]) + I_keel += I_ring + m_ring[k] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) + outputs["stiffener_I_base"] = util.unassembleI(I_keel) + + # Compute costs based on "Optimum Design of Steel Structures" by Farkas and Jarmai + # All dimensions for correlations based on mm, not meters. + k_m = util.sectionalInterp(s_stiff, s_full, inputs["unit_cost_full"]) + k_f = inputs["labor_cost_rate"] # 1.0 # USD / min labor + k_p = inputs["painting_cost_rate"] # USD / m^2 painting + m_shell = outputs["shell_mass"] + + # Cost Step 1) Cutting stiffener strips from flat plates using plasma cutter + cutLengths_w = 2.0 * np.pi * 0.5 * (R_wo + R_wi) + cutLengths_f = 2.0 * np.pi * R_fo + # Cost Step 2) Welding T-stiffeners together GMAW-C (gas metal arc welding with CO2) fillet welds + theta_w = 3.0 # Difficulty factor + # Cost Step 3) Welding stiffeners to shell GMAW-C (gas metal arc welding with CO2) fillet welds + # Will likely fillet weld twice (top & bottom), so factor of 2 on second welding terms + + # Labor-based expenses + K_f = k_f * ( + manufacture.steel_cutting_plasma_time(cutLengths_w.sum(), t_web) + + manufacture.steel_cutting_plasma_time(cutLengths_f.sum(), t_flange) + + manufacture.steel_filett_welding_time(theta_w, 1, m_ring, 2 * np.pi * R_fo, t_web) + + manufacture.steel_filett_welding_time(theta_w, 1, m_ring + m_shell, 2 * np.pi * R_wo, t_web) + ) + + # Cost Step 4) Painting + theta_p = 2.0 + K_p = ( + k_p + * theta_p + * 2 + * np.pi + * np.sum(R_wo ** 2.0 - R_wi ** 2.0 + 0.5 * (R_fo + R_fi) * (2 * w_flange + 2 * t_flange) - R_fo * t_web) + ) + + # Material cost, without outfitting + K_m = np.sum(k_m * m_ring) + + # Total cost + c_ring = K_m + K_f + K_p + outputs["stiffener_cost"] = c_ring + + def add_ballast_sections(self, inputs, outputs): + # Unpack variables + s_full = inputs["s_full"] + z_full = inputs["z_full"] + R_od = 0.5 * inputs["d_full"] + twall = inputs["t_full"] + s_ballast = inputs["ballast_grid"] + rho_ballast = inputs["ballast_density"] + V_ballast = inputs["ballast_volume"] + km_ballast = inputs["ballast_unit_cost"] + s_ghost1 = float(inputs["s_ghost1"]) + s_ghost2 = float(inputs["s_ghost2"]) + n_ballast = len(V_ballast) + if n_ballast == 0: + return + + # Move away from ghost regions + s_ballast += s_ghost1 + s_ballast = np.minimum(s_ballast, s_ghost2) + + # Number of points for volume integration + npts = 10 + m_ballast = rho_ballast * V_ballast + I_ballast = np.zeros(6) + z_cg = np.zeros(n_ballast) + V_avail = np.zeros(n_ballast) + for k in range(n_ballast): + # Find geometry of the ballast space + sinterp = np.linspace(s_ballast[k, 0], s_ballast[k, 1], npts) + zpts = np.interp(sinterp, s_full, z_full) + H = zpts[-1] - zpts[0] + R_od_pts = np.interp(sinterp, s_full, R_od) + twall_pts = util.sectionalInterp(sinterp, s_full, twall) + R_id_pts = R_od_pts - twall_pts + + # Available volume in this ballast space + V_pts = frustum.frustumVol(R_id_pts[:-1], R_id_pts[1:], np.diff(zpts)) + V_avail[k] = V_pts.sum() + + # Augment density for these sections (should already be bulkheads at boundaries) + for s in self.sections: + if s >= s_ballast[k, 0] and s < s_ballast[k, 1]: + self.sections[s].rho += m_ballast[k] / self.sections[s].A / H + + # If permanent ballast, compute mass properties, but have to find where ballast extends to in cavity + if V_ballast[k] > 0.0: + z_end = np.interp(V_ballast[k], np.cumsum(np.r_[0, V_pts]), zpts) + zpts = np.linspace(zpts[0], z_end, npts) + H = np.diff(zpts) + + R_od_pts = np.interp(zpts, z_full, R_od) + twall_pts = util.sectionalInterp(zpts, z_full, twall) + R_id_pts = R_od_pts - twall_pts + + V_pts = frustum.frustumVol(R_id_pts[:-1], R_id_pts[1:], H) + cg_pts = frustum.frustumCG(R_id_pts[:-1], R_id_pts[1:], H) + zpts[:-1] + z_cg[k] = np.dot(cg_pts, V_pts) / V_pts.sum() + + Ixx = Iyy = frustum.frustumIxx(R_id_pts[:-1], R_id_pts[1:], H) + Izz = frustum.frustumIzz(R_id_pts[:-1], R_id_pts[1:], H) + I_temp = np.zeros((3, 3)) + for ii in range(npts - 1): + R = np.array([0.0, 0.0, cg_pts[ii]]) + Icg = util.assembleI([Ixx[ii], Iyy[ii], Izz[ii], 0.0, 0.0, 0.0]) + I_temp += Icg + V_pts[ii] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) + I_ballast += rho_ballast[k] * util.unassembleI(I_temp) + else: + outputs["variable_ballast_capacity"] = V_avail[k] + + # Save permanent ballast mass and variable height + # TODO: Add the mass to sectional density? + outputs["ballast_mass"] = m_ballast.sum() + outputs["ballast_I_base"] = I_ballast + outputs["ballast_z_cg"] = np.dot(z_cg, m_ballast) / (m_ballast.sum() + eps) + outputs["ballast_cost"] = np.dot(km_ballast, m_ballast) + outputs["constr_ballast_capacity"] = V_ballast / V_avail + + def compute_mass_properties(self, inputs, outputs): + # Unpack variables + z_full = inputs["z_full"] + + z_shell = outputs["shell_z_cg"] + z_ballast = outputs["ballast_z_cg"] + z_bulkhead = outputs["bulkhead_z_cg"] + z_stiffener = outputs["stiffener_z_cg"] + + m_shell = outputs["shell_mass"] + m_ballast = outputs["ballast_mass"] + m_bulkhead = outputs["bulkhead_mass"] + m_stiffener = outputs["stiffener_mass"] + + c_shell = outputs["shell_cost"] + c_ballast = outputs["ballast_cost"] + c_bulkhead = outputs["bulkhead_cost"] + c_stiffener = outputs["stiffener_cost"] + + I_shell = outputs["shell_I_base"] + I_ballast = outputs["ballast_I_base"] + I_bulkhead = outputs["bulkhead_I_base"] + I_stiffener = outputs["stiffener_I_base"] + + # Find mass of all of the sub-components of the member + m_total = m_shell + m_ballast + m_bulkhead + m_stiffener + c_total = c_shell + c_ballast + c_bulkhead + c_stiffener + + # Masses assumed to be focused at section centroids + z_cg = ( + m_shell * z_shell + m_ballast * z_ballast + m_bulkhead * z_bulkhead + m_stiffener * z_stiffener + ) / m_total + + # Add up moments of inertia at keel, make sure to scale mass appropriately + I_total = I_shell + I_ballast + I_bulkhead + I_stiffener + + # Move moments of inertia from keel to cg + I_total -= m_total * ((z_cg - z_full[0]) ** 2.0) * np.r_[1.0, 1.0, np.zeros(4)] + + # Store outputs addressed so far + outputs["total_mass"] = m_total + outputs["structural_mass"] = m_total - m_ballast + outputs["z_cg"] = z_cg + outputs["I_total"] = I_total + outputs["total_cost"] = c_total + outputs["structural_cost"] = c_total - c_ballast + # outputs["cost_rate"] = c_total / m_total + + def nodal_discretization(self, inputs, outputs): + # Unpack inputs + s_full = inputs["s_full"] + d_full = inputs["d_full"] + z_full = inputs["z_full"] + s_axial = inputs["grid_axial_joints"] + xyz0 = inputs["joint1"] + xyz1 = inputs["joint2"] + dxyz = xyz1 - xyz0 + + # Add in axial nodes + for s in s_axial: + self.add_node(s) + + # Convert non-dimensional to dimensional + s_grid = np.array(list(self.sections.keys())) + r_grid = 0.5 * np.interp(s_grid, s_full, d_full) + n_nodes = s_grid.size + nodes = np.outer(s_grid, dxyz) + xyz0[np.newaxis, :] + + # Convert axial to absolute + outputs["center_of_mass"] = (outputs["z_cg"] / z_full[-1]) * dxyz + xyz0 + + # Store all nodes and sections + outputs["s_all"] = NULL * np.ones(MEMMAX) + outputs["nodes_r"] = NULL * np.ones(MEMMAX) + outputs["nodes_xyz"] = NULL * np.ones((MEMMAX, 3)) + outputs["section_D"] = NULL * np.ones(MEMMAX) + outputs["section_t"] = NULL * np.ones(MEMMAX) + outputs["section_A"] = NULL * np.ones(MEMMAX) + outputs["section_Asx"] = NULL * np.ones(MEMMAX) + outputs["section_Asy"] = NULL * np.ones(MEMMAX) + outputs["section_rho"] = NULL * np.ones(MEMMAX) + outputs["section_Ixx"] = NULL * np.ones(MEMMAX) + outputs["section_Iyy"] = NULL * np.ones(MEMMAX) + outputs["section_Izz"] = NULL * np.ones(MEMMAX) + outputs["section_E"] = NULL * np.ones(MEMMAX) + outputs["section_G"] = NULL * np.ones(MEMMAX) + + outputs["s_all"][:n_nodes] = s_grid + outputs["nodes_xyz"][:n_nodes, :] = nodes + outputs["nodes_r"][:n_nodes] = r_grid + + for k, s in enumerate(s_grid): + if s == s_grid[-1]: + continue + outputs["section_D"][k] = self.sections[s].D + outputs["section_t"][k] = self.sections[s].t + outputs["section_A"][k] = self.sections[s].A + outputs["section_Asx"][k] = self.sections[s].Asx + outputs["section_Asy"][k] = self.sections[s].Asy + outputs["section_rho"][k] = self.sections[s].rho + outputs["section_Ixx"][k] = self.sections[s].Ixx + outputs["section_Iyy"][k] = self.sections[s].Iyy + outputs["section_Izz"][k] = self.sections[s].Izz + outputs["section_E"][k] = self.sections[s].E + outputs["section_G"][k] = self.sections[s].G + + +class MemberHydro(om.ExplicitComponent): + """ + Compute member substructure elements in floating offshore wind turbines. + + Parameters + ---------- + rho_water : float, [kg/m**3] + density of water + s_full : numpy array[n_full], [m] + non-dimensional coordinates of section nodes + z_full : numpy array[n_full], [m] + z-coordinates of section nodes + d_full : numpy array[n_full], [m] + outer diameter at each section node bottom to top (length = nsection + 1) + s_all : numpy array[npts] + Final non-dimensional points of all internal member nodes + nodes_xyz : numpy array[npts,3], [m] + Global dimensional coordinates (x-y-z) for all member nodes in s_all output + + + Returns + ------- + center_of_buoyancy : numpy array[3], [m] + z-position CofB of member + displacement : float, [m**3] + Volume of water displaced by member + buoyancy_force : float, [N] + Net z-force from buoyancy on member + idx_cb : int + Index of closest node to center of buoyancy + Awater : float, [m**2] + Area of waterplace cross section + Iwater : float, [m**4] + Second moment of area of waterplace cross section + added_mass : numpy array[6], [kg] + hydrodynamic added mass matrix diagonal + + """ + + def initialize(self): + self.options.declare("n_height") + self.options.declare("n_refine", default=NREFINE) + + def setup(self): + n_height = self.options["n_height"] + n_full = get_nfull(n_height, nref=self.options["n_refine"]) + + # Variables local to the class and not OpenMDAO + self.ibox = None + + self.add_input("rho_water", 0.0, units="kg/m**3") + self.add_input("s_full", np.zeros(n_full), units="m") + self.add_input("z_full", np.zeros(n_full), units="m") + self.add_input("d_full", np.zeros(n_full), units="m") + self.add_input("s_all", NULL * np.ones(MEMMAX)) + self.add_input("nodes_xyz", NULL * np.ones((MEMMAX, 3)), units="m") + + self.add_output("center_of_buoyancy", np.zeros(3), units="m") + self.add_output("displacement", 0.0, units="m**3") + self.add_output("buoyancy_force", 0.0, units="N") + self.add_output("idx_cb", 0) + self.add_output("Awater", 0.0, units="m**2") + self.add_output("Iwater", 0.0, units="m**4") + self.add_output("added_mass", np.zeros(6), units="kg") + + def compute(self, inputs, outputs): + # Unpack variables + nnode = np.where(inputs["s_all"] == NULL)[0][0] + s_grid = inputs["s_all"][:nnode] + xyz = inputs["nodes_xyz"][:nnode, :] + s_full = inputs["s_full"] + z_full = inputs["z_full"] + R_od = 0.5 * inputs["d_full"] + rho_water = inputs["rho_water"] + xyz0 = xyz[0, :] + dxyz = xyz[-1, :] - xyz[0, :] + + # Compute volume of each section and mass of displaced water by section + # Find the radius at the waterline so that we can compute the submerged volume as a sum of frustum sections + if xyz[:, 2].min() < 0.0 and xyz[:, 2].max() > 0.0: + s_waterline = np.interp(0.0, xyz[:, 2], s_grid) + ind = np.where(xyz[:, 2] < 0.0)[0] + s_under = np.r_[s_grid[ind], s_waterline] + waterline = True + elif xyz[:, 2].max() < 0.0: + s_under = s_grid + waterline = False + r_waterline = 0.0 + else: + return + z_under = np.interp(s_under, s_full, z_full) + r_under = np.interp(s_under, s_full, R_od) + if waterline: + r_waterline = r_under[-1] + + # Submerged volume (with zero-padding) + dz = np.diff(z_under) + V_under = frustum.frustumVol(r_under[:-1], r_under[1:], dz) + V_under_tot = V_under.sum() + outputs["displacement"] = V_under_tot + outputs["buoyancy_force"] = rho_water * outputs["displacement"] * gravity + + # Compute Center of Buoyancy in z-coordinates (0=waterline) + # First get z-coordinates of CG of all frustums + z_cg_under = frustum.frustumCG(r_under[:-1], r_under[1:], dz) + z_under[:-1] + z_cb = np.dot(z_cg_under, V_under) / V_under_tot + s_cb = np.interp(z_cb, z_under, s_under) + cb = xyz0 + s_cb * dxyz + outputs["center_of_buoyancy"] = cb + outputs["idx_cb"] = util.closest_node(xyz, cb) + + # 2nd moment of area for circular cross section + # Note: Assuming Iwater here depends on "water displacement" cross-section + # and not actual moment of inertia type of cross section (thin hoop) + outputs["Iwater"] = 0.25 * np.pi * r_waterline ** 4.0 + outputs["Awater"] = np.pi * r_waterline ** 2.0 + + # Calculate diagonal entries of added mass matrix + temp = np.linspace(z_under[0], z_under[-1], 200) + r_under = np.interp(temp, z_under, r_under) + z_under = temp + m_a = np.zeros(6) + m_a[:2] = rho_water * V_under_tot # A11 surge, A22 sway + + Lxy = np.sqrt((xyz[:, 0].max() - xyz[:, 0].min()) ** 2 + (xyz[:, 1].max() - xyz[:, 1].min()) ** 2) + D = 2 * r_under.max() + Lxy = np.maximum(Lxy, D) + m_a[2] = (1.0 / 6.0) * rho_water * Lxy * D ** 2.0 # A33 heave + m_a[3:5] = ( + np.pi * rho_water * np.trapz((z_under - z_cb) ** 2.0 * r_under ** 2.0, z_under) + ) # A44 roll, A55 pitch + m_a[5] = 0.0 # A66 yaw + outputs["added_mass"] = m_a + + +class Member(om.Group): + def initialize(self): + self.options.declare("column_options") + self.options.declare("idx") + self.options.declare("n_mat") + + def setup(self): + opt = self.options["column_options"] + idx = self.options["idx"] + + n_height = opt["n_height"][idx] + n_refine = NREFINE if n_height > 2 else np.maximum(NREFINE, 2) + + # TODO: Use reference axis and curvature, s, instead of assuming everything is vertical on z + self.add_subsystem( + "yaml", DiscretizationYAML(options=opt, idx=idx, n_mat=self.options["n_mat"]), promotes=["*"] + ) + + self.add_subsystem( + "gc", GeometricConstraints(nPoints=n_height, diamFlag=True), promotes=["constr_taper", "constr_d_to_t"] + ) + self.connect("outer_diameter", "gc.d") + self.connect("wall_thickness", "gc.t") + + self.add_subsystem("geom", MemberDiscretization(n_height=n_height, n_refine=n_refine), promotes=["*"]) + + self.add_subsystem("comp", MemberComponent(options=opt, idx=idx, n_refine=n_refine), promotes=["*"]) + + self.add_subsystem("hydro", MemberHydro(n_height=n_height, n_refine=n_refine), promotes=["*"]) + + """ + # TODO: Get actual z coordinates into CylinderEnvironment + prom = [ + "Uref", + "zref", + "shearExp", + "z0", + "cd_usr", + "cm", + "beta_wind", + "rho_air", + "mu_air", + "beta_water", + "rho_water", + "mu_water", + "Uc", + "Hsig_wave", + "Tsig_wave", + "rho_water", + "water_depth", + "Px", + "Py", + "Pz", + "qdyn", + "yaw", + ] + self.add_subsystem("env", CylinderEnvironment(nPoints=n_full, water_flag=True), promotes=prom) + self.connect("z_full", "env.z") + self.connect("d_full", "env.d") + """ diff --git a/wisdem/floatingse/substructure.py b/wisdem/floatingse/substructure.py deleted file mode 100644 index a97f96539..000000000 --- a/wisdem/floatingse/substructure.py +++ /dev/null @@ -1,704 +0,0 @@ -import numpy as np -from scipy.integrate import cumtrapz -import openmdao.api as om - -from wisdem.commonse import gravity, eps, DirectionVector, NFREQ -from wisdem.commonse.utilities import assembleI, unassembleI -from wisdem.commonse.vertical_cylinder import get_nfull -from .map_mooring import NLINES_MAX - - -class SubstructureGeometry(om.ExplicitComponent): - """ - Component for substructure geometry for floating offshore wind turbines. - - Parameters - ---------- - main_d_full : numpy array[n_full_main], [m] - outer radius at each section node bottom to top (length = nsection + 1) - main_z_nodes : numpy array[n_full_main], [m] - z-coordinates of section nodes (length = nsection+1) - offset_d_full : numpy array[n_full_off], [m] - outer radius at each section node bottom to top (length = nsection + 1) - offset_z_nodes : numpy array[n_full_off], [m] - z-coordinates of section nodes (length = nsection+1) - offset_freeboard : float, [m] - Length of column above water line - offset_draft : float, [m] - Length of column below water line - fairlead_location : float - Fractional length from column bottom to top for mooring line attachment - fairlead_offset_from_shell : float, [m] - fairlead offset from shell - radius_to_offset_column : float, [m] - Distance from main column centerpoint to offset column centerpoint - number_of_offset_columns : float - Number of offset columns evenly spaced around main column - tower_d_base : float, [m] - base diameter of the tower - hsig_wave : float, [m] - significant wave height - max_survival_heel : float, [deg] - max heel angle for turbine survival - - Returns - ------- - fairlead : float, [m] - Depth below water line for mooring line attachment - fairlead_radius : float, [m] - Outer spar radius at fairlead depth (point of mooring attachment) - main_offset_spacing : float - Radius of main and offset columns relative to spacing - tower_transition_buffer : float, [m] - Buffer between substructure main and tower main - nacelle_transition_buffer : float, [m] - Buffer between tower top and nacelle main - offset_freeboard_heel_margin : float, [m] - Margin so offset column does not submerge during max heel - offset_draft_heel_margin : float, [m] - Margin so offset column does not leave water during max heel - wave_height_fairlead_ratio : float - Ratio of maximum wave height (avg of top 1%) to fairlead - - """ - - def initialize(self): - self.options.declare("n_height_main") - self.options.declare("n_height_off") - - def setup(self): - n_height_main = self.options["n_height_main"] - n_height_off = self.options["n_height_off"] - n_full_main = get_nfull(n_height_main) - n_full_off = get_nfull(n_height_off) - - self.add_input("main_d_full", np.zeros(n_full_main), units="m") - self.add_input("main_z_nodes", np.zeros(n_full_main), units="m") - self.add_input("offset_d_full", np.zeros(n_full_off), units="m") - self.add_input("offset_z_nodes", np.zeros(n_full_off), units="m") - self.add_input("offset_freeboard", 0.0, units="m") - self.add_input("offset_draft", 0.0, units="m") - self.add_input("fairlead_location", 0.0) - self.add_input("fairlead_offset_from_shell", 0.0, units="m") - self.add_input("radius_to_offset_column", 0.0, units="m") - self.add_input("number_of_offset_columns", 0) - self.add_input("tower_d_base", 0.0, units="m") - self.add_input("hsig_wave", 0.0, units="m") - self.add_input("max_survival_heel", 0.0, units="deg") - - self.add_output("fairlead", 0.0, units="m") - self.add_output("fairlead_radius", 0.0, units="m") - self.add_output("main_offset_spacing", 0.0) - self.add_output("tower_transition_buffer", 0.0, units="m") - self.add_output("offset_freeboard_heel_margin", 0.0, units="m") - self.add_output("offset_draft_heel_margin", 0.0, units="m") - self.add_output("wave_height_fairlead_ratio", 0.0) - - # Derivatives - self.declare_partials("*", "*", method="fd", form="central", step=1e-6) - - def compute(self, inputs, outputs): - """Sets nodal points and sectional centers of mass in z-coordinate system with z=0 at the waterline. - Nodal points are the beginning and end points of each section. - Nodes and sections start at bottom and move upwards. - - INPUTS: - ---------- - params : dictionary of input parameters - outputs : dictionary of output parameters - - OUTPUTS : none (all unknown dictionary values set) - """ - # Unpack variables - ncolumns = int(inputs["number_of_offset_columns"]) - R_od_main = 0.5 * inputs["main_d_full"] - R_od_offset = 0.5 * inputs["offset_d_full"] - R_semi = inputs["radius_to_offset_column"] - R_tower_base = 0.5 * inputs["tower_d_base"] - - z_nodes_offset = inputs["offset_z_nodes"] - z_nodes_main = inputs["main_z_nodes"] - - location = inputs["fairlead_location"] - fair_off = inputs["fairlead_offset_from_shell"] - off_freeboard = inputs["offset_freeboard"] - off_draft = inputs["offset_draft"] - max_heel = inputs["max_survival_heel"] - - # Set spacing constraint - outputs["main_offset_spacing"] = R_semi - R_od_main.max() - R_od_offset.max() - - # Determine location and radius at mooring connection point (fairlead) - if ncolumns > 0: - z_fairlead = location * (z_nodes_offset[-1] - z_nodes_offset[0]) + z_nodes_offset[0] - outputs["fairlead_radius"] = R_semi + fair_off + np.interp(z_fairlead, z_nodes_offset, R_od_offset) - else: - z_fairlead = location * (z_nodes_main[-1] - z_nodes_main[0]) + z_nodes_main[0] - outputs["fairlead_radius"] = fair_off + np.interp(z_fairlead, z_nodes_main, R_od_main) - outputs["fairlead"] = -z_fairlead # Fairlead defined as positive below waterline - outputs["wave_height_fairlead_ratio"] = inputs["hsig_wave"] / np.abs(z_fairlead) - - # Constrain spar top to be at least greater than tower main - outputs["tower_transition_buffer"] = R_od_main[-1] - R_tower_base - - # Make sure semi columns don't get submerged - heel_deflect = R_semi * np.sin(np.deg2rad(max_heel)) - outputs["offset_freeboard_heel_margin"] = off_freeboard - heel_deflect - outputs["offset_draft_heel_margin"] = off_draft - heel_deflect - - -class Substructure(om.ExplicitComponent): - """ - Calculate substructure properties - - Parameters - ---------- - water_density : float, [kg/m**3] - density of water - wave_period_range_low : float, [s] - Lower bound of typical ocean wavve period - wave_period_range_high : float, [s] - Upper bound of typical ocean wavve period - operational_heel : float, [deg] - Maximum angle of heel allowable - mooring_mass : float, [kg] - Mass of mooring lines - mooring_moments_of_inertia : numpy array[6], [kg*m**2] - mass moment of inertia of mooring system about fairlead-centerline point [xx yy - zz xy xz yz] - mooring_neutral_load : numpy array[NLINES_MAX, 3], [N] - z-force of mooring lines on structure - mooring_surge_restoring_force : float, [N] - Restoring force from mooring system after surge motion - mooring_pitch_restoring_force : numpy array[NLINES_MAX, 3], [N] - Restoring force from mooring system after pitch motion - mooring_cost : float, [USD] - Cost of mooring system - mooring_stiffness : numpy array[6, 6], [N/m] - Linearized stiffness matrix of mooring system at neutral (no offset) conditions. - fairlead : float, [m] - Depth below water for mooring line attachment - fairlead_radius : float, [m] - Outer spar radius at fairlead depth (point of mooring attachment) - number_of_offset_columns : float - Number of offset columns evenly spaced around main column - radius_to_offset_column : float, [m] - Distance from main column centerpoint to offset column centerpoint - main_Iwaterplane : float, [m**4] - Second moment of area of waterplane cross-section - main_Awaterplane : float, [m**2] - Area of waterplane cross-section - main_cost : float, [USD] - Cost of spar structure - main_mass : numpy array[n_full_main-1], [kg] - mass of main column by section - main_freeboard : float, [m] - Length of spar above water line - main_center_of_buoyancy : float, [m] - z-position of center of column buoyancy force - main_center_of_mass : float, [m] - z-position of center of column mass - main_moments_of_inertia : numpy array[6], [kg*m**2] - mass moment of inertia of column about main [xx yy zz xy xz yz] - main_added_mass : numpy array[6], [kg] - Diagonal of added mass matrix- masses are first 3 entries, moments are last 3 - offset_Iwaterplane : float, [m**4] - Second moment of area of waterplane cross-section - offset_Awaterplane : float, [m**2] - Area of waterplane cross-section - offset_cost : float, [USD] - Cost of spar structure - offset_mass : numpy array[n_full_off-1], [kg] - mass of offset column by section - offset_center_of_buoyancy : float, [m] - z-position of center of column buoyancy force - offset_center_of_mass : float, [m] - z-position of center of column mass - offset_moments_of_inertia : numpy array[6], [kg*m**2] - mass moment of inertia of column about main [xx yy zz xy xz yz] - offset_added_mass : numpy array[6], [kg] - Diagonal of added mass matrix- masses are first 3 entries, moments are last 3 - tower_mass : float, [kg] - Mass of tower - tower_shell_cost : float, [USD] - Cost of tower - tower_I_base : numpy array[6], [kg*m**2] - Moments about tower main - tower_z_full : numpy array[n_full_tow], [m] - z-coordinates of section nodes (length = nsection+1) - rna_mass : float, [kg] - Mass of RNA - rna_cg : numpy array[3], [m] - Location of RNA center of mass relative to tower top - rna_I : numpy array[6], [kg*m**2] - Moments about turbine main - water_ballast_zpts_vector : numpy array[n_full_main], [m] - z-points of potential ballast mass - water_ballast_radius_vector : numpy array[n_full_main], [m] - Inner radius of potential ballast mass - structural_mass : float, [kg] - Mass of whole turbine except for mooring lines - structure_center_of_mass : numpy array[3], [m] - xyz-position of center of gravity of whole turbine - structural_frequencies : numpy array[NFREQ], [Hz] - Structural frequencies outputted from FEM calculation - z_center_of_buoyancy : float, [m] - z-position of center of gravity (x,y = 0,0) - total_displacement : float, [m**3] - Total volume of water displaced by floating turbine (except for mooring lines) - total_force : numpy array[3], [N] - Net forces on turbine - total_moment : numpy array[3], [N*m] - Moments on whole turbine - pontoon_cost : float, [USD] - Cost of pontoon elements and connecting truss - - Returns - ------- - substructure_moments_of_inertia : numpy array[6], [kg*m**2] - mass moment of inertia of substructure (no tower or rna or mooring) - [xx yy zz xy xz yz] - total_mass : float, [kg] - total mass of spar and moorings - total_cost : float, [USD] - total cost of spar and moorings - metacentric_height : float, [m] - measure of static overturning stability - buoyancy_to_gravity : float - static stability margin based on position of centers of gravity and buoyancy - offset_force_ratio : float - total surge force divided by restoring force - heel_moment_ratio : float - total pitch moment divided by restoring moment - Iwaterplane_system : float, [m**4] - Second moment of area of waterplane cross-section for whole structure - center_of_mass : numpy array[3], [m] - xyz-position of center of gravity (x,y = 0,0) - variable_ballast_mass : float, [kg] - Amount of variable water ballast - variable_ballast_center_of_mass : float, [m] - Center of mass for variable ballast - variable_ballast_moments_of_inertia : numpy array[6], [kg*m**2] - mass moment of inertia of variable ballast [xx yy zz xy xz yz] - variable_ballast_height : float, [m] - height of water ballast to balance spar - variable_ballast_height_ratio : float - Ratio of water ballast height to available height - mass_matrix : numpy array[6], [kg] - Summary mass matrix of structure (minus pontoons) - added_mass_matrix : numpy array[6], [kg] - Summary hydrodynamic added mass matrix of structure (minus pontoons) - hydrostatic_stiffness : numpy array[6], [N/m] - Summary hydrostatic stiffness of structure - rigid_body_periods : numpy array[6], [s] - Natural periods of oscillation in 6 DOF - period_margin_low : numpy array[6] - Margin between natural periods and 2 second wave period - period_margin_high : numpy array[6] - Margin between natural periods and 20 second wave period - modal_margin_low : numpy array[NFREQ] - Margin between structural modes and 2 second wave period - modal_margin_high : numpy array[NFREQ] - Margin between structural modes and 20 second wave period - - """ - - def initialize(self): - self.options.declare("n_height_main") - self.options.declare("n_height_off") - self.options.declare("n_height_tow") - - def setup(self): - n_height_main = self.options["n_height_main"] - n_height_off = self.options["n_height_off"] - n_height_tow = self.options["n_height_tow"] - n_full_main = get_nfull(n_height_main) - n_full_off = get_nfull(n_height_off) - n_full_tow = get_nfull(n_height_tow) - - self.add_input("rho_water", 0.0, units="kg/m**3") - self.add_input("wave_period_range_low", 2.0, units="s") - self.add_input("wave_period_range_high", 20.0, units="s") - self.add_input("operational_heel", 0.0, units="deg") - self.add_input("mooring_mass", 0.0, units="kg") - self.add_input("mooring_moments_of_inertia", np.zeros(6), units="kg*m**2") - self.add_input("mooring_neutral_load", np.zeros((NLINES_MAX, 3)), units="N") - self.add_input("mooring_surge_restoring_force", 0.0, units="N") - self.add_input("mooring_pitch_restoring_force", np.zeros((NLINES_MAX, 3)), units="N") - self.add_input("mooring_cost", 0.0, units="USD") - self.add_input("mooring_stiffness", np.zeros((6, 6)), units="N/m") - self.add_input("fairlead", 1.0, units="m") - self.add_input("fairlead_radius", 0.0, units="m") - self.add_input("number_of_offset_columns", 0) - self.add_input("radius_to_offset_column", 0.0, units="m") - - self.add_input("main_Iwaterplane", 0.0, units="m**4") - self.add_input("main_Awaterplane", 0.0, units="m**2") - self.add_input("main_cost", 0.0, units="USD") - self.add_input("main_mass", np.zeros(n_full_main - 1), units="kg") - self.add_input("main_freeboard", 0.0, units="m") - self.add_input("main_center_of_buoyancy", 0.0, units="m") - self.add_input("main_center_of_mass", 0.0, units="m") - self.add_input("main_moments_of_inertia", np.zeros(6), units="kg*m**2") - self.add_input("main_added_mass", np.zeros(6), units="kg") - - self.add_input("offset_Iwaterplane", 0.0, units="m**4") - self.add_input("offset_Awaterplane", 0.0, units="m**2") - self.add_input("offset_cost", 0.0, units="USD") - self.add_input("offset_mass", np.zeros(n_full_off - 1), units="kg") - self.add_input("offset_center_of_buoyancy", 0.0, units="m") - self.add_input("offset_center_of_mass", 0.0, units="m") - self.add_input("offset_moments_of_inertia", np.zeros(6), units="kg*m**2") - self.add_input("offset_added_mass", np.zeros(6), units="kg") - - self.add_input("tower_mass", 0.0, units="kg") - self.add_input("tower_shell_cost", 0.0, units="USD") - self.add_input("tower_I_base", np.zeros(6), units="kg*m**2") - self.add_input("tower_z_full", np.zeros(n_full_tow), units="m") - self.add_input("rna_mass", 0.0, units="kg") - self.add_input("rna_cg", np.zeros(3), units="m") - self.add_input("rna_I", np.zeros(6), units="kg*m**2") - - self.add_input("water_ballast_zpts_vector", np.zeros(n_full_main), units="m") - self.add_input("water_ballast_radius_vector", np.zeros(n_full_main), units="m") - - self.add_input("structural_mass", 0.0, units="kg") - self.add_input("structure_center_of_mass", np.zeros(3), units="m") - self.add_input("structural_frequencies", np.zeros(NFREQ), units="Hz") - self.add_input("z_center_of_buoyancy", 0.0, units="m") - self.add_input("total_displacement", 0.0, units="m**3") - self.add_input("total_force", np.zeros(3), units="N") - self.add_input("total_moment", np.zeros(3), units="N*m") - self.add_input("pontoon_cost", 0.0, units="USD") - - self.add_output("substructure_moments_of_inertia", np.zeros(6), units="kg*m**2") - self.add_output("total_mass", 0.0, units="kg") - self.add_output("total_cost", 0.0, units="USD") - self.add_output("metacentric_height", 0.0, units="m") - self.add_output("buoyancy_to_gravity", 0.0) - self.add_output("offset_force_ratio", 0.0) - self.add_output("heel_moment_ratio", 0.0) - self.add_output("Iwaterplane_system", 0.0, units="m**4") - self.add_output("center_of_mass", np.zeros(3), units="m") - self.add_output("variable_ballast_mass", 0.0, units="kg") - self.add_output("variable_ballast_center_of_mass", 0.0, units="m") - self.add_output("variable_ballast_moments_of_inertia", np.zeros(6), units="kg*m**2") - self.add_output("variable_ballast_height", 0.0, units="m") - self.add_output("variable_ballast_height_ratio", 0.0) - self.add_output("mass_matrix", np.zeros(6), units="kg") - self.add_output("added_mass_matrix", np.zeros(6), units="kg") - self.add_output("hydrostatic_stiffness", np.zeros(6), units="N/m") - self.add_output("rigid_body_periods", np.zeros(6), units="s") - self.add_output("period_margin_low", np.zeros(6)) - self.add_output("period_margin_high", np.zeros(6)) - self.add_output("modal_margin_low", np.zeros(NFREQ)) - self.add_output("modal_margin_high", np.zeros(NFREQ)) - - self.declare_partials("*", "*", method="fd", form="central", step=1e-6) - - def compute(self, inputs, outputs): - # TODO: Get centerlines right- in sparGeometry? - # Determine ballast and cg of system - self.balance(inputs, outputs) - - # Determine stability, metacentric height from waterplane profile, displaced volume - self.compute_stability(inputs, outputs) - - # Compute natural periods of osciallation - self.compute_rigid_body_periods(inputs, outputs) - - # Check margins of natural and eigenfrequencies against waves - self.check_frequency_margins(inputs, outputs) - - # Sum all costs - self.compute_costs(inputs, outputs) - - def balance(self, inputs, outputs): - # Unpack variables - m_struct = inputs["structural_mass"] - Fz_mooring = np.sum(inputs["mooring_neutral_load"][:, -1]) - m_mooring = inputs["mooring_mass"] - - V_system = inputs["total_displacement"] - - cg_struct = inputs["structure_center_of_mass"] - - z_water_data = inputs["water_ballast_zpts_vector"] - r_water_data = inputs["water_ballast_radius_vector"] - rhoWater = inputs["rho_water"] - - # SEMI TODO: Make water_ballast in main only? columns too? How to apportion? - - # Make sure total mass of system with variable water ballast balances against displaced volume - # Water ballast should be buried in m_column - m_water = V_system * rhoWater - (m_struct + Fz_mooring / gravity) - m_system = m_struct + m_water - - # Output substructure total turbine mass - outputs["total_mass"] = m_struct + m_mooring - - # Find height given interpolant functions from columns - m_water_data = rhoWater * np.pi * cumtrapz(r_water_data ** 2, z_water_data) - m_water_data = np.r_[0.0, m_water_data] # cumtrapz has length-1 - - if m_water_data[-1] < m_water: - # Don't have enough space, so max out variable balast here and constraints will catch this - z_end = z_water_data[-1] - coeff = m_water / m_water_data[-1] - elif m_water < 0.0: - z_end = z_water_data[0] - coeff = 0.0 - else: - z_end = np.interp(m_water, m_water_data, z_water_data) - coeff = 1.0 - h_water = z_end - z_water_data[0] - outputs["variable_ballast_mass"] = m_water - outputs["variable_ballast_height"] = coeff * h_water - outputs["variable_ballast_height_ratio"] = coeff * h_water / (z_water_data[-1] - z_water_data[0]) - - # Find cg of whole system - # First find cg of water variable ballast by finding midpoint of mass sum - z_cg = np.interp(0.5 * coeff * m_water, m_water_data, z_water_data) - outputs["center_of_mass"] = (m_struct * cg_struct + m_water * np.r_[0.0, 0.0, z_cg]) / m_system - outputs["variable_ballast_center_of_mass"] = z_cg - - # Integrate for moment of inertia of variable ballast - npts = 100 - z_int = np.linspace(float(z_water_data[0]), float(z_end), npts) - r_int = np.interp(z_int, z_water_data, r_water_data) - Izz = 0.5 * rhoWater * np.pi * np.trapz(r_int ** 4, z_int) - Ixx = rhoWater * np.pi * np.trapz(0.25 * r_int ** 4 + r_int ** 2 * (z_int - z_cg) ** 2, z_int) - outputs["variable_ballast_moments_of_inertia"] = np.r_[Ixx, Ixx, Izz, 0.0, 0.0, 0.0] - - def compute_stability(self, inputs, outputs): - # Unpack variables - ncolumn = int(inputs["number_of_offset_columns"]) - z_cb = inputs["z_center_of_buoyancy"] - z_cg = outputs["center_of_mass"][-1] - V_system = inputs["total_displacement"] - - Iwater_main = inputs["main_Iwaterplane"] - Iwater_column = inputs["offset_Iwaterplane"] - Awater_column = inputs["offset_Awaterplane"] - - F_surge = inputs["total_force"][0] - M_pitch = inputs["total_moment"][1] - F_restore = inputs["mooring_surge_restoring_force"] - rhoWater = inputs["rho_water"] - R_semi = inputs["radius_to_offset_column"] - - F_restore_pitch = inputs["mooring_pitch_restoring_force"] - z_fairlead = inputs["fairlead"] * (-1) - R_fairlead = inputs["fairlead_radius"] - oper_heel = float(inputs["operational_heel"]) - - # Compute the distance from the center of buoyancy to the metacentre (BM is naval architecture) - # BM = Iw / V where V is the displacement volume (just computed) - # Iw is the area moment of inertia (meters^4) of the water-plane cross section about the heel axis - # For a spar, we assume this is just the I of a circle about x or y - # See https://en.wikipedia.org/wiki/Metacentric_height - # https://en.wikipedia.org/wiki/List_of_second_moments_of_area - # and http://farside.ph.utexas.edu/teaching/336L/Fluidhtml/node30.html - - # Water plane area of all components with parallel axis theorem - Iwater_system = Iwater_main - radii = R_semi * np.cos(np.linspace(0, 2 * np.pi, ncolumn + 1)) - for k in range(ncolumn): - Iwater_system += Iwater_column + Awater_column * radii[k] ** 2 - outputs["Iwaterplane_system"] = Iwater_column - - # Measure static stability: - # 1. Center of buoyancy should be above CG (difference should be positive) - # 2. Metacentric height should be positive - buoyancy2metacentre_BM = Iwater_system / V_system - outputs["buoyancy_to_gravity"] = z_cg - z_cb - outputs["metacentric_height"] = buoyancy2metacentre_BM - outputs["buoyancy_to_gravity"] - - F_buoy = V_system * rhoWater * gravity - M_restore = outputs["metacentric_height"] * np.sin(np.deg2rad(oper_heel)) * F_buoy - - # Convert mooring restoring force after pitch to a restoring moment - nlines = np.count_nonzero(F_restore_pitch[:, 2]) - F_restore_pitch = F_restore_pitch[:nlines, :] - moorx = R_fairlead * np.cos(np.linspace(0, 2 * np.pi, nlines + 1)[:-1]) - moory = R_fairlead * np.sin(np.linspace(0, 2 * np.pi, nlines + 1)[:-1]) - r_moor = np.c_[moorx, moory, (z_fairlead - z_cg) * np.ones(moorx.shape)] - Msum = 0.0 - for k in range(nlines): - dvF = DirectionVector.fromArray(F_restore_pitch[k, :]) - dvR = DirectionVector.fromArray(r_moor[k, :]).yawToHub(oper_heel) - M = dvR.cross(dvF) - Msum += M.y - - M_restore += Msum - - # Comput heel angle, scaling overturning moment by defect of inflow velocity - # TODO: Make this another load case in Frame3DD - outputs["heel_moment_ratio"] = np.abs(np.cos(np.deg2rad(oper_heel)) ** 2.0 * M_pitch / M_restore) - - # Now compute offsets from the applied force - # First use added mass (the mass of the water that must be displaced in movement) - # http://www.iaea.org/inis/collection/NCLCollectionStore/_Public/09/411/9411273.pdf - # mass_add_surge = rhoWater * np.pi * R_od.max() * draft - # T_surge = 2*np.pi*np.sqrt( (outputs['total_mass']+mass_add_surge) / kstiff_horiz_mooring) - - # Compare restoring force from mooring to force of worst case spar displacement - outputs["offset_force_ratio"] = np.abs(F_surge / F_restore) - - def compute_rigid_body_periods(self, inputs, outputs): - # Unpack variables - ncolumn = int(inputs["number_of_offset_columns"]) - R_semi = inputs["radius_to_offset_column"] - - m_main = np.sum(inputs["main_mass"]) - m_column = np.sum(inputs["offset_mass"]) - m_tower = np.sum(inputs["tower_mass"]) - m_rna = inputs["rna_mass"] - m_mooring = inputs["mooring_mass"] - m_total = outputs["total_mass"] - m_water = np.maximum(0.0, outputs["variable_ballast_mass"]) - m_a_main = inputs["main_added_mass"] - m_a_column = inputs["offset_added_mass"] - - rhoWater = inputs["rho_water"] - V_system = inputs["total_displacement"] - h_metacenter = outputs["metacentric_height"] - - Awater_main = inputs["main_Awaterplane"] - Awater_column = inputs["offset_Awaterplane"] - I_main = inputs["main_moments_of_inertia"] - I_column = inputs["offset_moments_of_inertia"] - I_mooring = inputs["mooring_moments_of_inertia"] - I_water = outputs["variable_ballast_moments_of_inertia"] - I_tower = inputs["tower_I_base"] - I_rna = inputs["rna_I"] - I_waterplane = outputs["Iwaterplane_system"] - - z_cg_main = float(inputs["main_center_of_mass"]) - z_cb_main = float(inputs["main_center_of_buoyancy"]) - z_cg_column = float(inputs["offset_center_of_mass"]) - z_cb_column = float(inputs["offset_center_of_buoyancy"]) - z_cb = float(inputs["z_center_of_buoyancy"]) - z_cg_water = float(outputs["variable_ballast_center_of_mass"]) - z_fairlead = float(inputs["fairlead"] * (-1)) - - r_cg = outputs["center_of_mass"] - cg_rna = inputs["rna_cg"] - z_tower = inputs["tower_z_full"] - - K_moor = np.diag(inputs["mooring_stiffness"]) - - # Number of degrees of freedom - nDOF = 6 - - # Compute elements on mass matrix diagonal - M_mat = np.zeros((nDOF,)) - # Surge, sway, heave just use normal inertia (without mooring according to Senu) - M_mat[:3] = m_total + m_water - m_mooring - # Add in moments of inertia of primary column - I_total = assembleI(np.zeros(6)) - I_main = assembleI(I_main) - R = np.array([0.0, 0.0, z_cg_main]) - r_cg - I_total += I_main + m_main * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) - # Add up moments of intertia of other columns - radii_x = R_semi * np.cos(np.linspace(0, 2 * np.pi, ncolumn + 1)) - radii_y = R_semi * np.sin(np.linspace(0, 2 * np.pi, ncolumn + 1)) - I_column = assembleI(I_column) - for k in range(ncolumn): - R = np.array([radii_x[k], radii_y[k], z_cg_column]) - r_cg - I_total += I_column + m_column * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) - # Add in variable ballast - R = np.array([0.0, 0.0, z_cg_water]) - r_cg - I_total += assembleI(I_water) + m_water * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) - - # Save what we have so far as m_substructure & I_substructure and move to its own CM - m_subs = m_main + ncolumn * m_column + m_water - z_cg_subs = (m_main * z_cg_main + ncolumn * m_column * z_cg_column + m_water * z_cg_water) / m_subs - R = r_cg - np.r_[0.0, 0.0, z_cg_subs] - I_substructure = I_total + m_subs * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) - outputs["substructure_moments_of_inertia"] = unassembleI(I_total) - - # Now go back to the total - # Add in mooring system- Not needed according to Senu - # R = np.array([0.0, 0.0, z_fairlead]) - r_cg - # I_total += assembleI(I_mooring) + m_mooring*(np.dot(R, R)*np.eye(3) - np.outer(R, R)) - # Add in tower - R = np.array([0.0, 0.0, z_tower[0]]) - r_cg - I_total += assembleI(I_tower) + m_tower * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) - # Add in RNA - R = np.array([0.0, 0.0, z_tower[-1]]) + cg_rna - r_cg - I_total += assembleI(I_rna) + m_rna * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) - # Stuff moments of inertia into mass matrix - M_mat[3:] = unassembleI(I_total)[:3] - outputs["mass_matrix"] = M_mat - - # Add up all added mass entries in a similar way - A_mat = np.zeros((nDOF,)) - # Surge, sway, heave just use normal inertia - A_mat[:3] = m_a_main[:3] + ncolumn * m_a_column[:3] - # Add up moments of inertia, move added mass moments from CofB to CofG - I_main = assembleI(np.r_[m_a_main[3:], np.zeros(3)]) - R = np.array([0.0, 0.0, z_cb_main]) - r_cg - I_total = I_main + m_a_main[0] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) - # Add up added moments of intertia of all columns for other entries - I_column = assembleI(np.r_[m_a_column[3:], np.zeros(3)]) - for k in range(ncolumn): - R = np.array([radii_x[k], radii_y[k], z_cb_column]) - r_cg - I_total += I_column + m_a_column[0] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) - A_mat[3:] = unassembleI(I_total)[:3] - outputs["added_mass_matrix"] = A_mat - - # Hydrostatic stiffness has contributions in heave (K33) and roll/pitch (K44/55) - # See DNV-RP-H103: Modeling and Analyis of Marine Operations - K_hydro = np.zeros((nDOF,)) - K_hydro[2] = rhoWater * gravity * (Awater_main + ncolumn * Awater_column) - K_hydro[3:5] = rhoWater * gravity * V_system * h_metacenter # FAST eqns: (I_waterplane + V_system * z_cb) - outputs["hydrostatic_stiffness"] = K_hydro - - # Now compute all six natural periods at once - epsilon = 1e-6 # Avoids numerical issues - K_total = np.maximum(K_hydro + K_moor, 0.0) - outputs["rigid_body_periods"] = 2 * np.pi * np.sqrt((M_mat + A_mat) / (K_total + epsilon)) - - def check_frequency_margins(self, inputs, outputs): - # Unpack variables - T_sys = outputs["rigid_body_periods"] - T_wave_low = inputs["wave_period_range_low"] - T_wave_high = inputs["wave_period_range_high"] - f_struct = inputs["structural_frequencies"] - T_struct = np.zeros(f_struct.shape) - for k in range(T_struct.size): - T_struct[k] = 0.0 if f_struct[k] == 0.0 else 1 / f_struct[k] - - # Waves cannot excite yaw, so removing that constraint - - # Compute margins between wave forcing and natural periods - indicator_high = T_wave_high * np.ones(T_sys.shape) - indicator_high[T_sys < T_wave_low] = 1e-16 - indicator_high[-1] = 1e-16 # Not yaw - outputs["period_margin_high"] = T_sys / indicator_high - - indicator_low = T_wave_low * np.ones(T_sys.shape) - indicator_low[T_sys > T_wave_high] = 1e30 - indicator_low[-1] = 1e30 # Not yaw - outputs["period_margin_low"] = T_sys / indicator_low - - # Compute margins bewteen wave forcing and structural frequencies - indicator_high = T_wave_high * np.ones(T_struct.shape) - indicator_high[T_struct < T_wave_low] = 1e-16 - outputs["modal_margin_high"] = T_struct / indicator_high - - indicator_low = T_wave_low * np.ones(T_struct.shape) - indicator_low[T_struct > T_wave_high] = 1e30 - outputs["modal_margin_low"] = T_struct / indicator_low - - def compute_costs(self, inputs, outputs): - # Unpack variables - ncolumn = int(inputs["number_of_offset_columns"]) - c_mooring = inputs["mooring_cost"] - c_aux = inputs["offset_cost"] - c_main = inputs["main_cost"] - c_pontoon = inputs["pontoon_cost"] - c_tower = inputs["tower_shell_cost"] - - outputs["total_cost"] = c_mooring + ncolumn * c_aux + c_main + c_pontoon + c_tower diff --git a/wisdem/floatingse/visualize.py b/wisdem/floatingse/visualize.py index ee840b1f0..8674d91f3 100644 --- a/wisdem/floatingse/visualize.py +++ b/wisdem/floatingse/visualize.py @@ -1,6 +1,5 @@ import numpy as np from mayavi import mlab -import matplotlib.pyplot as plt def sectional2nodal(x): diff --git a/wisdem/glue_code/gc_LoadInputs.py b/wisdem/glue_code/gc_LoadInputs.py index e2980a5f5..c8cd53ed8 100644 --- a/wisdem/glue_code/gc_LoadInputs.py +++ b/wisdem/glue_code/gc_LoadInputs.py @@ -30,6 +30,12 @@ def set_run_flags(self): # Create components flag struct self.modeling_options["flags"] = {} + # Backwards compatibility + modules = ["RotorSE", "DriveSE", "GeneratorSE", "TowerSE", "FloatingSE", "Loading", "BOS"] + for m in modules: + if m in self.modeling_options: + self.modeling_options["WISDEM"][m].update(self.modeling_options[m]) + for k in ["blade", "hub", "nacelle", "tower", "monopile", "floating_platform", "mooring", "RNA"]: self.modeling_options["flags"][k] = k in self.wt_init["components"] @@ -40,9 +46,9 @@ def set_run_flags(self): self.modeling_options["flags"]["generator"] = False if self.modeling_options["flags"]["nacelle"] and "generator" in self.wt_init["components"]["nacelle"]: self.modeling_options["flags"]["generator"] = True - if not "GeneratorSE" in self.modeling_options: - self.modeling_options["GeneratorSE"] = {} - self.modeling_options["GeneratorSE"]["type"] = self.wt_init["components"]["nacelle"]["generator"][ + if not "GeneratorSE" in self.modeling_options["WISDEM"]: + self.modeling_options["WISDEM"]["GeneratorSE"] = {} + self.modeling_options["WISDEM"]["GeneratorSE"]["type"] = self.wt_init["components"]["nacelle"]["generator"][ "generator_type" ].lower() @@ -57,17 +63,17 @@ def set_run_flags(self): # Even if the block is in the inputs, the user can turn off via modeling options if flags["bos"]: - flags["bos"] = self.modeling_options["BOS"]["flag"] + flags["bos"] = self.modeling_options["WISDEM"]["BOS"]["flag"] if flags["blade"]: - flags["blade"] = self.modeling_options["RotorSE"]["flag"] + flags["blade"] = self.modeling_options["WISDEM"]["RotorSE"]["flag"] if flags["tower"]: - flags["tower"] = self.modeling_options["TowerSE"]["flag"] + flags["tower"] = self.modeling_options["WISDEM"]["TowerSE"]["flag"] if flags["hub"]: - flags["hub"] = self.modeling_options["DriveSE"]["flag"] + flags["hub"] = self.modeling_options["WISDEM"]["DriveSE"]["flag"] if flags["nacelle"]: - flags["nacelle"] = self.modeling_options["DriveSE"]["flag"] + flags["nacelle"] = self.modeling_options["WISDEM"]["DriveSE"]["flag"] if flags["generator"]: - flags["generator"] = self.modeling_options["DriveSE"]["flag"] + flags["generator"] = self.modeling_options["WISDEM"]["DriveSE"]["flag"] flags["hub"] = flags["nacelle"] = flags["hub"] or flags["nacelle"] # Hub and nacelle have to go together # Blades and airfoils @@ -109,82 +115,96 @@ def set_openmdao_vectors(self): # Airfoils if self.modeling_options["flags"]["airfoils"]: - self.modeling_options["RotorSE"]["n_af"] = len(self.wt_init["airfoils"]) - self.modeling_options["RotorSE"]["n_aoa"] = self.modeling_options["RotorSE"]["n_aoa"] - if self.modeling_options["RotorSE"]["n_aoa"] / 4.0 == int(self.modeling_options["RotorSE"]["n_aoa"] / 4.0): + self.modeling_options["WISDEM"]["RotorSE"]["n_af"] = len(self.wt_init["airfoils"]) + self.modeling_options["WISDEM"]["RotorSE"]["n_aoa"] = self.modeling_options["WISDEM"]["RotorSE"]["n_aoa"] + if self.modeling_options["WISDEM"]["RotorSE"]["n_aoa"] / 4.0 == int( + self.modeling_options["WISDEM"]["RotorSE"]["n_aoa"] / 4.0 + ): # One fourth of the angles of attack from -pi to -pi/6, half between -pi/6 to pi/6, and one fourth from pi/6 to pi - self.modeling_options["RotorSE"]["aoa"] = np.unique( + self.modeling_options["WISDEM"]["RotorSE"]["aoa"] = np.unique( np.hstack( [ - np.linspace(-np.pi, -np.pi / 6.0, int(self.modeling_options["RotorSE"]["n_aoa"] / 4.0 + 1)), np.linspace( - -np.pi / 6.0, np.pi / 6.0, int(self.modeling_options["RotorSE"]["n_aoa"] / 2.0) + -np.pi, -np.pi / 6.0, int(self.modeling_options["WISDEM"]["RotorSE"]["n_aoa"] / 4.0 + 1) + ), + np.linspace( + -np.pi / 6.0, + np.pi / 6.0, + int(self.modeling_options["WISDEM"]["RotorSE"]["n_aoa"] / 2.0), + ), + np.linspace( + np.pi / 6.0, np.pi, int(self.modeling_options["WISDEM"]["RotorSE"]["n_aoa"] / 4.0 + 1) ), - np.linspace(np.pi / 6.0, np.pi, int(self.modeling_options["RotorSE"]["n_aoa"] / 4.0 + 1)), ] ) ) else: - self.modeling_options["RotorSE"]["aoa"] = np.linspace( - -np.pi, np.pi, self.modeling_options["RotorSE"]["n_aoa"] + self.modeling_options["WISDEM"]["RotorSE"]["aoa"] = np.linspace( + -np.pi, np.pi, self.modeling_options["WISDEM"]["RotorSE"]["n_aoa"] ) print( "WARNING: If you like a grid of angles of attack more refined between +- 30 deg, please choose a n_aoa in the analysis option input file that is a multiple of 4. The current value of " - + str(self.modeling_options["RotorSE"]["n_aoa"]) + + str(self.modeling_options["WISDEM"]["RotorSE"]["n_aoa"]) + " is not a multiple of 4 and an equally spaced grid is adopted." ) Re_all = [] - for i in range(self.modeling_options["RotorSE"]["n_af"]): + for i in range(self.modeling_options["WISDEM"]["RotorSE"]["n_af"]): for j in range(len(self.wt_init["airfoils"][i]["polars"])): Re_all.append(self.wt_init["airfoils"][i]["polars"][j]["re"]) - self.modeling_options["RotorSE"]["n_Re"] = len(np.unique(Re_all)) - self.modeling_options["RotorSE"]["n_tab"] = 1 - self.modeling_options["RotorSE"]["n_xy"] = self.modeling_options["RotorSE"]["n_xy"] - self.modeling_options["RotorSE"]["af_used"] = self.wt_init["components"]["blade"]["outer_shape_bem"][ - "airfoil_position" - ]["labels"] + self.modeling_options["WISDEM"]["RotorSE"]["n_Re"] = len(np.unique(Re_all)) + self.modeling_options["WISDEM"]["RotorSE"]["n_tab"] = 1 + self.modeling_options["WISDEM"]["RotorSE"]["n_xy"] = self.modeling_options["WISDEM"]["RotorSE"]["n_xy"] + self.modeling_options["WISDEM"]["RotorSE"]["af_used"] = self.wt_init["components"]["blade"][ + "outer_shape_bem" + ]["airfoil_position"]["labels"] # Blade if self.modeling_options["flags"]["blade"]: - self.modeling_options["RotorSE"]["nd_span"] = np.linspace( - 0.0, 1.0, self.modeling_options["RotorSE"]["n_span"] + self.modeling_options["WISDEM"]["RotorSE"]["nd_span"] = np.linspace( + 0.0, 1.0, self.modeling_options["WISDEM"]["RotorSE"]["n_span"] ) # Equally spaced non-dimensional spanwise grid - self.modeling_options["RotorSE"]["n_af_span"] = len( + self.modeling_options["WISDEM"]["RotorSE"]["n_af_span"] = len( self.wt_init["components"]["blade"]["outer_shape_bem"]["airfoil_position"]["labels"] ) # This is the number of airfoils defined along blade span and it is often different than n_af, which is the number of airfoils defined in the airfoil database - self.modeling_options["RotorSE"]["n_webs"] = len( + self.modeling_options["WISDEM"]["RotorSE"]["n_webs"] = len( self.wt_init["components"]["blade"]["internal_structure_2d_fem"]["webs"] ) - self.modeling_options["RotorSE"]["n_layers"] = len( + self.modeling_options["WISDEM"]["RotorSE"]["n_layers"] = len( self.wt_init["components"]["blade"]["internal_structure_2d_fem"]["layers"] ) - self.modeling_options["RotorSE"]["lofted_output"] = False - self.modeling_options["RotorSE"]["n_freq"] = 10 # Number of blade nat frequencies computed - - self.modeling_options["RotorSE"]["layer_name"] = self.modeling_options["RotorSE"]["n_layers"] * [""] - self.modeling_options["RotorSE"]["layer_mat"] = self.modeling_options["RotorSE"]["n_layers"] * [""] - for i in range(self.modeling_options["RotorSE"]["n_layers"]): - self.modeling_options["RotorSE"]["layer_name"][i] = self.wt_init["components"]["blade"][ + self.modeling_options["WISDEM"]["RotorSE"]["lofted_output"] = False + self.modeling_options["WISDEM"]["RotorSE"]["n_freq"] = 10 # Number of blade nat frequencies computed + + self.modeling_options["WISDEM"]["RotorSE"]["layer_name"] = self.modeling_options["WISDEM"]["RotorSE"][ + "n_layers" + ] * [""] + self.modeling_options["WISDEM"]["RotorSE"]["layer_mat"] = self.modeling_options["WISDEM"]["RotorSE"][ + "n_layers" + ] * [""] + for i in range(self.modeling_options["WISDEM"]["RotorSE"]["n_layers"]): + self.modeling_options["WISDEM"]["RotorSE"]["layer_name"][i] = self.wt_init["components"]["blade"][ "internal_structure_2d_fem" ]["layers"][i]["name"] - self.modeling_options["RotorSE"]["layer_mat"][i] = self.wt_init["components"]["blade"][ + self.modeling_options["WISDEM"]["RotorSE"]["layer_mat"][i] = self.wt_init["components"]["blade"][ "internal_structure_2d_fem" ]["layers"][i]["material"] - self.modeling_options["RotorSE"]["web_name"] = self.modeling_options["RotorSE"]["n_webs"] * [""] - for i in range(self.modeling_options["RotorSE"]["n_webs"]): - self.modeling_options["RotorSE"]["web_name"][i] = self.wt_init["components"]["blade"][ + self.modeling_options["WISDEM"]["RotorSE"]["web_name"] = self.modeling_options["WISDEM"]["RotorSE"][ + "n_webs" + ] * [""] + for i in range(self.modeling_options["WISDEM"]["RotorSE"]["n_webs"]): + self.modeling_options["WISDEM"]["RotorSE"]["web_name"][i] = self.wt_init["components"]["blade"][ "internal_structure_2d_fem" ]["webs"][i]["name"] # Distributed aerodynamic control devices along blade - self.modeling_options["RotorSE"]["n_te_flaps"] = 0 + self.modeling_options["WISDEM"]["RotorSE"]["n_te_flaps"] = 0 if "aerodynamic_control" in self.wt_init["components"]["blade"]: if "te_flaps" in self.wt_init["components"]["blade"]["aerodynamic_control"]: - self.modeling_options["RotorSE"]["n_te_flaps"] = len( + self.modeling_options["WISDEM"]["RotorSE"]["n_te_flaps"] = len( self.wt_init["components"]["blade"]["aerodynamic_control"]["te_flaps"] ) - self.modeling_options["RotorSE"]["n_tab"] = 3 + self.modeling_options["WISDEM"]["RotorSE"]["n_tab"] = 3 else: raise RuntimeError( "A distributed aerodynamic control device is provided in the yaml input file, but not supported by wisdem." @@ -192,7 +212,7 @@ def set_openmdao_vectors(self): # Drivetrain if self.modeling_options["flags"]["nacelle"]: - self.modeling_options["DriveSE"]["direct"] = self.wt_init["assembly"]["drivetrain"].lower() in [ + self.modeling_options["WISDEM"]["DriveSE"]["direct"] = self.wt_init["assembly"]["drivetrain"].lower() in [ "direct", "direct_drive", "pm_direct_drive", @@ -200,19 +220,19 @@ def set_openmdao_vectors(self): # Tower if self.modeling_options["flags"]["tower"]: - self.modeling_options["TowerSE"]["n_height_tower"] = len( + self.modeling_options["WISDEM"]["TowerSE"]["n_height_tower"] = len( self.wt_init["components"]["tower"]["outer_shape_bem"]["outer_diameter"]["grid"] ) - self.modeling_options["TowerSE"]["n_layers_tower"] = len( + self.modeling_options["WISDEM"]["TowerSE"]["n_layers_tower"] = len( self.wt_init["components"]["tower"]["internal_structure_2d_fem"]["layers"] ) # Monopile if self.modeling_options["flags"]["monopile"]: - self.modeling_options["TowerSE"]["n_height_monopile"] = len( + self.modeling_options["WISDEM"]["TowerSE"]["n_height_monopile"] = len( self.wt_init["components"]["monopile"]["outer_shape_bem"]["outer_diameter"]["grid"] ) - self.modeling_options["TowerSE"]["n_layers_monopile"] = len( + self.modeling_options["WISDEM"]["TowerSE"]["n_layers_monopile"] = len( self.wt_init["components"]["monopile"]["internal_structure_2d_fem"]["layers"] ) @@ -236,6 +256,16 @@ def set_openmdao_vectors(self): "floating_platform" ]["joints"][i]["cylindrical"] + # Check that there is at most one transition joint + if self.modeling_options["floating"]["joints"]["transition"].count(True) > 1: + raise ValueError("Can only support one tower on the floating platform for now") + try: + self.modeling_options["floating"]["transition_joint"] = self.modeling_options["floating"]["joints"][ + "transition" + ].index(True) + except: + self.modeling_options["floating"]["transition_joint"] = None + n_members = len(self.wt_init["components"]["floating_platform"]["members"]) self.modeling_options["floating"]["members"] = {} self.modeling_options["floating"]["members"]["n_members"] = n_members @@ -243,8 +273,10 @@ def set_openmdao_vectors(self): self.modeling_options["floating"]["members"]["joint1"] = [""] * n_members self.modeling_options["floating"]["members"]["joint2"] = [""] * n_members self.modeling_options["floating"]["members"]["outer_shape"] = [""] * n_members + self.modeling_options["floating"]["members"]["n_height"] = np.zeros(n_members, dtype=int) self.modeling_options["floating"]["members"]["n_layers"] = np.zeros(n_members, dtype=int) self.modeling_options["floating"]["members"]["n_ballasts"] = np.zeros(n_members, dtype=int) + self.modeling_options["floating"]["members"]["n_bulkheads"] = np.zeros(n_members, dtype=int) self.modeling_options["floating"]["members"]["n_axial_joints"] = np.zeros(n_members, dtype=int) for i in range(n_members): self.modeling_options["floating"]["members"]["name"][i] = self.wt_init["components"][ @@ -260,6 +292,10 @@ def set_openmdao_vectors(self): "floating_platform" ]["members"][i]["outer_shape"]["shape"] + grid = self.wt_init["components"]["floating_platform"]["members"][i]["outer_shape"]["outer_diameter"][ + "grid" + ][:] + n_layers = len( self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]["layers"] ) @@ -271,24 +307,54 @@ def set_openmdao_vectors(self): else: n_ballasts = 0 self.modeling_options["floating"]["members"]["n_ballasts"][i] = n_ballasts - grid = [] + + # Add in bulkheads and enforce at least endcaps for submerged environment + # Don't add to master grid as they are handled differently in FloatingSE if "bulkhead" in self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]: - grid = np.unique( - np.hstack( - [ - self.wt_init["components"]["floating_platform"]["members"][i]["outer_shape"][ - "outer_diameter" - ]["grid"], - self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"][ - "bulkhead" - ]["thickness"]["grid"], - ] - ) - ) + bulkgrid = self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"][ + "bulkhead" + ]["thickness"]["grid"] + if not 0.0 in bulkgrid: + self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]["bulkhead"][ + "thickness" + ]["grid"].append(0.0) + self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]["bulkhead"][ + "thickness" + ]["values"].append(0.02) + if not 1.0 in bulkgrid: + self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]["bulkhead"][ + "thickness" + ]["grid"].append(1.0) + self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]["bulkhead"][ + "thickness" + ]["values"].append(0.02) + # grid += bulkgrid # Handled differently in the floating code else: - grid = self.wt_init["components"]["floating_platform"]["members"][i]["outer_shape"][ - "outer_diameter" + self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]["bulkhead"] = {} + self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]["bulkhead"][ + "material" + ] = self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]["layers"][ + 0 + ][ + "material" + ] + self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]["bulkhead"][ + "thickness" + ] = {} + self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]["bulkhead"][ + "thickness" + ]["grid"] = [0.0, 1.0] + self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]["bulkhead"][ + "thickness" + ]["values"] = [0.02, 0.02] + + n_bulk = len( + self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"]["bulkhead"][ + "thickness" ]["grid"] + ) + self.modeling_options["floating"]["members"]["n_bulkheads"][i] = n_bulk + self.modeling_options["floating"]["members"][ "layer_mat_member_" + self.modeling_options["floating"]["members"]["name"][i] ] = [""] * n_layers @@ -302,16 +368,10 @@ def set_openmdao_vectors(self): ][ "material" ] - grid = np.unique( - np.hstack( - [ - grid, - self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"][ - "layers" - ][j]["thickness"]["grid"], - ] - ) - ) + grid += self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"][ + "layers" + ][j]["thickness"]["grid"] + self.modeling_options["floating"]["members"][ "ballast_flag_member_" + self.modeling_options["floating"]["members"]["name"][i] ] = [False] * n_ballasts @@ -343,16 +403,10 @@ def set_openmdao_vectors(self): ][ "material" ] - grid = np.unique( - np.hstack( - [ - grid, - self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"][ - "ballasts" - ][k]["grid"], - ] - ) - ) + grid += self.wt_init["components"]["floating_platform"]["members"][i]["internal_structure"][ + "ballasts" + ][k]["grid"] + if "axial_joints" in self.wt_init["components"]["floating_platform"]["members"][i]: n_axial_joints = len(self.wt_init["components"]["floating_platform"]["members"][i]["axial_joints"]) self.modeling_options["floating"]["members"]["n_axial_joints"][i] = n_axial_joints @@ -362,22 +416,34 @@ def set_openmdao_vectors(self): for m in range(n_axial_joints): self.modeling_options["floating"]["members"][ "axial_joint_name_member_" + self.modeling_options["floating"]["members"]["name"][i] - ] = self.wt_init["components"]["floating_platform"]["members"][i]["axial_joints"][m]["name"] - grid = np.unique( - np.hstack( - [ - grid, - self.wt_init["components"]["floating_platform"]["members"][i]["axial_joints"][m][ - "grid" - ], - ] - ) + ][m] = self.wt_init["components"]["floating_platform"]["members"][i]["axial_joints"][m]["name"] + grid.append( + self.wt_init["components"]["floating_platform"]["members"][i]["axial_joints"][m]["grid"] ) else: self.modeling_options["floating"]["members"]["n_axial_joints"][i] = 0 + + final_grid = np.unique(grid) self.modeling_options["floating"]["members"][ "grid_member_" + self.modeling_options["floating"]["members"]["name"][i] - ] = grid + ] = final_grid + self.modeling_options["floating"]["members"]["n_height"][i] = len(final_grid) + + # Floating tower params + self.modeling_options["floating"]["tower"] = {} + self.modeling_options["floating"]["tower"]["n_ballasts"] = [0] + self.modeling_options["floating"]["tower"]["n_bulkheads"] = [0] + self.modeling_options["floating"]["tower"]["n_axial_joints"] = [0] + if self.modeling_options["flags"]["tower"]: + self.modeling_options["floating"]["tower"]["n_height"] = [ + self.modeling_options["WISDEM"]["TowerSE"]["n_height_tower"] + ] + self.modeling_options["floating"]["tower"]["n_layers"] = [ + self.modeling_options["WISDEM"]["TowerSE"]["n_layers_tower"] + ] + else: + self.modeling_options["floating"]["tower"]["n_height"] = [0] + self.modeling_options["floating"]["tower"]["n_layers"] = [0] # Mooring self.modeling_options["mooring"] = {} @@ -388,15 +454,20 @@ def set_openmdao_vectors(self): n_anchor_types = len(self.wt_init["components"]["mooring"]["anchor_types"]) self.modeling_options["mooring"]["n_nodes"] = n_nodes self.modeling_options["mooring"]["n_lines"] = n_lines + self.modeling_options["mooring"]["n_anchors"] = n_lines self.modeling_options["mooring"]["n_line_types"] = n_line_types self.modeling_options["mooring"]["n_anchor_types"] = n_anchor_types self.modeling_options["mooring"]["node_type"] = [""] * n_nodes + self.modeling_options["mooring"]["node_names"] = [""] * n_nodes self.modeling_options["mooring"]["anchor_type"] = [""] * n_nodes self.modeling_options["mooring"]["fairlead_type"] = [""] * n_nodes for i in range(n_nodes): self.modeling_options["mooring"]["node_type"][i] = self.wt_init["components"]["mooring"]["nodes"][i][ "node_type" ] + self.modeling_options["mooring"]["node_names"][i] = self.wt_init["components"]["mooring"]["nodes"][i][ + "name" + ] self.modeling_options["mooring"]["anchor_type"][i] = self.wt_init["components"]["mooring"]["nodes"][i][ "anchor_type" ] @@ -406,6 +477,7 @@ def set_openmdao_vectors(self): self.modeling_options["mooring"]["node1"] = [""] * n_lines self.modeling_options["mooring"]["node2"] = [""] * n_lines self.modeling_options["mooring"]["line_type"] = [""] * n_lines + fairlead_nodes = [] for i in range(n_lines): self.modeling_options["mooring"]["node1"][i] = self.wt_init["components"]["mooring"]["lines"][i][ "node1" @@ -416,6 +488,18 @@ def set_openmdao_vectors(self): self.modeling_options["mooring"]["line_type"][i] = self.wt_init["components"]["mooring"]["lines"][i][ "line_type" ] + # For the vessel attachments, find the list of fairlead nodes on the structure + node1id = self.modeling_options["mooring"]["node_names"].index( + self.modeling_options["mooring"]["node1"][i] + ) + node2id = self.modeling_options["mooring"]["node_names"].index( + self.modeling_options["mooring"]["node2"][i] + ) + if self.modeling_options["mooring"]["node_type"][node1id] == "vessel": + fairlead_nodes.append(self.wt_init["components"]["mooring"]["nodes"][node1id]["joint"]) + if self.modeling_options["mooring"]["node_type"][node2id] == "vessel": + fairlead_nodes.append(self.wt_init["components"]["mooring"]["nodes"][node2id]["joint"]) + self.modeling_options["mooring"]["line_type_name"] = [""] * n_line_types for i in range(n_line_types): self.modeling_options["mooring"]["line_type_name"][i] = self.wt_init["components"]["mooring"][ @@ -426,6 +510,7 @@ def set_openmdao_vectors(self): self.modeling_options["mooring"]["anchor_type_name"][i] = self.wt_init["components"]["mooring"][ "anchor_types" ][i]["name"] + self.modeling_options["mooring"]["n_attach"] = len(set(fairlead_nodes)) # Assembly self.modeling_options["assembly"] = {} @@ -453,22 +538,26 @@ def recursive_flag(d): # If not an optimization DV, then the number of points should be same as the discretization blade_opt_options = self.analysis_options["design_variables"]["blade"] if not blade_opt_options["aero_shape"]["twist"]["flag"]: - blade_opt_options["aero_shape"]["twist"]["n_opt"] = self.modeling_options["RotorSE"]["n_span"] + blade_opt_options["aero_shape"]["twist"]["n_opt"] = self.modeling_options["WISDEM"]["RotorSE"]["n_span"] elif blade_opt_options["aero_shape"]["twist"]["n_opt"] < 4: raise ValueError("Cannot optimize twist with less than 4 control points along blade span") if not blade_opt_options["aero_shape"]["chord"]["flag"]: - blade_opt_options["aero_shape"]["chord"]["n_opt"] = self.modeling_options["RotorSE"]["n_span"] + blade_opt_options["aero_shape"]["chord"]["n_opt"] = self.modeling_options["WISDEM"]["RotorSE"]["n_span"] elif blade_opt_options["aero_shape"]["chord"]["n_opt"] < 4: raise ValueError("Cannot optimize chord with less than 4 control points along blade span") if not blade_opt_options["structure"]["spar_cap_ss"]["flag"]: - blade_opt_options["structure"]["spar_cap_ss"]["n_opt"] = self.modeling_options["RotorSE"]["n_span"] + blade_opt_options["structure"]["spar_cap_ss"]["n_opt"] = self.modeling_options["WISDEM"]["RotorSE"][ + "n_span" + ] elif blade_opt_options["structure"]["spar_cap_ss"]["n_opt"] < 4: raise ValueError("Cannot optimize spar cap suction side with less than 4 control points along blade span") if not blade_opt_options["structure"]["spar_cap_ps"]["flag"]: - blade_opt_options["structure"]["spar_cap_ps"]["n_opt"] = self.modeling_options["RotorSE"]["n_span"] + blade_opt_options["structure"]["spar_cap_ps"]["n_opt"] = self.modeling_options["WISDEM"]["RotorSE"][ + "n_span" + ] elif blade_opt_options["structure"]["spar_cap_ps"]["n_opt"] < 4: raise ValueError("Cannot optimize spar cap pressure side with less than 4 control points along blade span") @@ -523,7 +612,7 @@ def write_ontology(self, wt_opt, fname_output): "components" ]["blade"]["outer_shape_bem"]["reference_axis"] # Webs positions - for i in range(self.modeling_options["RotorSE"]["n_webs"]): + for i in range(self.modeling_options["WISDEM"]["RotorSE"]["n_webs"]): if "rotation" in self.wt_init["components"]["blade"]["internal_structure_2d_fem"]["webs"]: self.wt_init["components"]["blade"]["internal_structure_2d_fem"]["webs"][i]["rotation"][ "grid" @@ -555,7 +644,7 @@ def write_ontology(self, wt_opt, fname_output): ] = wt_opt["blade.internal_structure_2d_fem.web_end_nd"][i, :].tolist() # Structural layers - for i in range(self.modeling_options["RotorSE"]["n_layers"]): + for i in range(self.modeling_options["WISDEM"]["RotorSE"]["n_layers"]): self.wt_init["components"]["blade"]["internal_structure_2d_fem"]["layers"][i]["thickness"][ "grid" ] = wt_opt["blade.internal_structure_2d_fem.s"].tolist() @@ -647,7 +736,7 @@ def write_ontology(self, wt_opt, fname_output): "blade.outer_shape_bem.s" ].tolist() K = [] - for i in range(self.modeling_options["RotorSE"]["n_span"]): + for i in range(self.modeling_options["WISDEM"]["RotorSE"]["n_span"]): Ki = np.zeros(21) Ki[11] = wt_opt["re.EA"][i] Ki[15] = wt_opt["re.EIxx"][i] @@ -660,7 +749,7 @@ def write_ontology(self, wt_opt, fname_output): "grid" ] = wt_opt["blade.outer_shape_bem.s"].tolist() I = [] - for i in range(self.modeling_options["RotorSE"]["n_span"]): + for i in range(self.modeling_options["WISDEM"]["RotorSE"]["n_span"]): Ii = np.zeros(21) Ii[0] = wt_opt["re.rhoA"][i] Ii[5] = -wt_opt["re.rhoA"][i] * wt_opt["re.precomp.y_cg"][i] @@ -732,7 +821,7 @@ def write_ontology(self, wt_opt, fname_output): "nacelle.bedplate_material" ] - if self.modeling_options["DriveSE"]["direct"]: + if self.modeling_options["WISDEM"]["DriveSE"]["direct"]: # Direct only s_nose = np.linspace(0.0, 1.0, len(wt_opt["nacelle.nose_diameter"])).tolist() s_bed = np.linspace(0.0, 1.0, len(wt_opt["nacelle.bedplate_wall_thickness"])).tolist() @@ -830,7 +919,7 @@ def write_ontology(self, wt_opt, fname_output): self.wt_init["components"]["nacelle"]["generator"]["C_Fes"] = float(wt_opt["generator.C_Fes"]) self.wt_init["components"]["nacelle"]["generator"]["C_PM"] = float(wt_opt["generator.C_PM"]) - if self.modeling_options["GeneratorSE"]["type"] in ["pmsg_outer"]: + if self.modeling_options["WISDEM"]["GeneratorSE"]["type"] in ["pmsg_outer"]: self.wt_init["components"]["nacelle"]["generator"]["N_c"] = float(wt_opt["generator.N_c"]) self.wt_init["components"]["nacelle"]["generator"]["b"] = float(wt_opt["generator.b"]) self.wt_init["components"]["nacelle"]["generator"]["c"] = float(wt_opt["generator.c"]) @@ -853,13 +942,13 @@ def write_ontology(self, wt_opt, fname_output): ) self.wt_init["components"]["nacelle"]["generator"]["B_tmax"] = float(wt_opt["generator.B_tmax"]) - if self.modeling_options["GeneratorSE"]["type"] in ["eesg", "pmsg_arms", "pmsg_disc"]: + if self.modeling_options["WISDEM"]["GeneratorSE"]["type"] in ["eesg", "pmsg_arms", "pmsg_disc"]: self.wt_init["components"]["nacelle"]["generator"]["tau_p"] = float(wt_opt["generator.tau_p"]) self.wt_init["components"]["nacelle"]["generator"]["h_ys"] = float(wt_opt["generator.h_ys"]) self.wt_init["components"]["nacelle"]["generator"]["h_yr"] = float(wt_opt["generator.h_yr"]) self.wt_init["components"]["nacelle"]["generator"]["b_arm"] = float(wt_opt["generator.b_arm"]) - elif self.modeling_options["GeneratorSE"]["type"] in ["scig", "dfig"]: + elif self.modeling_options["WISDEM"]["GeneratorSE"]["type"] in ["scig", "dfig"]: self.wt_init["components"]["nacelle"]["generator"]["B_symax"] = float(wt_opt["generator.B_symax"]) self.wt_init["components"]["nacelle"]["generator"]["S_Nmax"] = float(wt_opt["generator.S_Nmax"]) @@ -892,7 +981,7 @@ def write_ontology(self, wt_opt, fname_output): self.wt_init["components"]["tower"]["internal_structure_2d_fem"]["outfitting_factor"] = float( wt_opt["tower.outfitting_factor"] ) - for i in range(self.modeling_options["TowerSE"]["n_layers_tower"]): + for i in range(self.modeling_options["WISDEM"]["TowerSE"]["n_layers_tower"]): self.wt_init["components"]["tower"]["internal_structure_2d_fem"]["layers"][i]["thickness"][ "grid" ] = wt_opt["tower_grid.s"].tolist() @@ -929,7 +1018,7 @@ def write_ontology(self, wt_opt, fname_output): self.wt_init["components"]["monopile"]["internal_structure_2d_fem"]["outfitting_factor"] = float( wt_opt["monopile.outfitting_factor"] ) - for i in range(self.modeling_options["TowerSE"]["n_layers_monopile"]): + for i in range(self.modeling_options["WISDEM"]["TowerSE"]["n_layers_monopile"]): self.wt_init["components"]["monopile"]["internal_structure_2d_fem"]["layers"][i]["thickness"][ "grid" ] = wt_opt["monopile.s"].tolist() @@ -953,7 +1042,7 @@ def write_ontology(self, wt_opt, fname_output): # Update controller if self.modeling_options["flags"]["control"]: - self.wt_init["control"]["tsr"] = float(wt_opt["control.rated_TSR"]) + self.wt_init["control"]["torque"]["tsr"] = float(wt_opt["control.rated_TSR"]) # Write yamls with updated values sch.write_geometry_yaml(self.wt_init, fname_output) diff --git a/wisdem/glue_code/gc_PoseOptimization.py b/wisdem/glue_code/gc_PoseOptimization.py index ad180581e..45e8f427b 100644 --- a/wisdem/glue_code/gc_PoseOptimization.py +++ b/wisdem/glue_code/gc_PoseOptimization.py @@ -27,7 +27,11 @@ def get_number_design_variables(self): if blade_opt["aero_shape"]["chord"]["flag"]: n_DV += blade_opt["aero_shape"]["chord"]["n_opt"] - 3 if blade_opt["aero_shape"]["af_positions"]["flag"]: - n_DV += self.modeling["RotorSE"]["n_af_span"] - blade_opt["aero_shape"]["af_positions"]["af_start"] - 1 + n_DV += ( + self.modeling["WISDEM"]["RotorSE"]["n_af_span"] + - blade_opt["aero_shape"]["af_positions"]["af_start"] + - 1 + ) if blade_opt["structure"]["spar_cap_ss"]["flag"]: n_DV += blade_opt["structure"]["spar_cap_ss"]["n_opt"] - 2 if ( @@ -42,13 +46,17 @@ def get_number_design_variables(self): # if self.opt["design_variables"]["control"]["servo"]["torque_control"]["flag"]: # n_DV += 2 if tower_opt["outer_diameter"]["flag"]: - n_DV += self.modeling["TowerSE"]["n_height_tower"] + n_DV += self.modeling["WISDEM"]["TowerSE"]["n_height_tower"] if tower_opt["layer_thickness"]["flag"]: - n_DV += (self.modeling["TowerSE"]["n_height_tower"] - 1) * self.modeling["TowerSE"]["n_layers_tower"] + n_DV += (self.modeling["WISDEM"]["TowerSE"]["n_height_tower"] - 1) * self.modeling["WISDEM"]["TowerSE"][ + "n_layers_tower" + ] if mono_opt["outer_diameter"]["flag"]: - n_DV += self.modeling["TowerSE"]["n_height_monopile"] + n_DV += self.modeling["WISDEM"]["TowerSE"]["n_height_monopile"] if mono_opt["layer_thickness"]["flag"]: - n_DV += (self.modeling["TowerSE"]["n_height_monopile"] - 1) * self.modeling["TowerSE"]["n_layers_monopile"] + n_DV += (self.modeling["WISDEM"]["TowerSE"]["n_height_monopile"] - 1) * self.modeling["WISDEM"]["TowerSE"][ + "n_layers_monopile" + ] if hub_opt["cone"]["flag"]: n_DV += 1 if hub_opt["hub_diameter"]["flag"]: @@ -80,72 +88,103 @@ def get_number_design_variables(self): n_DV += 2 if drive_opt["bedplate_wall_thickness"]["flag"]: n_DV += 4 - if self.opt["driver"]["form"] == "central": + if self.opt["driver"]["optimization"]["form"] == "central": n_DV *= 2 return n_DV def _get_step_size(self): # If a step size for the driver-level finite differencing is provided, use that step size. Otherwise use a default value. - return 1.0e-6 if not "step_size" in self.opt["driver"] else self.opt["driver"]["step_size"] + return 1.0e-6 if not "step_size" in self.opt["driver"]["optimization"] else self.opt["driver"]["optimization"]["step_size"] def set_driver(self, wt_opt): folder_output = self.opt["general"]["folder_output"] - step_size = self._get_step_size() - - # Solver has specific meaning in OpenMDAO - wt_opt.model.approx_totals(method="fd", step=step_size, form=self.opt["driver"]["form"]) - - # Set optimization solver and options. First, Scipy's SLSQP - if self.opt["driver"]["solver"] == "SLSQP": - wt_opt.driver = om.ScipyOptimizeDriver() - wt_opt.driver.options["optimizer"] = self.opt["driver"]["solver"] - wt_opt.driver.options["tol"] = self.opt["driver"]["tol"] - wt_opt.driver.options["maxiter"] = self.opt["driver"]["max_iter"] - - # The next two optimization methods require pyOptSparse. - elif self.opt["driver"]["solver"] == "CONMIN": - try: - from openmdao.api import pyOptSparseDriver - except: - raise ImportError( - "You requested the optimization solver CONMIN, but you have not installed the pyOptSparseDriver. Please do so and rerun." - ) - wt_opt.driver = pyOptSparseDriver() - wt_opt.driver.options["optimizer"] = self.opt["driver"]["solver"] - wt_opt.driver.opt_settings["ITMAX"] = self.opt["driver"]["max_iter"] - - elif self.opt["driver"]["solver"] == "SNOPT": - try: - from openmdao.api import pyOptSparseDriver - except: - raise ImportError( - "You requested the optimization solver SNOPT, but you have not installed the pyOptSparseDriver. Please do so and rerun." - ) - wt_opt.driver = pyOptSparseDriver() - try: - wt_opt.driver.options["optimizer"] = self.opt["driver"]["solver"] - except: - raise ImportError( - "You requested the optimization solver SNOPT, but you have not installed it within the pyOptSparseDriver. Please do so and rerun." - ) - wt_opt.driver.opt_settings["Major optimality tolerance"] = float(self.opt["driver"]["tol"]) - wt_opt.driver.opt_settings["Major iterations limit"] = int(self.opt["driver"]["max_major_iter"]) - wt_opt.driver.opt_settings["Iterations limit"] = int(self.opt["driver"]["max_minor_iter"]) - wt_opt.driver.opt_settings["Major feasibility tolerance"] = float(self.opt["driver"]["tol"]) - wt_opt.driver.opt_settings["Summary file"] = os.path.join(folder_output, "SNOPT_Summary_file.txt") - wt_opt.driver.opt_settings["Print file"] = os.path.join(folder_output, "SNOPT_Print_file.txt") - if "hist_file_name" in self.opt["driver"]: - wt_opt.driver.hist_file = self.opt["driver"]["hist_file_name"] - if "verify_level" in self.opt["driver"]: - wt_opt.driver.opt_settings["Verify level"] = self.opt["driver"]["verify_level"] - # wt_opt.driver.declare_coloring() - if "hotstart_file" in self.opt["driver"]: - wt_opt.driver.hotstart_file = self.opt["driver"]["hotstart_file"] + if self.opt['driver']['optimization']['flag']: + step_size = self._get_step_size() + + # Solver has specific meaning in OpenMDAO + wt_opt.model.approx_totals(method="fd", step=step_size, form=self.opt["driver"]["optimization"]["form"]) + + # Set optimization solver and options. First, Scipy's SLSQP + if self.opt["driver"]["optimization"]["solver"] == "SLSQP": + wt_opt.driver = om.ScipyOptimizeDriver() + wt_opt.driver.options["optimizer"] = self.opt["driver"]["optimization"]["solver"] + wt_opt.driver.options["tol"] = self.opt["driver"]["optimization"]["tol"] + wt_opt.driver.options["maxiter"] = self.opt["driver"]["optimization"]["max_iter"] + + # The next two optimization methods require pyOptSparse. + elif self.opt["driver"]["optimization"]["solver"] == "CONMIN": + try: + from openmdao.api import pyOptSparseDriver + except: + raise ImportError( + "You requested the optimization solver CONMIN, but you have not installed the pyOptSparseDriver. Please do so and rerun." + ) + wt_opt.driver = pyOptSparseDriver() + wt_opt.driver.options["optimizer"] = self.opt["driver"]["optimization"]["solver"] + wt_opt.driver.opt_settings["ITMAX"] = self.opt["driver"]["optimization"]["max_iter"] + + elif self.opt["driver"]["optimization"]["solver"] == "SNOPT": + try: + from openmdao.api import pyOptSparseDriver + except: + raise ImportError( + "You requested the optimization solver SNOPT, but you have not installed the pyOptSparseDriver. Please do so and rerun." + ) + wt_opt.driver = pyOptSparseDriver() + try: + wt_opt.driver.options["optimizer"] = self.opt["driver"]["optimization"]["solver"] + except: + raise ImportError( + "You requested the optimization solver SNOPT, but you have not installed it within the pyOptSparseDriver. Please do so and rerun." + ) + wt_opt.driver.opt_settings["Major optimality tolerance"] = float(self.opt["driver"]["optimization"]["tol"]) + wt_opt.driver.opt_settings["Major iterations limit"] = int(self.opt["driver"]["optimization"]["max_major_iter"]) + wt_opt.driver.opt_settings["Iterations limit"] = int(self.opt["driver"]["optimization"]["max_minor_iter"]) + wt_opt.driver.opt_settings["Major feasibility tolerance"] = float(self.opt["driver"]["optimization"]["tol"]) + wt_opt.driver.opt_settings["Summary file"] = os.path.join(folder_output, "SNOPT_Summary_file.txt") + wt_opt.driver.opt_settings["Print file"] = os.path.join(folder_output, "SNOPT_Print_file.txt") + if "hist_file_name" in self.opt["driver"]["optimization"]: + wt_opt.driver.hist_file = self.opt["driver"]["optimization"]["hist_file_name"] + if "verify_level" in self.opt["driver"]["optimization"]: + wt_opt.driver.opt_settings["Verify level"] = self.opt["driver"]["optimization"]["verify_level"] + # wt_opt.driver.declare_coloring() + if "hotstart_file" in self.opt["driver"]["optimization"]: + wt_opt.driver.hotstart_file = self.opt["driver"]["optimization"]["hotstart_file"] + + else: + raise ValueError("The optimizer " + self.opt["driver"]["optimization"]["solver"] + "is not yet supported!") + + elif self.opt['driver']['design_of_experiments']['flag']: + if self.opt['driver']['design_of_experiments']['generator'].lower() == 'uniform': + generator = om.UniformGenerator( + num_samples = self.opt['driver']['design_of_experiments']['num_samples'], + seed = self.opt['driver']['design_of_experiments']['seed']) + elif self.opt['driver']['design_of_experiments']['generator'].lower() == 'fullfact': + generator = om.FullFactorialGenerator( + levels = self.opt['driver']['design_of_experiments']['num_samples']) + elif self.opt['driver']['design_of_experiments']['generator'].lower() == 'plackettburman': + generator = om.PlackettBurmanGenerator() + elif self.opt['driver']['design_of_experiments']['generator'].lower() == 'boxbehnken': + generator = om.BoxBehnkenGenerator() + elif self.opt['driver']['design_of_experiments']['generator'].lower() == 'latinhypercube': + generator = om.LatinHypercubeGenerator( + samples = self.opt['driver']['design_of_experiments']['num_samples'], + criterion = self.opt['driver']['design_of_experiments']['criterion'], + seed = self.opt['driver']['design_of_experiments']['seed']) + else: + exit('The generator type {} is unsupported.'.format( + self.opt['driver']['design_of_experiments']['generator'])) + + # Initialize driver + wt_opt.driver = om.DOEDriver(generator) + + # options + wt_opt.driver.options['run_parallel'] = self.opt['driver']['design_of_experiments']['run_parallel'] else: - raise ValueError("The optimizer " + self.opt["driver"]["solver"] + "is not yet supported!") + exit('Design variables are set to be optimized or studied, but no driver is selected. Please enable a driver.') return wt_opt @@ -182,13 +221,16 @@ def set_objective(self, wt_opt): elif self.opt["merit_figure"] == "nacelle_mass": wt_opt.model.add_objective("drivese.nacelle_mass", ref=1e6) + elif self.opt["merit_figure"] == "nacelle_cost": + wt_opt.model.add_objective("tcc.nacelle_cost", ref=1e6) + elif self.opt["merit_figure"] == "Cp": if self.modeling["flags"]["blade"]: wt_opt.model.add_objective("rp.powercurve.Cp_regII", ref=-1.0) else: wt_opt.model.add_objective("ccblade.CP", ref=-1.0) else: - raise ValueError("The merit figure " + self.opt["merit_figure"] + " is not supported.") + raise ValueError("The merit figure " + self.opt["merit_figure"] + " is unknown or not supported.") return wt_opt @@ -223,7 +265,7 @@ def set_design_variables(self, wt_opt, wt_init): ) if blade_opt["aero_shape"]["af_positions"]["flag"]: - n_af = self.modeling["RotorSE"]["n_af_span"] + n_af = self.modeling["WISDEM"]["RotorSE"]["n_af_span"] indices = range(blade_opt["aero_shape"]["af_positions"]["af_start"], n_af - 1) af_pos_init = wt_init["components"]["blade"]["outer_shape_bem"]["airfoil_position"]["grid"] step_size = self._get_step_size() @@ -468,18 +510,18 @@ def set_constraints(self, wt_opt): ) if tower_constr["stress"]["flag"] or monopile_constr["stress"]["flag"]: - for k in range(self.modeling["TowerSE"]["nLC"]): - kstr = "" if self.modeling["TowerSE"]["nLC"] == 0 else str(k + 1) + for k in range(self.modeling["WISDEM"]["TowerSE"]["nLC"]): + kstr = "" if self.modeling["WISDEM"]["TowerSE"]["nLC"] == 0 else str(k + 1) wt_opt.model.add_constraint("towerse.post" + kstr + ".stress", upper=1.0) if tower_constr["global_buckling"]["flag"] or monopile_constr["global_buckling"]["flag"]: - for k in range(self.modeling["TowerSE"]["nLC"]): - kstr = "" if self.modeling["TowerSE"]["nLC"] == 0 else str(k + 1) + for k in range(self.modeling["WISDEM"]["TowerSE"]["nLC"]): + kstr = "" if self.modeling["WISDEM"]["TowerSE"]["nLC"] == 0 else str(k + 1) wt_opt.model.add_constraint("towerse.post" + kstr + ".global_buckling", upper=1.0) if tower_constr["shell_buckling"]["flag"] or monopile_constr["shell_buckling"]["flag"]: - for k in range(self.modeling["TowerSE"]["nLC"]): - kstr = "" if self.modeling["TowerSE"]["nLC"] == 0 else str(k + 1) + for k in range(self.modeling["WISDEM"]["TowerSE"]["nLC"]): + kstr = "" if self.modeling["WISDEM"]["TowerSE"]["nLC"] == 0 else str(k + 1) wt_opt.model.add_constraint("towerse.post" + kstr + ".shell_buckling", upper=1.0) if tower_constr["d_to_t"]["flag"] or monopile_constr["d_to_t"]["flag"]: @@ -503,8 +545,8 @@ def set_constraints(self, wt_opt): wt_opt.model.add_constraint("tcons.constr_tower_f_NPmargin", upper=0.0) elif tower_constr["frequency_1"]["flag"] or monopile_constr["frequency_1"]["flag"]: - for k in range(self.modeling["TowerSE"]["nLC"]): - kstr = "" if self.modeling["TowerSE"]["nLC"] == 0 else str(k + 1) + for k in range(self.modeling["WISDEM"]["TowerSE"]["nLC"]): + kstr = "" if self.modeling["WISDEM"]["TowerSE"]["nLC"] == 0 else str(k + 1) wt_opt.model.add_constraint( "towerse.post" + kstr + ".structural_frequencies", indices=[0], @@ -528,6 +570,24 @@ def set_constraints(self, wt_opt): if drive_constr[k]["flag"]: wt_opt.model.add_constraint("drivese.constr_" + k, lower=0.0) + # Floating platform and mooring constraints + float_constr = self.opt["constraints"]["floating"] + + if float_constr["operational_heel"]["flag"]: + wt_opt.model.add_constraint("floatingse.constr_operational_heel", upper=1.0) + + if float_constr["survival_heel"]["flag"]: + wt_opt.model.add_constraint("floatingse.constr_survival_heel", upper=1.0) + + if float_constr["max_surge"]["flag"]: + wt_opt.model.add_constraint("floatingse.constr_max_surge", upper=1.0) + + if float_constr["mooring_tension"]["flag"]: + wt_opt.model.add_constraint("floatingse.constr_axial_load", upper=1.0) + + if float_constr["mooring_length"]["flag"]: + wt_opt.model.add_constraint("floatingse.constr_mooring_length", upper=1.0) + return wt_opt def set_recorders(self, wt_opt): @@ -578,19 +638,25 @@ def set_initial(self, wt_opt, wt_init): wt_opt["stall_check.stall_margin"] = blade_constr["stall"]["margin"] * 180.0 / np.pi wt_opt["tcons.max_allowable_td_ratio"] = blade_constr["tip_deflection"]["margin"] - if self.modeling["flags"]["nacelle"] and self.modeling["DriveSE"]["direct"]: + if self.modeling["flags"]["nacelle"] and self.modeling["WISDEM"]["DriveSE"]["direct"]: drive_constr = self.opt["constraints"]["drivetrain"] wt_opt["drivese.access_diameter"] = drive_constr["access"]["lower_bound"] + if self.modeling["flags"]["floating"]: + float_constr = self.opt["constraints"]["floating"] + wt_opt["floatingse.max_surge_fraction"] = float_constr["max_surge"]["upper_bound"] + wt_opt.set_val("floatingse.operational_heel", float_constr["operational_heel"]["upper_bound"], units="rad") + wt_opt.set_val("floatingse.survival_heel", float_constr["survival_heel"]["upper_bound"], units="rad") + return wt_opt def set_restart(self, wt_opt): - if "warmstart_file" in self.opt["driver"]: + if "warmstart_file" in self.opt["driver"]["optimization"]: # Directly read the pyoptsparse sqlite db file from pyoptsparse import SqliteDict - db = SqliteDict(self.opt["driver"]["warmstart_file"]) + db = SqliteDict(self.opt["driver"]["optimization"]["warmstart_file"]) # Grab the last iteration's design variables last_key = db["last"] diff --git a/wisdem/glue_code/gc_WT_DataStruc.py b/wisdem/glue_code/gc_WT_DataStruc.py index b00709634..6a287e18d 100644 --- a/wisdem/glue_code/gc_WT_DataStruc.py +++ b/wisdem/glue_code/gc_WT_DataStruc.py @@ -28,7 +28,7 @@ def setup(self): # Airfoil dictionary inputs if modeling_options["flags"]["airfoils"]: airfoils = om.IndepVarComp() - rotorse_options = modeling_options["RotorSE"] + rotorse_options = modeling_options["WISDEM"]["RotorSE"] n_af = rotorse_options["n_af"] # Number of airfoils n_aoa = rotorse_options["n_aoa"] # Number of angle of attacks n_Re = rotorse_options["n_Re"] # Number of Reynolds, so far hard set at 1 @@ -80,7 +80,7 @@ def setup(self): self.add_subsystem( "blade", Blade( - rotorse_options=modeling_options["RotorSE"], + rotorse_options=modeling_options["WISDEM"]["RotorSE"], opt_options=opt_options, ), ) @@ -163,7 +163,7 @@ def setup(self): "bedplate_material", val="steel", desc="Material name identifier for the bedplate" ) - if modeling_options["DriveSE"]["direct"]: + if modeling_options["WISDEM"]["DriveSE"]["direct"]: # Direct only nacelle_ivc.add_output( "nose_diameter", val=np.zeros(2), units="m", desc="Diameter of nose (also called turret or spindle)" @@ -270,7 +270,7 @@ def setup(self): generator_ivc.add_output("C_Fes", val=0.0, units="USD/kg") generator_ivc.add_output("C_PM", val=0.0, units="USD/kg") - if modeling_options["GeneratorSE"]["type"] in ["pmsg_outer"]: + if modeling_options["WISDEM"]["GeneratorSE"]["type"] in ["pmsg_outer"]: generator_ivc.add_output("N_c", 0.0) generator_ivc.add_output("b", 0.0) generator_ivc.add_output("c", 0.0) @@ -287,19 +287,20 @@ def setup(self): generator_ivc.add_output("z_allow_deg", 0.0, units="deg") generator_ivc.add_output("B_tmax", 0.0, units="T") - if modeling_options["GeneratorSE"]["type"] in ["eesg", "pmsg_arms", "pmsg_disc"]: + if modeling_options["WISDEM"]["GeneratorSE"]["type"] in ["eesg", "pmsg_arms", "pmsg_disc"]: generator_ivc.add_output("tau_p", val=0.0, units="m") generator_ivc.add_output("h_ys", val=0.0, units="m") generator_ivc.add_output("h_yr", val=0.0, units="m") generator_ivc.add_output("b_arm", val=0.0, units="m") - elif modeling_options["GeneratorSE"]["type"] in ["scig", "dfig"]: + elif modeling_options["WISDEM"]["GeneratorSE"]["type"] in ["scig", "dfig"]: generator_ivc.add_output("B_symax", val=0.0, units="T") generator_ivc.add_output("S_Nmax", val=-0.2) else: # If using simple (regression) generator scaling, this is an optional input to override default values - n_pc = modeling_options["RotorSE"]["n_pc"] + n_pc = modeling_options["WISDEM"]["RotorSE"]["n_pc"] + generator_ivc.add_output("generator_radius_user", val=0.0, units="m") generator_ivc.add_output("generator_mass_user", val=0.0, units="kg") generator_ivc.add_output("generator_efficiency_user", val=np.zeros((n_pc, 2))) @@ -307,7 +308,7 @@ def setup(self): # Tower inputs if modeling_options["flags"]["tower"]: - tower_init_options = modeling_options["TowerSE"] + tower_init_options = modeling_options["WISDEM"]["TowerSE"] n_height_tower = tower_init_options["n_height_tower"] n_layers_tower = tower_init_options["n_layers_tower"] ivc = self.add_subsystem("tower", om.IndepVarComp()) @@ -350,7 +351,7 @@ def setup(self): # Monopile inputs if modeling_options["flags"]["monopile"]: - self.add_subsystem("monopile", Monopile(towerse_options=modeling_options["TowerSE"])) + self.add_subsystem("monopile", Monopile(towerse_options=modeling_options["WISDEM"]["TowerSE"])) if modeling_options["flags"]["floating_platform"]: self.add_subsystem("floating", Floating(floating_init_options=modeling_options["floating"])) @@ -1200,6 +1201,10 @@ def setup(self): desc="2D array of the width along the outer profile of a layer. The first dimension represents each layer, the second dimension represents each entry along blade span.", ) + ivc.add_output("joint_position", val=0.0, desc="Spanwise position of the segmentation joint.",) + ivc.add_output("joint_mass", val=0.0, desc="Mass of the joint.") + ivc.add_output("joint_cost", val=0.0, units='USD', desc="Cost of the joint.") + self.add_subsystem( "compute_internal_structure_2d_fem", Compute_Blade_Internal_Structure_2D_FEM(rotorse_options=rotorse_options), @@ -1859,25 +1864,49 @@ def setup(self): n_joints = floating_init_options["joints"]["n_joints"] n_members = floating_init_options["members"]["n_members"] - jivc = self.add_subsystem("floating_joints", om.IndepVarComp()) + jivc = self.add_subsystem("joints", om.IndepVarComp()) jivc.add_output("location", val=np.zeros((n_joints, 3)), units="m") + jivc.add_output("transition_node", val=np.zeros(3), units="m") for i in range(n_members): name_member = floating_init_options["members"]["name"][i] - ivc = self.add_subsystem("floating_member_" + name_member, om.IndepVarComp()) + ivc = self.add_subsystem("member_" + name_member, om.IndepVarComp()) n_grid = len(floating_init_options["members"]["grid_member_" + name_member]) n_layers = floating_init_options["members"]["n_layers"][i] n_ballasts = floating_init_options["members"]["n_ballasts"][i] + n_bulkheads = floating_init_options["members"]["n_bulkheads"][i] n_axial_joints = floating_init_options["members"]["n_axial_joints"][i] - ivc.add_output("grid", val=np.zeros(n_grid)) + ivc.add_output("s", val=np.zeros(n_grid)) ivc.add_output("outer_diameter", val=np.zeros(n_grid), units="m") - ivc.add_output("bulkhead_thickness", val=np.zeros(n_grid), units="m") + ivc.add_output("bulkhead_grid", val=np.zeros(n_bulkheads)) + ivc.add_output("bulkhead_thickness", val=np.zeros(n_bulkheads), units="m") + ivc.add_discrete_output("layer_materials", val=[""] * n_layers) ivc.add_output("layer_thickness", val=np.zeros((n_layers, n_grid)), units="m") + ivc.add_output("ballast_grid", val=np.zeros((n_ballasts, 2))) ivc.add_output("ballast_volume", val=np.zeros(n_ballasts), units="m**3") + ivc.add_discrete_output("ballast_materials", val=[""] * n_ballasts) ivc.add_output("grid_axial_joints", val=np.zeros(n_axial_joints)) + ivc.add_output("outfitting_factor", 0.0) + ivc.add_output("ring_stiffener_web_height", 0.0, units="m") + ivc.add_output("ring_stiffener_web_thickness", 0.0, units="m") + ivc.add_output("ring_stiffener_flange_width", 0.0, units="m") + ivc.add_output("ring_stiffener_flange_thickness", 0.0, units="m") + ivc.add_output("ring_stiffener_spacing", 0.0, units="m") + ivc.add_output("axial_stiffener_web_height", 0.0, units="m") + ivc.add_output("axial_stiffener_web_thickness", 0.0, units="m") + ivc.add_output("axial_stiffener_flange_width", 0.0, units="m") + ivc.add_output("axial_stiffener_flange_thickness", 0.0, units="m") + ivc.add_output("axial_stiffener_spacing", 0.0, units="m") self.add_subsystem("alljoints", CombineJoints(floating_init_options=floating_init_options), promotes=["*"]) + self.connect("joints.location", "location") + for i in range(n_members): + name_member = floating_init_options["members"]["name"][i] + self.connect("member_" + name_member + ".grid_axial_joints", "member_" + name_member + ":grid_axial_joints") + self.connect("member_" + name_member + ".outer_diameter", "member_" + name_member + ":outer_diameter") + self.connect("member_" + name_member + ".s", "member_" + name_member + ":s") + class CombineJoints(om.ExplicitComponent): def initialize(self): @@ -1894,7 +1923,18 @@ def setup(self): for i in range(n_members): iname = floating_init_options["members"]["name"][i] i_axial_joints = floating_init_options["members"]["n_axial_joints"][i] - self.add_input(iname + ":grid_axial_joints", val=np.zeros(i_axial_joints)) + i_grid = len(floating_init_options["members"]["grid_member_" + iname]) + + self.add_input("member_" + iname + ":s", val=np.zeros(i_grid)) + self.add_input("member_" + iname + ":outer_diameter", val=np.zeros(i_grid), units="m") + self.add_input("member_" + iname + ":grid_axial_joints", val=np.zeros(i_axial_joints)) + + self.add_output("member_" + iname + ":joint1", val=np.zeros(3), units="m") + self.add_output("member_" + iname + ":joint2", val=np.zeros(3), units="m") + self.add_output("member_" + iname + ":height", val=0.0, units="m") + self.add_output("member_" + iname + ":s_ghost1", val=0.0) + self.add_output("member_" + iname + ":s_ghost2", val=1.0) + self.add_discrete_output("member_" + iname + ":transition_flag", val=[False, False]) self.n_joint_tot += i_axial_joints self.add_output("joints_xyz", val=np.zeros((self.n_joint_tot, 3)), units="m") @@ -1903,8 +1943,9 @@ def setup(self): def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Unpack options floating_init_options = self.options["floating_init_options"] + memopt = floating_init_options["members"] n_joints = floating_init_options["joints"]["n_joints"] - n_members = floating_init_options["members"]["n_members"] + n_members = memopt["n_members"] # Unpack inputs locations = inputs["location"] @@ -1921,29 +1962,81 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): name2idx = dict(zip(floating_init_options["joints"]["name"], range(n_joints))) count = n_joints + # Initial biggest radius at each node + node_r = np.zeros(joints_xyz.shape[0]) + intersects = np.zeros(joints_xyz.shape[0]) + # Now add axial joints member_list = list(range(n_members)) while count < self.n_joint_tot: for k in member_list[:]: - if (floating_init_options["members"]["joint1"][k] in name2idx) and ( - floating_init_options["members"]["joint2"][k] in name2idx - ): + if (memopt["joint1"][k] in name2idx) and (memopt["joint2"][k] in name2idx): member_list.remove(k) else: continue - joint1xyz = locations_xyz[name2idx[floating_init_options["members"]["joint1"][k]], :] - joint2xyz = locations_xyz[name2idx[floating_init_options["members"]["joint2"][k]], :] + joint1xyz = joints_xyz[name2idx[memopt["joint1"][k]], :] + joint2xyz = joints_xyz[name2idx[memopt["joint2"][k]], :] dxyz = joint2xyz - joint1xyz - iname = floating_init_options["members"]["name"][k] - i_axial_joints = floating_init_options["members"]["n_axial_joints"][k] - i_axial_joint_names = floating_init_options["members"]["axial_joint_name_member_" + iname] + i_axial_joints = memopt["n_axial_joints"][k] + if i_axial_joints == 0: + continue + + iname = memopt["name"][k] + i_axial_joint_names = memopt["axial_joint_name_member_" + iname] + + s = 0.5 * inputs["member_" + iname + ":s"] + Rk = 0.5 * inputs["member_" + iname + ":outer_diameter"] + for a in range(i_axial_joints): - joints_xyz[count, :] = joint1xyz + inputs[iname + ":grid_axial_joints"][a] * dxyz - name2idx[i_axial_joint_names] = count + s_axial = inputs["member_" + iname + ":grid_axial_joints"][a] + joints_xyz[count, :] = joint1xyz + s_axial * dxyz + name2idx[i_axial_joint_names[a]] = count + + Ra = np.interp(s_axial, s, Rk) + node_r[count] = max(node_r[count], Ra) + intersects[count] += 1 + count += 1 + # Record starting and ending location for each member now. + # Also log biggest radius at each node intersection to compute ghost nodes + itrans = floating_init_options["transition_joint"] + for k in range(n_members): + iname = memopt["name"][k] + joint1id = name2idx[memopt["joint1"][k]] + joint2id = name2idx[memopt["joint2"][k]] + joint1xyz = joints_xyz[joint1id, :] + joint2xyz = joints_xyz[joint2id, :] + outputs["member_" + iname + ":joint1"] = joint1xyz + outputs["member_" + iname + ":joint2"] = joint2xyz + outputs["member_" + iname + ":height"] = np.sqrt(np.sum((joint2xyz - joint1xyz) ** 2)) + discrete_outputs["member_" + iname + ":transition_flag"] = [joint1id == itrans, joint2id == itrans] + + # Largest radius at connection points for this member + Rk = 0.5 * inputs["member_" + iname + ":outer_diameter"] + node_r[joint1id] = max(node_r[joint1id], Rk[0]) + node_r[joint2id] = max(node_r[joint2id], Rk[-1]) + intersects[joint1id] += 1 + intersects[joint2id] += 1 + + # Store the ghost node non-dimensional locations + for k in range(n_members): + iname = memopt["name"][k] + joint1id = name2idx[memopt["joint1"][k]] + joint2id = name2idx[memopt["joint2"][k]] + hk = outputs["member_" + iname + ":height"] + Rk = 0.5 * inputs["member_" + iname + ":outer_diameter"] + s_ghost1 = 0.0 + s_ghost2 = 1.0 + if intersects[joint1id] > 1 and node_r[joint1id] > Rk[0]: + s_ghost1 = node_r[joint1id] / hk + if intersects[joint2id] > 1 and node_r[joint2id] > Rk[-1]: + s_ghost2 = 1.0 - node_r[joint2id] / hk + outputs["member_" + iname + ":s_ghost1"] = s_ghost1 + outputs["member_" + iname + ":s_ghost2"] = s_ghost2 + # Store outputs outputs["joints_xyz"] = joints_xyz discrete_outputs["joints_name2idx"] = name2idx @@ -1959,40 +2052,85 @@ def setup(self): n_nodes = mooring_init_options["n_nodes"] n_lines = mooring_init_options["n_lines"] n_line_types = mooring_init_options["n_line_types"] - n_anchor_types = mooring_init_options["n_anchor_types"] + # n_anchor_types = mooring_init_options["n_anchor_types"] ivc = self.add_subsystem("mooring", om.IndepVarComp(), promotes=["*"]) ivc.add_discrete_output("node_names", val=[""] * n_nodes) + ivc.add_discrete_output("n_lines", val=0) # Needed for ORBIT ivc.add_output("nodes_location", val=np.zeros((n_nodes, 3)), units="m") ivc.add_output("nodes_mass", val=np.zeros(n_nodes), units="kg") ivc.add_output("nodes_volume", val=np.zeros(n_nodes), units="m**3") ivc.add_output("nodes_added_mass", val=np.zeros(n_nodes)) ivc.add_output("nodes_drag_area", val=np.zeros(n_nodes), units="m**2") ivc.add_discrete_output("nodes_joint_name", val=[""] * n_nodes) - ivc.add_discrete_output("line_id", val=[""] * n_lines) ivc.add_output("unstretched_length", val=np.zeros(n_lines), units="m") - ivc.add_discrete_output("n_lines", val=n_lines) - ivc.add_discrete_output("line_type_names", val=[""] * n_line_types) - ivc.add_output("line_diameter", val=np.zeros(n_line_types), units="m") - ivc.add_output("line_mass_density", val=np.zeros(n_line_types), units="kg/m") - ivc.add_output("line_stiffness", val=np.zeros(n_line_types), units="N") - ivc.add_output("line_breaking_load", val=np.zeros(n_line_types), units="N") - ivc.add_output("line_cost_rate", val=np.zeros(n_line_types), units="USD/m") - ivc.add_output("line_transverse_added_mass", val=np.zeros(n_line_types), units="kg/m") - ivc.add_output("line_tangential_added_mass", val=np.zeros(n_line_types), units="kg/m") - ivc.add_output("line_transverse_drag", val=np.zeros(n_line_types)) - ivc.add_output("line_tangential_drag", val=np.zeros(n_line_types)) - ivc.add_discrete_output("anchor_names", val=[""] * n_anchor_types) - ivc.add_output("anchor_mass", val=np.zeros(n_anchor_types), units="kg") - ivc.add_output("anchor_cost", val=np.zeros(n_anchor_types), units="USD") - ivc.add_output("anchor_max_vertical_load", val=np.zeros(n_anchor_types), units="N") - ivc.add_output("anchor_max_lateral_load", val=np.zeros(n_anchor_types), units="N") - - self.add_subsystem("moormass", MooringMassProps(mooring_init_options=mooring_init_options), promotes=["*"]) + ivc.add_discrete_output("line_id", val=[""] * n_lines) + # ivc.add_discrete_output("n_lines", val=n_lines) + ivc.add_discrete_output("line_type_names", val=[""] * n_line_types) ## For MoorDyn + ivc.add_output("line_diameter", val=np.zeros(n_lines), units="m") + ivc.add_output("line_mass_density_coeff", val=np.zeros(n_lines), units="kg/m**3") + ivc.add_output("line_stiffness_coeff", val=np.zeros(n_lines), units="N/m**2") + ivc.add_output("line_breaking_load_coeff", val=np.zeros(n_lines), units="N/m**2") + ivc.add_output("line_cost_rate_coeff", val=np.zeros(n_lines), units="USD/m**3") + ivc.add_output("line_transverse_added_mass_coeff", val=np.zeros(n_lines), units="kg/m**3") + ivc.add_output("line_tangential_added_mass_coeff", val=np.zeros(n_lines), units="kg/m**3") + ivc.add_output("line_transverse_drag_coeff", val=np.zeros(n_lines), units="N/m**2") + ivc.add_output("line_tangential_drag_coeff", val=np.zeros(n_lines), units="N/m**2") + # ivc.add_discrete_output("anchor_names", val=[""] * n_anchor_types) + ivc.add_output("anchor_mass", val=np.zeros(n_lines), units="kg") + ivc.add_output("anchor_cost", val=np.zeros(n_lines), units="USD") + ivc.add_output("anchor_max_vertical_load", val=np.zeros(n_lines), units="N") + ivc.add_output("anchor_max_lateral_load", val=np.zeros(n_lines), units="N") + + self.add_subsystem("moorprop", MooringProperties(mooring_init_options=mooring_init_options), promotes=["*"]) self.add_subsystem("moorjoint", MooringJoints(mooring_init_options=mooring_init_options), promotes=["*"]) +class MooringProperties(om.ExplicitComponent): + def initialize(self): + self.options.declare("mooring_init_options") + + def setup(self): + mooring_init_options = self.options["mooring_init_options"] + n_lines = mooring_init_options["n_lines"] + + self.add_input("line_diameter", val=np.zeros(n_lines), units="m") + self.add_input("line_mass_density_coeff", val=np.zeros(n_lines), units="kg/m**3") + self.add_input("line_stiffness_coeff", val=np.zeros(n_lines), units="N/m**2") + self.add_input("line_breaking_load_coeff", val=np.zeros(n_lines), units="N/m**2") + self.add_input("line_cost_rate_coeff", val=np.zeros(n_lines), units="USD/m**3") + self.add_input("line_transverse_added_mass_coeff", val=np.zeros(n_lines), units="kg/m**3") + self.add_input("line_tangential_added_mass_coeff", val=np.zeros(n_lines), units="kg/m**3") + self.add_input("line_transverse_drag_coeff", val=np.zeros(n_lines), units="N/m**2") + self.add_input("line_tangential_drag_coeff", val=np.zeros(n_lines), units="N/m**2") + + self.add_output("line_mass_density", val=np.zeros(n_lines), units="kg/m") + self.add_output("line_stiffness", val=np.zeros(n_lines), units="N") + self.add_output("line_breaking_load", val=np.zeros(n_lines), units="N") + self.add_output("line_cost_rate", val=np.zeros(n_lines), units="USD/m") + self.add_output("line_transverse_added_mass", val=np.zeros(n_lines), units="kg/m") + self.add_output("line_tangential_added_mass", val=np.zeros(n_lines), units="kg/m") + self.add_output("line_transverse_drag", val=np.zeros(n_lines)) + self.add_output("line_tangential_drag", val=np.zeros(n_lines)) + + def compute(self, inputs, outputs): + d = inputs["line_diameter"] + d2 = d * d + varlist = [ + "line_mass_density", + "line_stiffness", + "line_breaking_load", + "line_cost_rate", + "line_transverse_added_mass", + "line_tangential_added_mass", + "line_transverse_drag", + "line_tangential_drag", + ] + for var in varlist: + outputs[var] = d2 * inputs[var + "_coeff"] + + class MooringJoints(om.ExplicitComponent): def initialize(self): self.options.declare("mooring_init_options") @@ -2000,13 +2138,20 @@ def initialize(self): def setup(self): mooring_init_options = self.options["mooring_init_options"] n_nodes = mooring_init_options["n_nodes"] + n_attach = mooring_init_options["n_attach"] + n_lines = mooring_init_options["n_lines"] self.add_discrete_input("nodes_joint_name", val=[""] * n_nodes) self.add_discrete_input("joints_name2idx", val={}) self.add_input("nodes_location", val=np.zeros((n_nodes, 3)), units="m") self.add_input("joints_xyz", shape_by_conn=True, units="m") - self.add_output("nodes_location_full", val=np.zeros((n_nodes, 3)), units="m") + self.add_output("mooring_nodes", val=np.zeros((n_nodes, 3)), units="m") + self.add_output("fairlead_nodes", val=np.zeros((n_attach, 3)), units="m") + self.add_output("fairlead", val=np.zeros(n_lines), units="m") + self.add_output("fairlead_radius", val=np.zeros(n_attach), units="m") + self.add_output("anchor_nodes", val=np.zeros((n_lines, 3)), units="m") + self.add_output("anchor_radius", val=np.zeros(n_lines), units="m") def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): mooring_init_options = self.options["mooring_init_options"] @@ -2021,46 +2166,20 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): continue idx = idx_map[node_joints[k]] node_loc[k, :] = joints_loc[idx, :] + outputs["mooring_nodes"] = node_loc - outputs["nodes_location_full"] = node_loc - - -class MooringMassProps(om.ExplicitComponent): - def initialize(self): - self.options.declare("mooring_init_options") - - def setup(self): - mooring_init_options = self.options["mooring_init_options"] - n_lines = mooring_init_options["n_lines"] - n_line_types = mooring_init_options["n_line_types"] - - self.add_discrete_input("line_id", val=[""] * n_lines) - self.add_input("unstretched_length", val=np.zeros(n_lines), units="m") - self.add_discrete_input("line_names", val=[""] * n_line_types) - self.add_input("line_mass_density", val=np.zeros(n_line_types), units="kg/m") - self.add_input("line_cost_rate", val=np.zeros(n_line_types), units="USD/m") + node_loc = np.unique(node_loc, axis=0) + tol = 0.5 + z_fair = node_loc[:, 2].max() + z_anch = node_loc[:, 2].min() + ifair = np.where(np.abs(node_loc[:, 2] - z_fair) < tol)[0] + ianch = np.where(np.abs(node_loc[:, 2] - z_anch) < tol)[0] - self.add_output("line_mass", val=np.zeros(n_lines), units="kg") - self.add_output("mooring_mass", val=0.0, units="kg") - self.add_output("line_cost", val=np.zeros(n_lines), units="USD") - self.add_output("mooring_cost", val=0.0, units="USD") - - def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - mooring_init_options = self.options["mooring_init_options"] - n_lines = mooring_init_options["n_lines"] - - line_mass = np.zeros(n_lines) - line_cost = np.zeros(n_lines) - for ii, iname in enumerate(discrete_inputs["line_names"]): - for jj, jname in enumerate(discrete_inputs["line_id"]): - if jname == iname: - line_mass[jj] = inputs["line_mass_density"][ii] * inputs["unstretched_length"][jj] - line_cost[jj] = inputs["line_cost_rate"][ii] * inputs["unstretched_length"][jj] - - outputs["line_mass"] = line_mass - outputs["line_cost"] = line_cost - outputs["mooring_mass"] = line_mass.sum() - outputs["mooring_cost"] = line_cost.sum() + outputs["fairlead_nodes"] = node_loc[ifair, :] + outputs["anchor_nodes"] = node_loc[ianch, :] + outputs["fairlead"] = z_fair + outputs["fairlead_radius"] = np.sqrt(np.sum(node_loc[ifair, :2] ** 2, axis=1)) + outputs["anchor_radius"] = np.sqrt(np.sum(node_loc[ianch, :2] ** 2, axis=1)) class ComputeMaterialsProperties(om.ExplicitComponent): @@ -2337,11 +2456,11 @@ def setup(self): modeling_options = self.options["modeling_options"] if modeling_options["flags"]["blade"]: - n_span = modeling_options["RotorSE"]["n_span"] + n_span = modeling_options["WISDEM"]["RotorSE"]["n_span"] else: n_span = 0 if modeling_options["flags"]["tower"]: - n_height_tower = modeling_options["TowerSE"]["n_height_tower"] + n_height_tower = modeling_options["WISDEM"]["TowerSE"]["n_height_tower"] else: n_height_tower = 0 @@ -2441,9 +2560,17 @@ def compute(self, inputs, outputs): if modeling_options["flags"]["tower"]: if inputs["hub_height_user"] != 0.0: outputs["hub_height"] = inputs["hub_height_user"] - outputs["tower_ref_axis"][:, 2] = (inputs["tower_ref_axis_user"][:, 2] - inputs["tower_ref_axis_user"][0, 2] - + inputs["distance_tt_hub"]) * inputs["hub_height_user"] / (inputs["tower_ref_axis_user"][-1, 2] - + inputs["distance_tt_hub"]) + inputs["tower_ref_axis_user"][0, 2] - inputs["distance_tt_hub"] + outputs["tower_ref_axis"][:, 2] = ( + ( + inputs["tower_ref_axis_user"][:, 2] + - inputs["tower_ref_axis_user"][0, 2] + + inputs["distance_tt_hub"] + ) + * inputs["hub_height_user"] + / (inputs["tower_ref_axis_user"][-1, 2] + inputs["distance_tt_hub"]) + + inputs["tower_ref_axis_user"][0, 2] + - inputs["distance_tt_hub"] + ) else: outputs["hub_height"] = inputs["tower_ref_axis_user"][-1, 2] + inputs["distance_tt_hub"] outputs["tower_ref_axis"] = inputs["tower_ref_axis_user"] diff --git a/wisdem/glue_code/gc_WT_InitModel.py b/wisdem/glue_code/gc_WT_InitModel.py index a6c5be4f7..fa7342199 100644 --- a/wisdem/glue_code/gc_WT_InitModel.py +++ b/wisdem/glue_code/gc_WT_InitModel.py @@ -107,7 +107,7 @@ def assign_blade_values(wt_opt, modeling_options, blade): def assign_outer_shape_bem_values(wt_opt, modeling_options, outer_shape_bem): # Function to assign values to the openmdao component Blade_Outer_Shape_BEM - nd_span = modeling_options["RotorSE"]["nd_span"] + nd_span = modeling_options["WISDEM"]["RotorSE"]["nd_span"] wt_opt["blade.outer_shape_bem.af_position"] = outer_shape_bem["airfoil_position"]["grid"] wt_opt["blade.opt_var.af_position"] = outer_shape_bem["airfoil_position"]["grid"] @@ -193,8 +193,8 @@ def assign_outer_shape_bem_values(wt_opt, modeling_options, outer_shape_bem): def assign_internal_structure_2d_fem_values(wt_opt, modeling_options, internal_structure_2d_fem): # Function to assign values to the openmdao component Blade_Internal_Structure_2D_FEM - n_span = modeling_options["RotorSE"]["n_span"] - n_webs = modeling_options["RotorSE"]["n_webs"] + n_span = modeling_options["WISDEM"]["RotorSE"]["n_span"] + n_webs = modeling_options["WISDEM"]["RotorSE"]["n_webs"] web_rotation = np.zeros((n_webs, n_span)) web_offset_y_pa = np.zeros((n_webs, n_span)) @@ -212,7 +212,7 @@ def assign_internal_structure_2d_fem_values(wt_opt, modeling_options, internal_s else: raise ValueError( "Invalid rotation reference for web " - + self.modeling_options["RotorSE"]["web_name"][i] + + self.modeling_options["WISDEM"]["RotorSE"]["web_name"][i] + ". Please check the yaml input file" ) else: @@ -253,7 +253,7 @@ def assign_internal_structure_2d_fem_values(wt_opt, modeling_options, internal_s else: raise ValueError("Webs definition not supported. Please check the yaml input.") - n_layers = modeling_options["RotorSE"]["n_layers"] + n_layers = modeling_options["WISDEM"]["RotorSE"]["n_layers"] layer_name = n_layers * [""] layer_mat = n_layers * [""] thickness = np.zeros((n_layers, n_span)) @@ -272,8 +272,8 @@ def assign_internal_structure_2d_fem_values(wt_opt, modeling_options, internal_s # Loop through the layers, interpolate along blade span, assign the inputs, and the definition flag for i in range(n_layers): - layer_name[i] = modeling_options["RotorSE"]["layer_name"][i] - layer_mat[i] = modeling_options["RotorSE"]["layer_mat"][i] + layer_name[i] = modeling_options["WISDEM"]["RotorSE"]["layer_name"][i] + layer_mat[i] = modeling_options["WISDEM"]["RotorSE"]["layer_mat"][i] thickness[i] = np.interp( nd_span, internal_structure_2d_fem["layers"][i]["thickness"]["grid"], @@ -443,8 +443,8 @@ def assign_internal_structure_2d_fem_values(wt_opt, modeling_options, internal_s if "web" in internal_structure_2d_fem["layers"][i]: web_name_i = internal_structure_2d_fem["layers"][i]["web"] - for j in range(modeling_options["RotorSE"]["n_webs"]): - if web_name_i == modeling_options["RotorSE"]["web_name"][j]: + for j in range(modeling_options["WISDEM"]["RotorSE"]["n_webs"]): + if web_name_i == modeling_options["WISDEM"]["RotorSE"]["web_name"][j]: k = j + 1 break layer_web[i] = k @@ -470,13 +470,18 @@ def assign_internal_structure_2d_fem_values(wt_opt, modeling_options, internal_s wt_opt["blade.internal_structure_2d_fem.layer_end_nd_yaml"] = layer_end_nd wt_opt["blade.internal_structure_2d_fem.layer_rotation_yaml"] = layer_rotation + # Spanwise joint + wt_opt["blade.internal_structure_2d_fem.joint_position"] = internal_structure_2d_fem["joint"]["position"] + wt_opt["blade.internal_structure_2d_fem.joint_mass"] = internal_structure_2d_fem["joint"]["mass"] + wt_opt["blade.internal_structure_2d_fem.joint_cost"] = internal_structure_2d_fem["joint"]["cost"] + return wt_opt def assign_te_flaps_values(wt_opt, modeling_options, blade): # Function to assign the trailing edge flaps data to the openmdao data structure - if modeling_options["RotorSE"]["n_te_flaps"] > 0: - n_te_flaps = modeling_options["RotorSE"]["n_te_flaps"] + if modeling_options["WISDEM"]["RotorSE"]["n_te_flaps"] > 0: + n_te_flaps = modeling_options["WISDEM"]["RotorSE"]["n_te_flaps"] for i in range(n_te_flaps): wt_opt["dac_ivc.te_flap_start"][i] = blade["aerodynamic_control"]["te_flaps"][i]["span_start"] wt_opt["dac_ivc.te_flap_end"][i] = blade["aerodynamic_control"]["te_flaps"][i]["span_end"] @@ -599,7 +604,7 @@ def assign_hub_values(wt_opt, hub): def assign_nacelle_values(wt_opt, modeling_options, nacelle): # Common direct and geared - wt_opt["nacelle.uptilt"] = nacelle["drivetrain"]["uptilt_angle"] + wt_opt["nacelle.uptilt"] = nacelle["drivetrain"]["uptilt"] wt_opt["nacelle.distance_tt_hub"] = nacelle["drivetrain"]["distance_tt_hub"] wt_opt["nacelle.overhang"] = nacelle["drivetrain"]["overhang"] wt_opt["nacelle.distance_hub2mb"] = nacelle["drivetrain"]["distance_hub_mb"] @@ -620,7 +625,7 @@ def assign_nacelle_values(wt_opt, modeling_options, nacelle): wt_opt["nacelle.lss_wall_thickness"] = nacelle["drivetrain"]["lss_wall_thickness"] wt_opt["nacelle.lss_diameter"] = nacelle["drivetrain"]["lss_diameter"] - if modeling_options["DriveSE"]["direct"]: + if modeling_options["WISDEM"]["DriveSE"]["direct"]: # Direct only wt_opt["nacelle.nose_wall_thickness"] = nacelle["drivetrain"]["nose_wall_thickness"] wt_opt["nacelle.nose_diameter"] = nacelle["drivetrain"]["nose_diameter"] @@ -643,13 +648,14 @@ def assign_nacelle_values(wt_opt, modeling_options, nacelle): wt_opt["nacelle.hss_material"] = nacelle["drivetrain"]["hss_material"] if not modeling_options["flags"]["generator"]: + wt_opt["generator.generator_radius_user"] = nacelle["drivetrain"]["generator_radius_user"] wt_opt["generator.generator_mass_user"] = nacelle["drivetrain"]["generator_mass_user"] eff_user = np.c_[ nacelle["drivetrain"]["generator_rpm_efficiency_user"]["grid"], nacelle["drivetrain"]["generator_rpm_efficiency_user"]["values"], ] - n_pc = modeling_options["RotorSE"]["n_pc"] + n_pc = modeling_options["WISDEM"]["RotorSE"]["n_pc"] if np.any(eff_user): newrpm = np.linspace(eff_user[:, 0].min(), eff_user[:, 0].max(), n_pc) neweff = np.interp(newrpm, eff_user[:, 0], eff_user[:, 1]) @@ -719,7 +725,7 @@ def assign_generator_values(wt_opt, modeling_options, nacelle): wt_opt["generator.C_Fes"] = nacelle["generator"]["C_Fes"] wt_opt["generator.C_PM"] = nacelle["generator"]["C_PM"] - if modeling_options["GeneratorSE"]["type"] in ["pmsg_outer"]: + if modeling_options["WISDEM"]["GeneratorSE"]["type"] in ["pmsg_outer"]: wt_opt["generator.N_c"] = nacelle["generator"]["N_c"] wt_opt["generator.b"] = nacelle["generator"]["b"] wt_opt["generator.c"] = nacelle["generator"]["c"] @@ -736,13 +742,13 @@ def assign_generator_values(wt_opt, modeling_options, nacelle): wt_opt["generator.z_allow_deg"] = nacelle["generator"]["z_allow_deg"] wt_opt["generator.B_tmax"] = nacelle["generator"]["B_tmax"] - if modeling_options["GeneratorSE"]["type"] in ["eesg", "pmsg_arms", "pmsg_disc"]: + if modeling_options["WISDEM"]["GeneratorSE"]["type"] in ["eesg", "pmsg_arms", "pmsg_disc"]: wt_opt["generator.tau_p"] = nacelle["generator"]["tau_p"] wt_opt["generator.h_ys"] = nacelle["generator"]["h_ys"] wt_opt["generator.h_yr"] = nacelle["generator"]["h_yr"] wt_opt["generator.b_arm"] = nacelle["generator"]["b_arm"] - elif modeling_options["GeneratorSE"]["type"] in ["scig", "dfig"]: + elif modeling_options["WISDEM"]["GeneratorSE"]["type"] in ["scig", "dfig"]: wt_opt["generator.B_symax"] = nacelle["generator"]["B_symax"] wt_opt["generator.S_Nmax"] = nacelle["generator"]["S_Nmax"] @@ -751,8 +757,8 @@ def assign_generator_values(wt_opt, modeling_options, nacelle): def assign_tower_values(wt_opt, modeling_options, tower): # Function to assign values to the openmdao component Tower - n_height = modeling_options["TowerSE"]["n_height_tower"] # Number of points along tower height - n_layers = modeling_options["TowerSE"]["n_layers_tower"] + n_height = modeling_options["WISDEM"]["TowerSE"]["n_height_tower"] # Number of points along tower height + n_layers = modeling_options["WISDEM"]["TowerSE"]["n_layers_tower"] svec = np.unique( np.r_[ @@ -822,8 +828,8 @@ def assign_tower_values(wt_opt, modeling_options, tower): def assign_monopile_values(wt_opt, modeling_options, monopile): # Function to assign values to the openmdao component Monopile - n_height = modeling_options["TowerSE"]["n_height_monopile"] # Number of points along monopile height - n_layers = modeling_options["TowerSE"]["n_layers_monopile"] + n_height = modeling_options["WISDEM"]["TowerSE"]["n_height_monopile"] # Number of points along monopile height + n_layers = modeling_options["WISDEM"]["TowerSE"]["n_layers_monopile"] svec = np.unique( np.r_[ @@ -887,41 +893,67 @@ def assign_floating_values(wt_opt, modeling_options, floating): n_joints = floating_init_options["joints"]["n_joints"] # Loop through joints and assign location values to openmdao entry for i in range(n_joints): - wt_opt["floating.floating_joints.location"][i, :] = floating["joints"][i]["location"] + wt_opt["floating.joints.location"][i, :] = floating["joints"][i]["location"] + + # Set transition joint/node + if modeling_options["floating"]["transition_joint"] is None: + itrans = np.argmax(wt_opt["floating.joints.location"][:, 2]) + else: + itrans = modeling_options["floating"]["transition_joint"] + wt_opt["floating.joints.transition_node"] = wt_opt["floating.joints.location"][itrans, :] n_members = floating_init_options["members"]["n_members"] # Loop through members and assign grid, outer diameter, layer thickness and ballast volume to openmdao entry. The distributed quantities are interpolated to a common grid for i in range(n_members): name_member = floating_init_options["members"]["name"][i] grid_member = floating_init_options["members"]["grid_member_" + floating_init_options["members"]["name"][i]] - wt_opt["floating.floating_member_" + name_member + ".grid"] = grid_member - wt_opt["floating.floating_member_" + name_member + ".outer_diameter"] = np.interp( + wt_opt["floating.member_" + name_member + ".s"] = grid_member + wt_opt["floating.member_" + name_member + ".outfitting_factor"] = floating["members"][i]["internal_structure"][ + "outfitting_factor" + ] + wt_opt["floating.member_" + name_member + ".outer_diameter"] = np.interp( grid_member, floating["members"][i]["outer_shape"]["outer_diameter"]["grid"], floating["members"][i]["outer_shape"]["outer_diameter"]["values"], ) if "bulkhead" in floating["members"][i]["internal_structure"]: - wt_opt["floating.floating_member_" + name_member + ".bulkhead_thickness"] = np.interp( - grid_member, - floating["members"][i]["internal_structure"]["bulkhead"]["thickness"]["grid"], + wt_opt["floating.member_" + name_member + ".bulkhead_grid"] = floating["members"][i]["internal_structure"][ + "bulkhead" + ]["thickness"]["grid"] + wt_opt["floating.member_" + name_member + ".bulkhead_thickness"] = ( floating["members"][i]["internal_structure"]["bulkhead"]["thickness"]["values"], ) + n_layers = floating_init_options["members"]["n_layers"][i] + layer_mat = [""] * n_layers for j in range(n_layers): - wt_opt["floating.floating_member_" + name_member + ".layer_thickness"][j, :] = np.interp( + wt_opt["floating.member_" + name_member + ".layer_thickness"][j, :] = np.interp( grid_member, floating["members"][i]["internal_structure"]["layers"][j]["thickness"]["grid"], floating["members"][i]["internal_structure"]["layers"][j]["thickness"]["values"], ) + layer_mat[j] = floating["members"][i]["internal_structure"]["layers"][j]["material"] + wt_opt["floating.member_" + name_member + ".layer_materials"] = layer_mat + n_ballasts = floating_init_options["members"]["n_ballasts"][i] + ballast_mat = [""] * n_ballasts for j in range(n_ballasts): + wt_opt["floating.member_" + name_member + ".ballast_grid"][j, :] = floating["members"][i][ + "internal_structure" + ]["ballasts"][j]["grid"] if floating_init_options["members"]["ballast_flag_member_" + name_member][j] == False: - wt_opt["floating.floating_member_" + name_member + ".ballast_volume"][j] = floating["members"][i][ + wt_opt["floating.member_" + name_member + ".ballast_volume"][j] = floating["members"][i][ "internal_structure" ]["ballasts"][j]["volume"] + ballast_mat[j] = floating["members"][i]["internal_structure"]["ballasts"][j]["material"] + else: + wt_opt["floating.member_" + name_member + ".ballast_volume"][j] = 0.0 + ballast_mat[j] = "seawater" + wt_opt["floating.member_" + name_member + ".ballast_materials"] = ballast_mat + if floating_init_options["members"]["n_axial_joints"][i] > 0: for j in range(floating_init_options["members"]["n_axial_joints"][i]): - wt_opt["floating.floating_member_" + name_member + ".grid_axial_joints"][j] = floating["members"][i][ + wt_opt["floating.member_" + name_member + ".grid_axial_joints"][j] = floating["members"][i][ "axial_joints" ][j]["grid"] @@ -937,11 +969,12 @@ def assign_mooring_values(wt_opt, modeling_options, mooring): n_line_types = mooring_init_options["n_line_types"] n_anchor_types = mooring_init_options["n_anchor_types"] + wt_opt["mooring.n_lines"] = n_lines # Needed for ORBIT wt_opt["mooring.node_names"] = [mooring["nodes"][i]["name"] for i in range(n_nodes)] wt_opt["mooring.nodes_joint_name"] = ["" for i in range(n_nodes)] - wt_opt["mooring.line_id"] = [mooring["lines"][i]["name"] for i in range(n_lines)] - wt_opt["mooring.line_names"] = [mooring["line_types"][i]["name"] for i in range(n_line_types)] - wt_opt["mooring.anchor_names"] = [mooring["anchor_types"][i]["name"] for i in range(n_anchor_types)] + wt_opt["mooring.line_id"] = [mooring["lines"][i]["line_type"] for i in range(n_lines)] + line_names = [mooring["line_types"][i]["name"] for i in range(n_line_types)] + anchor_names = [mooring["anchor_types"][i]["name"] for i in range(n_anchor_types)] for i in range(n_nodes): if "location" in mooring["nodes"][i]: wt_opt["mooring.nodes_location"][i, :] = mooring["nodes"][i]["location"] @@ -951,23 +984,51 @@ def assign_mooring_values(wt_opt, modeling_options, mooring): wt_opt["mooring.nodes_volume"][i] = mooring["nodes"][i]["node_volume"] wt_opt["mooring.nodes_drag_area"][i] = mooring["nodes"][i]["drag_area"] wt_opt["mooring.nodes_added_mass"][i] = mooring["nodes"][i]["added_mass"] + for i in range(n_lines): wt_opt["mooring.unstretched_length"][i] = mooring["lines"][i]["unstretched_length"] - for i in range(n_line_types): - wt_opt["mooring.line_diameter"][i] = mooring["line_types"][i]["diameter"] - wt_opt["mooring.line_mass_density"][i] = mooring["line_types"][i]["mass_density"] - wt_opt["mooring.line_stiffness"][i] = mooring["line_types"][i]["stiffness"] - wt_opt["mooring.line_breaking_load"][i] = mooring["line_types"][i]["breaking_load"] - wt_opt["mooring.line_cost"][i] = mooring["line_types"][i]["cost"] - wt_opt["mooring.line_transverse_added_mass"][i] = mooring["line_types"][i]["transverse_added_mass"] - wt_opt["mooring.line_tangential_added_mass"][i] = mooring["line_types"][i]["tangential_added_mass"] - wt_opt["mooring.line_transverse_drag"][i] = mooring["line_types"][i]["transverse_drag"] - wt_opt["mooring.line_tangential_drag"][i] = mooring["line_types"][i]["tangential_drag"] - for i in range(n_anchor_types): - wt_opt["mooring.anchor_mass"][i] = mooring["anchor_types"][i]["mass"] - wt_opt["mooring.anchor_cost"][i] = mooring["anchor_types"][i]["cost"] - wt_opt["mooring.anchor_max_vertical_load"][i] = mooring["anchor_types"][i]["max_vertical_load"] - wt_opt["mooring.anchor_max_lateral_load"][i] = mooring["anchor_types"][i]["max_lateral_load"] + + for jj, jname in enumerate(wt_opt["mooring.line_id"]): + node1 = mooring["lines"][jj]["node1"] + node2 = mooring["lines"][jj]["node2"] + for ii, iname in enumerate(line_names): + if jname == iname: + d2 = mooring["line_types"][ii]["diameter"] ** 2 + wt_opt["mooring.line_diameter"][jj] = mooring["line_types"][ii]["diameter"] + wt_opt["mooring.line_mass_density_coeff"][jj] = mooring["line_types"][ii]["mass_density"] / d2 + wt_opt["mooring.line_stiffness_coeff"][jj] = mooring["line_types"][ii]["stiffness"] / d2 + wt_opt["mooring.line_breaking_load_coeff"][jj] = mooring["line_types"][ii]["breaking_load"] / d2 + wt_opt["mooring.line_cost_rate_coeff"][jj] = mooring["line_types"][ii]["cost"] / d2 + wt_opt["mooring.line_transverse_added_mass_coeff"][jj] = ( + mooring["line_types"][ii]["transverse_added_mass"] / d2 + ) + wt_opt["mooring.line_tangential_added_mass_coeff"][jj] = ( + mooring["line_types"][ii]["tangential_added_mass"] / d2 + ) + wt_opt["mooring.line_transverse_drag_coeff"][jj] = mooring["line_types"][ii]["transverse_drag"] / d2 + wt_opt["mooring.line_tangential_drag_coeff"][jj] = mooring["line_types"][ii]["tangential_drag"] / d2 + for ii, iname in enumerate(wt_opt["mooring.node_names"]): + if node1 == iname or node2 == iname and mooring["nodes"][ii]["node_type"] == "fixed": + for kk, kname in enumerate(anchor_names): + if kname == mooring["nodes"][ii]["anchor_type"]: + wt_opt["mooring.anchor_mass"][jj] = mooring["anchor_types"][kk]["mass"] + wt_opt["mooring.anchor_cost"][jj] = mooring["anchor_types"][kk]["cost"] + wt_opt["mooring.anchor_max_vertical_load"][jj] = mooring["anchor_types"][kk][ + "max_vertical_load" + ] + wt_opt["mooring.anchor_max_lateral_load"][jj] = mooring["anchor_types"][kk]["max_lateral_load"] + + # Give warnings if we have different types or asymmetrical lines + if ( + np.unique(wt_opt["mooring.unstretched_length"]).size > 1 + or np.unique(wt_opt["mooring.line_diameter"]).size > 1 + or np.unique(wt_opt["mooring.line_mass_density_coeff"]).size > 1 + or np.unique(wt_opt["mooring.line_stiffness_coeff"]).size > 1 + or np.unique(wt_opt["mooring.anchor_mass"]).size > 1 + ): + print( + "WARNING: Multiple mooring line or anchor types entered, but can only process symmetrical arrangements for now" + ) return wt_opt @@ -1094,12 +1155,12 @@ def assign_costs_values(wt_opt, costs): def assign_airfoil_values(wt_opt, modeling_options, airfoils): # Function to assign values to the openmdao component Airfoils - n_af = modeling_options["RotorSE"]["n_af"] - n_aoa = modeling_options["RotorSE"]["n_aoa"] - aoa = modeling_options["RotorSE"]["aoa"] - n_Re = modeling_options["RotorSE"]["n_Re"] - n_tab = modeling_options["RotorSE"]["n_tab"] - n_xy = modeling_options["RotorSE"]["n_xy"] + n_af = modeling_options["WISDEM"]["RotorSE"]["n_af"] + n_aoa = modeling_options["WISDEM"]["RotorSE"]["n_aoa"] + aoa = modeling_options["WISDEM"]["RotorSE"]["aoa"] + n_Re = modeling_options["WISDEM"]["RotorSE"]["n_Re"] + n_tab = modeling_options["WISDEM"]["RotorSE"]["n_tab"] + n_xy = modeling_options["WISDEM"]["RotorSE"]["n_xy"] name = n_af * [""] ac = np.zeros(n_af) diff --git a/wisdem/glue_code/glue_code.py b/wisdem/glue_code/glue_code.py index d13fc7da0..08cdacff9 100644 --- a/wisdem/glue_code/glue_code.py +++ b/wisdem/glue_code/glue_code.py @@ -1,6 +1,7 @@ import numpy as np import openmdao.api as om from wisdem.towerse.tower import TowerSE +from wisdem.floatingse.floating import FloatingSE from wisdem.rotorse.rotor_power import RotorPower, NoStallConstraint from wisdem.glue_code.gc_RunTools import Outputs_2_Screen, Convergence_Trends_Opt from wisdem.commonse.turbine_class import TurbineClass @@ -58,14 +59,16 @@ def setup(self): ) if modeling_options["flags"]["nacelle"]: self.add_subsystem("drivese", DrivetrainSE(modeling_options=modeling_options, n_dlcs=1)) - if modeling_options["flags"]["tower"]: + if modeling_options["flags"]["tower"] and not modeling_options["flags"]["floating"]: self.add_subsystem("towerse", TowerSE(modeling_options=modeling_options)) + if modeling_options["flags"]["floating"]: + self.add_subsystem("floatingse", FloatingSE(modeling_options=modeling_options)) if modeling_options["flags"]["blade"] and modeling_options["flags"]["tower"]: self.add_subsystem("tcons", TurbineConstraints(modeling_options=modeling_options)) self.add_subsystem("tcc", Turbine_CostsSE_2015(verbosity=modeling_options["General"]["verbosity"])) if modeling_options["flags"]["blade"]: - n_span = modeling_options["RotorSE"]["n_span"] + n_span = modeling_options["WISDEM"]["RotorSE"]["n_span"] # Conncetions to ccblade self.connect("blade.pa.chord_param", "ccblade.chord") @@ -100,12 +103,11 @@ def setup(self): self.connect("configuration.ws_class", "wt_class.turbine_class") # Connections from blade aero parametrization to other modules - self.connect("blade.pa.twist_param", ["re.theta", "rs.theta"]) - # self.connect('blade.pa.twist_param', 'rs.tip_pos.theta_tip', src_indices=[-1]) + self.connect("ccblade.theta", ["re.theta", "rs.theta"]) self.connect("blade.pa.chord_param", "re.chord") self.connect("blade.pa.chord_param", ["rs.chord"]) if modeling_options["flags"]["blade"]: - self.connect("blade.pa.twist_param", "rp.theta") + self.connect("ccblade.theta", "rp.theta") self.connect("blade.pa.chord_param", "rp.chord") self.connect("configuration.n_blades", "rs.constr.blade_number") @@ -124,6 +126,9 @@ def setup(self): self.connect("blade.internal_structure_2d_fem.definition_layer", "re.precomp.definition_layer") self.connect("blade.internal_structure_2d_fem.web_start_nd", "re.precomp.web_start_nd") self.connect("blade.internal_structure_2d_fem.web_end_nd", "re.precomp.web_end_nd") + self.connect("blade.internal_structure_2d_fem.joint_position", "re.precomp.joint_position") + self.connect("blade.internal_structure_2d_fem.joint_mass", "re.precomp.joint_mass") + self.connect("blade.internal_structure_2d_fem.joint_cost", "re.precomp.joint_cost") self.connect("materials.name", "re.precomp.mat_name") self.connect("materials.orth", "re.precomp.orth") self.connect("materials.E", "re.precomp.E") @@ -153,7 +158,6 @@ def setup(self): # Connection from ra to rs for the rated conditions # self.connect('rp.powercurve.rated_V', 'rs.aero_rated.V_load') - self.connect("rp.powercurve.rated_V", "rp.gust.V_hub") self.connect("rp.gust.V_gust", ["rs.aero_gust.V_load", "rs.aero_hub_loads.V_load"]) self.connect("env.shear_exp", ["rp.powercurve.shearExp", "rs.aero_gust.shearExp"]) self.connect( @@ -177,8 +181,6 @@ def setup(self): self.connect("drivese.lss_rpm", "rp.powercurve.lss_rpm") self.connect("drivese.generator_efficiency", "rp.powercurve.generator_efficiency") self.connect("assembly.r_blade", "rp.r") - # self.connect('blade.pa.chord_param', 'rp.chord') - # self.connect('blade.pa.twist_param', 'rp.theta') self.connect("hub.radius", "rp.Rhub") self.connect("assembly.rotor_radius", "rp.Rtip") self.connect("assembly.hub_height", "rp.hub_height") @@ -309,7 +311,7 @@ def setup(self): self.connect("nacelle.mb2Type", "drivese.bear2.bearing_type") self.connect("nacelle.lss_diameter", "drivese.lss_diameter") self.connect("nacelle.lss_wall_thickness", "drivese.lss_wall_thickness") - if modeling_options["DriveSE"]["direct"]: + if modeling_options["WISDEM"]["DriveSE"]["direct"]: self.connect("nacelle.nose_diameter", "drivese.bear1.D_shaft", src_indices=[0]) self.connect("nacelle.nose_diameter", "drivese.bear2.D_shaft", src_indices=[-1]) else: @@ -321,7 +323,7 @@ def setup(self): self.connect("nacelle.converter_mass_user", "drivese.converter_mass_user") self.connect("nacelle.transformer_mass_user", "drivese.transformer_mass_user") - if modeling_options["DriveSE"]["direct"]: + if modeling_options["WISDEM"]["DriveSE"]["direct"]: self.connect("nacelle.nose_diameter", "drivese.nose_diameter") # only used in direct self.connect("nacelle.nose_wall_thickness", "drivese.nose_wall_thickness") # only used in direct self.connect( @@ -410,7 +412,7 @@ def setup(self): self.connect("generator.C_Fes", "drivese.generator.C_Fes") self.connect("generator.C_PM", "drivese.generator.C_PM") - if modeling_options["GeneratorSE"]["type"] in ["pmsg_outer"]: + if modeling_options["WISDEM"]["GeneratorSE"]["type"] in ["pmsg_outer"]: self.connect("generator.N_c", "drivese.generator.N_c") self.connect("generator.b", "drivese.generator.b") self.connect("generator.c", "drivese.generator.c") @@ -428,28 +430,29 @@ def setup(self): self.connect("generator.B_tmax", "drivese.generator.B_tmax") self.connect("rp.powercurve.rated_mech", "drivese.generator.P_mech") - if modeling_options["GeneratorSE"]["type"] in ["eesg", "pmsg_arms", "pmsg_disc"]: + if modeling_options["WISDEM"]["GeneratorSE"]["type"] in ["eesg", "pmsg_arms", "pmsg_disc"]: self.connect("generator.tau_p", "drivese.generator.tau_p") self.connect("generator.h_ys", "drivese.generator.h_ys") self.connect("generator.h_yr", "drivese.generator.h_yr") self.connect("generator.b_arm", "drivese.generator.b_arm") - elif modeling_options["GeneratorSE"]["type"] in ["scig", "dfig"]: + elif modeling_options["WISDEM"]["GeneratorSE"]["type"] in ["scig", "dfig"]: self.connect("generator.B_symax", "drivese.generator.B_symax") self.connect("generator.S_Nmax", "drivese.generator.S_Nmax") - if modeling_options["DriveSE"]["direct"]: + if modeling_options["WISDEM"]["DriveSE"]["direct"]: self.connect("nacelle.nose_diameter", "drivese.generator.D_nose", src_indices=[-1]) self.connect("nacelle.lss_diameter", "drivese.generator.D_shaft", src_indices=[0]) else: self.connect("nacelle.hss_diameter", "drivese.generator.D_shaft", src_indices=[-1]) else: + self.connect("generator.generator_radius_user", "drivese.generator_radius_user") self.connect("generator.generator_mass_user", "drivese.generator_mass_user") self.connect("generator.generator_efficiency_user", "drivese.generator_efficiency_user") # Connections to TowerSE - if modeling_options["flags"]["tower"]: + if modeling_options["flags"]["tower"] and not modeling_options["flags"]["floating"]: if modeling_options["flags"]["nacelle"]: self.connect("drivese.base_F", "towerse.pre.rna_F") self.connect("drivese.base_M", "towerse.pre.rna_M") @@ -458,12 +461,12 @@ def setup(self): self.connect("drivese.rna_mass", "towerse.rna_mass") if modeling_options["flags"]["blade"]: self.connect("rp.gust.V_gust", "towerse.wind.Uref") - self.connect("assembly.hub_height", "towerse.wind_reference_height") # TODO- environment + self.connect("assembly.hub_height", "towerse.wind_reference_height") + self.connect("assembly.hub_height", "towerse.hub_height") self.connect("env.rho_air", "towerse.rho_air") self.connect("env.mu_air", "towerse.mu_air") self.connect("env.shear_exp", "towerse.shearExp") - self.connect("assembly.hub_height", "towerse.hub_height") - self.connect("tower_grid.foundation_height", "towerse.tower_foundation_height") # TODO: towerse.wind_z0" + self.connect("tower_grid.foundation_height", "towerse.tower_foundation_height") self.connect("tower.diameter", "towerse.tower_outer_diameter_in") self.connect("tower_grid.height", "towerse.tower_height") self.connect("tower_grid.s", "towerse.tower_s") @@ -482,8 +485,9 @@ def setup(self): self.connect("env.water_depth", "towerse.water_depth") self.connect("env.rho_water", "towerse.rho_water") self.connect("env.mu_water", "towerse.mu_water") - self.connect("env.G_soil", "towerse.G_soil") - self.connect("env.nu_soil", "towerse.nu_soil") + if modeling_options["WISDEM"]["TowerSE"]["soil_springs"]: + self.connect("env.G_soil", "towerse.G_soil") + self.connect("env.nu_soil", "towerse.nu_soil") self.connect("env.Hsig_wave", "towerse.Hsig_wave") self.connect("env.Tsig_wave", "towerse.Tsig_wave") self.connect("monopile.diameter", "towerse.monopile_outer_diameter_in") @@ -497,6 +501,87 @@ def setup(self): self.connect("monopile.transition_piece_mass", "towerse.transition_piece_mass") self.connect("monopile.gravity_foundation_mass", "towerse.gravity_foundation_mass") + if modeling_options["flags"]["floating"]: + self.connect("env.rho_water", "floatingse.rho_water") + self.connect("env.water_depth", "floatingse.water_depth") + # self.connect("env.mu_water", "floatingse.mu_water") + # self.connect("env.Hsig_wave", "floatingse.Hsig_wave") + # self.connect("env.Tsig_wave", "floatingse.Tsig_wave") + # self.connect("env.rho_air", "floatingse.rho_air") + # self.connect("env.mu_air", "floatingse.mu_air") + # self.connect("env.shear_exp", "floatingse.shearExp") + self.connect("materials.name", "floatingse.material_names") + self.connect("materials.E", "floatingse.E_mat") + self.connect("materials.G", "floatingse.G_mat") + self.connect("materials.rho", "floatingse.rho_mat") + self.connect("materials.sigma_y", "floatingse.sigma_y_mat") + self.connect("materials.unit_cost", "floatingse.unit_cost_mat") + self.connect("costs.labor_rate", "floatingse.labor_cost_rate") + self.connect("costs.painting_rate", "floatingse.painting_cost_rate") + self.connect("assembly.hub_height", "floatingse.hub_height") + self.connect("nacelle.distance_tt_hub", "floatingse.distance_tt_hub") + self.connect("tower.diameter", "floatingse.tower.outer_diameter_in") + self.connect("tower_grid.s", "floatingse.tower.s") + self.connect("tower.layer_thickness", "floatingse.tower.layer_thickness") + self.connect("tower.outfitting_factor", "floatingse.tower.outfitting_factor_in") + self.connect("tower.layer_mat", "floatingse.tower.layer_materials") + if modeling_options["flags"]["nacelle"]: + self.connect("drivese.base_F", "floatingse.rna_F") + self.connect("drivese.base_M", "floatingse.rna_M") + self.connect("drivese.rna_I_TT", "floatingse.rna_I") + self.connect("drivese.rna_cm", "floatingse.rna_cg") + self.connect("drivese.rna_mass", "floatingse.rna_mass") + + # Individual member connections + for k, kname in enumerate(modeling_options["floating"]["members"]["name"]): + self.connect( + "floating.member_" + kname + ".outer_diameter", + "floatingse.member" + str(k) + ".outer_diameter_in", + ) + self.connect( + "floating.member_" + kname + ".outfitting_factor", + "floatingse.member" + str(k) + ".outfitting_factor_in", + ) + + for var in [ + "s", + "layer_thickness", + "layer_materials", + "bulkhead_grid", + "bulkhead_thickness", + "ballast_grid", + "ballast_volume", + "grid_axial_joints", + "ring_stiffener_web_height", + "ring_stiffener_web_thickness", + "ring_stiffener_flange_width", + "ring_stiffener_flange_thickness", + "axial_stiffener_web_height", + "axial_stiffener_web_thickness", + "axial_stiffener_flange_width", + "axial_stiffener_flange_thickness", + "axial_stiffener_spacing", + ]: + self.connect("floating.member_" + kname + "." + var, "floatingse.member" + str(k) + "." + var) + + for var in ["joint1", "joint2", "s_ghost1", "s_ghost2", "transition_flag"]: + self.connect("floating.member_" + kname + ":" + var, "floatingse.member" + str(k) + "." + var) + + # Mooring connections + self.connect("mooring.unstretched_length", "floatingse.line_length", src_indices=[0]) + for var in [ + "fairlead", + "fairlead_radius", + "anchor_radius", + "anchor_cost", + "line_diameter", + "line_mass_density_coeff", + "line_stiffness_coeff", + "line_breaking_load_coeff", + "line_cost_rate_coeff", + ]: + self.connect("mooring." + var, "floatingse." + var, src_indices=[0]) + # Connections to turbine constraints if modeling_options["flags"]["blade"] and modeling_options["flags"]["tower"]: self.connect("configuration.rotor_orientation", "tcons.rotor_orientation") @@ -508,7 +593,10 @@ def setup(self): self.connect("nacelle.overhang", "tcons.overhang") self.connect("assembly.tower_ref_axis", "tcons.ref_axis_tower") self.connect("tower.diameter", "tcons.d_full") - self.connect("towerse.tower.freqs", "tcons.tower_freq", src_indices=[0]) + if modeling_options["flags"]["floating"]: + self.connect("floatingse.tower_freqs", "tcons.tower_freq", src_indices=[0]) + else: + self.connect("towerse.tower.freqs", "tcons.tower_freq", src_indices=[0]) self.connect("configuration.n_blades", "tcons.blade_number") self.connect("rp.powercurve.rated_Omega", "tcons.rated_Omega") @@ -540,9 +628,12 @@ def setup(self): if modeling_options["flags"]["generator"]: self.connect("drivese.generator_cost", "tcc.generator_cost_external") - if modeling_options["flags"]["tower"]: + if modeling_options["flags"]["tower"] and not modeling_options["flags"]["floating"]: self.connect("towerse.structural_mass", "tcc.tower_mass") self.connect("towerse.structural_cost", "tcc.tower_cost_external") + elif modeling_options["flags"]["floating"]: + self.connect("floatingse.tower_mass", "tcc.tower_mass") + self.connect("floatingse.tower_cost", "tcc.tower_cost_external") self.connect("costs.blade_mass_cost_coeff", "tcc.blade_mass_cost_coeff") self.connect("costs.hub_mass_cost_coeff", "tcc.hub_mass_cost_coeff") @@ -578,7 +669,7 @@ def setup(self): opt_options = self.options["opt_options"] self.add_subsystem("wt", WT_RNTA(modeling_options=modeling_options, opt_options=opt_options), promotes=["*"]) - if modeling_options["BOS"]["flag"]: + if modeling_options["WISDEM"]["BOS"]["flag"]: if modeling_options["flags"]["offshore"]: self.add_subsystem("orbit", Orbit(floating=modeling_options["flags"]["floating_platform"])) else: @@ -594,7 +685,7 @@ def setup(self): self.add_subsystem("conv_plots", Convergence_Trends_Opt(opt_options=opt_options)) # BOS inputs - if modeling_options["BOS"]["flag"]: + if modeling_options["WISDEM"]["BOS"]["flag"]: if modeling_options["flags"]["offshore"]: # Inputs into ORBIT self.connect("configuration.rated_power", "orbit.turbine_rating") @@ -603,18 +694,22 @@ def setup(self): self.connect("configuration.n_blades", "orbit.number_of_blades") self.connect("assembly.hub_height", "orbit.hub_height") self.connect("assembly.rotor_diameter", "orbit.turbine_rotor_diameter") - self.connect("towerse.tower_mass", "orbit.tower_mass") + self.connect("tower_grid.height", "orbit.tower_length") if modeling_options["flags"]["monopile"]: + self.connect("towerse.tower_mass", "orbit.tower_mass") self.connect("towerse.monopile_mass", "orbit.monopile_mass") - self.connect("towerse.monopile_length", "orbit.monopile_length") + self.connect("towerse.monopile_cost", "orbit.monopile_cost") + self.connect("monopile.height", "orbit.monopile_length") self.connect("monopile.transition_piece_mass", "orbit.transition_piece_mass") + self.connect("monopile.transition_piece_cost", "orbit.transition_piece_cost") self.connect("monopile.diameter", "orbit.monopile_diameter", src_indices=[0]) else: + self.connect("floatingse.tower_mass", "orbit.tower_mass") self.connect("mooring.n_lines", "orbit.num_mooring_lines") - self.connect("mooring.line_mass", "orbit.mooring_line_mass", src_indices=[0]) + self.connect("floatingse.line_mass", "orbit.mooring_line_mass", src_indices=[0]) self.connect("mooring.line_diameter", "orbit.mooring_line_diameter", src_indices=[0]) self.connect("mooring.unstretched_length", "orbit.mooring_line_length", src_indices=[0]) - self.connect("mooring.anchor_mass", "orbit.anchor_mass") + self.connect("mooring.anchor_mass", "orbit.anchor_mass", src_indices=[0]) self.connect("re.precomp.blade_mass", "orbit.blade_mass") self.connect("tcc.turbine_cost_kW", "orbit.turbine_capex") if modeling_options["flags"]["nacelle"]: diff --git a/wisdem/glue_code/runWISDEM.py b/wisdem/glue_code/runWISDEM.py index 52013bd12..d350d0119 100644 --- a/wisdem/glue_code/runWISDEM.py +++ b/wisdem/glue_code/runWISDEM.py @@ -87,7 +87,7 @@ def run_wisdem(fname_wt_input, fname_modeling_options, fname_opt_options, overri # needing to modify the yaml files. if overridden_values is not None: for key in overridden_values: - wt_opt[key][:] = overridden_values[key] + wt_opt[key] = overridden_values[key] # Place the last design variables from a previous run into the problem. # This needs to occur after the above setup() and yaml2openmdao() calls diff --git a/wisdem/inputs/analysis_schema.yaml b/wisdem/inputs/analysis_schema.yaml index 646997818..51434330d 100644 --- a/wisdem/inputs/analysis_schema.yaml +++ b/wisdem/inputs/analysis_schema.yaml @@ -137,6 +137,10 @@ properties: default: {} properties: flag: *flag + equal_to_suction: + type: boolean + default: true + description: If the pressure side spar cap should be equal to the suction side spar cap n_opt: type: integer default: 8 @@ -456,7 +460,6 @@ properties: upper_bound: *sbound monopile: *towdv constraints: - # GB: These all need gammas or safety factors type: object default: {} description: Activate the constraints that are applied to a design optimization @@ -501,6 +504,7 @@ properties: default: {} description: Enforce sufficient blade flexibility such that they can be transported on rail cars without exceeding maximum blade strains or derailment. User can activate either 8-axle flatcars or 4-axle properties: + flag: *flag 8_axle: *flag 4_axle: *flag stall: @@ -732,53 +736,146 @@ properties: description: For direct-drive configurations only, ensure that the elliptical bedplate length is greater than its height properties: flag: *flag + floating: + type: object + default: {} + properties: + operational_heel: &heelang + type: object + default: {} + description: Ensure that the mooring system has enough restoring force to keep the heel/pitch angle below this limit + properties: + flag: *flag + upper_bound: &upang + type: number + default: 0.17453292519943295 # 10 deg + minimum: 0.017453292519943295 # 1 deg + maximum: 0.7853981633974483 # 45 deg + unit: rad + survival_heel: *heelang + max_surge: + type: object + default: {} + description: Ensure that the mooring system has enough restoring force so that this surge distance, expressed as a fraction of water depth, is not exceeded + properties: + flag: *flag + upper_bound: + type: number + default: 0.1 + minimum: 0.01 + maximum: 1.0 + unit: none + + mooring_tension: + type: object + default: {} + description: Keep the mooring line tension below its breaking point + properties: + flag: *flag + + mooring_length: + type: object + default: {} + description: Keep the mooring line length within the bounds for catenary hang or TLP tension + properties: + flag: *flag + merit_figure: type: string description: Objective function / merit figure for optimization. Choices are LCOE- levelized cost of energy, AEP- turbine annual energy production, Cp- rotor power coefficient, blade_mass, tower_mass, tower_cost, monopile_mass, monopile_cost, structural_mass- tower+monopile mass, structural_cost- tower+monopile cost, blade_tip_deflection- blade tip deflection distance towards tower, My_std- blade flap moment standard deviation, flp1_std- trailing flap standard deviation default: LCOE - enum: [LCOE, AEP, Cp, blade_mass, tower_mass, tower_cost, monopile_mass, monopile_cost, structural_mass, structural_cost, blade_tip_deflection, My_std, flp1_std] + enum: [LCOE, AEP, Cp, blade_mass, nacelle_mass, nacelle_cost, tower_mass, tower_cost, monopile_mass, monopile_cost, structural_mass, structural_cost, blade_tip_deflection, My_std, flp1_std] driver: type: object - description: Specification of the optimization driver (optimization algorithm) parameters default: {} properties: - tol: - type: number - description: Convergence tolerance (relative) - default: 1e-6 - minimum: 1e-12 - maximum: 1.0 - unit: none - max_iter: - type: integer - description: Max number of optimization iterations - default: 100 - minimum: 0 - maximum: 100000 - max_function_calls: - type: integer - description: Max number of calls to objective function evaluation - default: 100000 - minimum: 0 - maximum: 100000000 - solver: - type: string - description: Optimization driver. Can be one of [SLSQP, CONMIN, COBYLA, SNOPT] - default: SLSQP - enum: [SLSQP, CONMIN, COBYLA, SNOPT] - step_size: - type: number - description: Maximum step size - default: 1e-3 - minimum: 1e-10 - maximum: 100.0 - form: - type: string - description: Finite difference calculation mode - default: central - enum: [central, forward, complex] + optimization: + type: object + description: Specification of the optimization driver (optimization algorithm) parameters + default: {} + properties: + flag: *flag + tol: + type: number + description: Convergence tolerance (relative) + default: 1e-6 + minimum: 1e-12 + maximum: 1.0 + unit: none + max_iter: + type: integer + description: Max number of optimization iterations + default: 100 + minimum: 0 + maximum: 100000 + max_function_calls: + type: integer + description: Max number of calls to objective function evaluation + default: 100000 + minimum: 0 + maximum: 100000000 + solver: + type: string + description: Optimization driver. Can be one of [SLSQP, CONMIN, COBYLA, SNOPT] + default: SLSQP + enum: [SLSQP, CONMIN, COBYLA, SNOPT] + step_size: + type: number + description: Maximum step size + default: 1e-3 + minimum: 1e-10 + maximum: 100.0 + form: + type: string + description: Finite difference calculation mode + default: central + enum: [central, forward, complex] + design_of_experiments: + type: object + description: Specification of the design of experiments driver parameters + default: {} + properties: + flag: *flag + run_parallel: + type: boolean + default: True + description: Toggle parallel model runs + generator: + type: string + description: Type of model input generator. + default: Uniform + enum: [Uniform, FullFact, PlackettBurman, BoxBehnken, LatinHypercube] + num_samples: + type: integer + description: Number of samples to evaluate model at (Uniform and LatinHypercube only) + default: 5 + minimum: 1 + maximum: 1000000 + seed: + type: integer + description: Random seed to use if design is randomized + default: 2 + minimum: 1 + maximum: 1000000 + levels: + type: integer + description: Number of evenly spaced levels between each design variable lower and upper bound (FullFactorial only) + default: 2 + minimum: 1 + maximum: 1000000 + criterion: + type: string + description: Descriptor of sampling method for LatinHypercube generator + default: None + enum: [None, center, c, maximin, m, centermaximin, cm, correelation, corr] + iterations: + type: integer + description: Number of iterations in maximin and correlations algorithms (LatinHypercube only) + default: 2 + minimum: 1 + maximum: 1000000 recorder: type: object diff --git a/wisdem/inputs/geometry_schema.yaml b/wisdem/inputs/geometry_schema.yaml index 2be97c1c8..17c98671d 100644 --- a/wisdem/inputs/geometry_schema.yaml +++ b/wisdem/inputs/geometry_schema.yaml @@ -82,6 +82,7 @@ properties: minimum: 0 components: type: object + default: {} optional: - blade - hub @@ -172,6 +173,7 @@ properties: internal_structure_2d_fem: type: object + default: {} required: - reference_axis - layers @@ -270,6 +272,32 @@ properties: $ref: "#/definitions/distributed_data/rotation" offset_y_pa: $ref: "#/definitions/distributed_data/offset" + joint: + type: object + default: {} + description: This is a spanwise joint along the blade, usually adopted to ease transportation constraints. WISDEM currently supports a single joint. + properties: + position: + type: number + description: Spanwise position of the segmentation joint. + unit: none + default: 0.0 + minimum: 0.0 + maximum: 1.0 + mass: + type: number + description: Mass of the joint. + unit: kg + default: 0.0 + minimum: 0.0 + maximum: 1.e+6 + cost: + type: number + description: Cost of the joint. + unit: USD + default: 0.0 + minimum: 0.0 + maximum: 1.e+6 # cfd_geometry: # type: object # TODO @@ -382,6 +410,13 @@ properties: minimum: 0. maximum: 20. default: 2.0 + generator_radius_user: + type: number + description: User input override of generator radius, only used when using simple generator scaling + unit: m + minimum: 0. + maximum: 20.0 + default: 0.0 generator_mass_user: type: number description: User input override of generator mass, only used when using simple generator mass scaling @@ -531,7 +566,7 @@ properties: minimum: 0.0 hvac_mass_coefficient: type: number - default: 0.08 + default: 0.025 units: kg/kW description: Regression-based scaling coefficient on machine rating to get HVAC system mass minimum: 0.0 diff --git a/wisdem/inputs/modeling_schema.yaml b/wisdem/inputs/modeling_schema.yaml index 33335c965..e6b1af287 100644 --- a/wisdem/inputs/modeling_schema.yaml +++ b/wisdem/inputs/modeling_schema.yaml @@ -18,306 +18,295 @@ properties: type: boolean default: False description: Prints additional outputs to screen (and to a file log in the future) - RotorSE: + WISDEM: type: object default: {} properties: - flag: - type: boolean - default: False - description: Whether or not to run RotorSE and ServoSE - n_aoa: - type: integer - default: 200 - description: Number of angles of attack in a common grid to define polars - n_xy: - type: integer - default: 200 - description: Number of coordinate point used to define airfoils #GB: This should depend on the airfoil. We are not resampling if the airfoils come inwith different numbers of points. PreComp doesn't care if different airfoils have different numbers of points, right? - n_span: - type: integer - default: 30 - description: Number of spanwise stations in a common grid used to define blade properties - n_pc: - type: integer - default: 20 - description: Number of wind speeds to compute the power curve - n_pc_spline: - type: integer - default: 200 - description: Number of wind speeds to spline the power curve - n_pitch_perf_surfaces: - type: integer - default: 20 - description: Number of pitch angles to determine the Cp-Ct-Cq-surfaces - min_pitch_perf_surfaces: - type: number - default: -5. - description: Min pitch angle of the Cp-Ct-Cq-surfaces - max_pitch_perf_surfaces: - type: number - default: 30. - description: Max pitch angle of the Cp-Ct-Cq-surfaces - n_tsr_perf_surfaces: - type: integer - default: 20 - description: Number of tsr values to determine the Cp-Ct-Cq-surfaces - min_tsr_perf_surfaces: - type: number - default: 2. - description: Min TSR of the Cp-Ct-Cq-surfaces - max_tsr_perf_surfaces: - type: number - default: 12. - description: Max TSR of the Cp-Ct-Cq-surfaces - n_U_perf_surfaces: - type: integer - default: 1 - description: Number of wind speeds to determine the Cp-Ct-Cq-surfaces - regulation_reg_III: - type: boolean - default: False - description: Flag to derive the regulation trajectory in region III in terms of pitch and TSR - spar_cap_ss: - type: string - default: 'none' - description: Composite layer modeling the spar cap on the suction side in the geometry yaml. This entry is used to compute ultimate strains and it is linked to the design variable spar_cap_ss. - spar_cap_ps: - type: string - default: 'none' - description: Composite layer modeling the spar cap on the pressure side in the geometry yaml. This entry is used to compute ultimate strains and it is linked to the design variable spar_cap_ps. - te_ss: - type: string - default: 'none' - description: Composite layer modeling the trailing edge reinforcement on the suction side in the geometry yaml. This entry is used to compute ultimate strains and it is linked to the design variable te_ss. - te_ps: - type: string - default: 'none' - description: Composite layer modeling the trailing edge reinforcement on the pressure side in the geometry yaml. This entry is used to compute ultimate strains and it is linked to the design variable te_ps. - gamma_freq: - type: number - description: Partial safety factor on modal frequencies - minimum: 1.0 - maximum: 5.0 - default: 1.1 - unit: none - DriveSE: - type: object - default: {} - properties: - flag: - type: boolean - default: False - description: Whether or not to run RotorSE and ServoSE - model_generator: - type: boolean - default: False - description: Whether or not to do detailed generator modeling using tools formerly in GeneratorSE - gamma_f: - type: number - description: Partial safety factor on loads - minimum: 1.0 - maximum: 5.0 - default: 1.35 - unit: none - gamma_m: - type: number - description: Partial safety factor for materials - minimum: 1.0 - maximum: 5.0 - default: 1.3 - unit: none - gamma_n: - type: number - description: Partial safety factor for consequence of failure - minimum: 1.0 - maximum: 5.0 - default: 1.0 - unit: none - hub: + RotorSE: + type: object + default: {} + properties: + flag: + type: boolean + default: False + description: Whether or not to run RotorSE and ServoSE + n_aoa: + type: integer + default: 200 + description: Number of angles of attack in a common grid to define polars + n_xy: + type: integer + default: 200 + description: Number of coordinate point used to define airfoils #GB: This should depend on the airfoil. We are not resampling if the airfoils come inwith different numbers of points. PreComp doesn't care if different airfoils have different numbers of points, right? + n_span: + type: integer + default: 30 + description: Number of spanwise stations in a common grid used to define blade properties + n_pc: + type: integer + default: 20 + description: Number of wind speeds to compute the power curve + n_pc_spline: + type: integer + default: 200 + description: Number of wind speeds to spline the power curve + n_pitch_perf_surfaces: + type: integer + default: 20 + description: Number of pitch angles to determine the Cp-Ct-Cq-surfaces + min_pitch_perf_surfaces: + type: number + default: -5. + description: Min pitch angle of the Cp-Ct-Cq-surfaces + max_pitch_perf_surfaces: + type: number + default: 30. + description: Max pitch angle of the Cp-Ct-Cq-surfaces + n_tsr_perf_surfaces: + type: integer + default: 20 + description: Number of tsr values to determine the Cp-Ct-Cq-surfaces + min_tsr_perf_surfaces: + type: number + default: 2. + description: Min TSR of the Cp-Ct-Cq-surfaces + max_tsr_perf_surfaces: + type: number + default: 12. + description: Max TSR of the Cp-Ct-Cq-surfaces + n_U_perf_surfaces: + type: integer + default: 1 + description: Number of wind speeds to determine the Cp-Ct-Cq-surfaces + regulation_reg_III: + type: boolean + default: True + description: Flag to derive the regulation trajectory in region III in terms of pitch and TSR + spar_cap_ss: + type: string + default: 'none' + description: Composite layer modeling the spar cap on the suction side in the geometry yaml. This entry is used to compute ultimate strains and it is linked to the design variable spar_cap_ss. + spar_cap_ps: + type: string + default: 'none' + description: Composite layer modeling the spar cap on the pressure side in the geometry yaml. This entry is used to compute ultimate strains and it is linked to the design variable spar_cap_ps. + te_ss: + type: string + default: 'none' + description: Composite layer modeling the trailing edge reinforcement on the suction side in the geometry yaml. This entry is used to compute ultimate strains and it is linked to the design variable te_ss. + te_ps: + type: string + default: 'none' + description: Composite layer modeling the trailing edge reinforcement on the pressure side in the geometry yaml. This entry is used to compute ultimate strains and it is linked to the design variable te_ps. + gamma_freq: &gamma_freq + type: number + description: Partial safety factor on modal frequencies + minimum: 1.0 + maximum: 5.0 + default: 1.1 + unit: none + DriveSE: type: object default: {} properties: - hub_gamma: + flag: + type: boolean + default: False + description: Whether or not to run RotorSE and ServoSE + model_generator: + type: boolean + default: False + description: Whether or not to do detailed generator modeling using tools formerly in GeneratorSE + gamma_f: &gamma_f type: number - description: Partial safety factor for hub sizing + description: Partial safety factor on loads minimum: 1.0 maximum: 5.0 - default: 2.0 + default: 1.35 unit: none - spinner_gamma: + gamma_m: &gamma_m type: number - description: Partial safety factor for spinner sizing + description: Partial safety factor for materials minimum: 1.0 maximum: 5.0 - default: 1.5 + default: 1.3 unit: none - TowerSE: - type: object - default: {} - properties: - flag: - type: boolean - default: False - description: Whether or not to run RotorSE and ServoSE - nLC: - type: integer - default: 1 - description: Number of load cases - wind: - type: string - enum: [PowerWind, LogisticWind] - default: PowerWind - description: Wind scaling relationship with height - gamma_f: - type: number - description: Partial safety factor on loads - minimum: 1.0 - maximum: 5.0 - default: 1.35 - unit: none - gamma_m: - type: number - description: Partial safety factor for materials - minimum: 1.0 - maximum: 5.0 - default: 1.3 - unit: none - gamma_n: - type: number - description: Partial safety factor for consequence of failure - minimum: 1.0 - maximum: 5.0 - default: 1.0 - unit: none - gamma_b: - type: number - description: Partial safety factor for buckling - minimum: 1.0 - maximum: 5.0 - default: 1.1 - unit: none - gamma_freq: - type: number - description: Partial safety factor on modal frequencies - minimum: 1.0 - maximum: 5.0 - default: 1.1 - unit: none - gamma_fatigue: - type: number - description: Partial safety factor for fatigue failure - minimum: 1.0 - maximum: 5.0 - default: 1.0 - unit: none - buckling_length: - type: number - description: Buckling length factor in Eurocode safety check - minimum: 1.0 - maximum: 100.0 - default: 1.0 - unit: m - frame3dd: + gamma_n: &gamma_n + type: number + description: Partial safety factor for consequence of failure + minimum: 1.0 + maximum: 5.0 + default: 1.0 + unit: none + hub: + type: object + default: {} + properties: + hub_gamma: + type: number + description: Partial safety factor for hub sizing + minimum: 1.0 + maximum: 5.0 + default: 2.0 + unit: none + spinner_gamma: + type: number + description: Partial safety factor for spinner sizing + minimum: 1.0 + maximum: 5.0 + default: 1.5 + unit: none + TowerSE: type: object - description: Set of Frame3DD options used for tower analysis default: {} properties: - shear: - type: boolean - default: True - description: Inclusion of shear area for symmetric sections - geom: + flag: type: boolean - default: True - description: Inclusion of shear stiffening through axial loading - nM: + default: False + description: Whether or not to run RotorSE and ServoSE + nLC: type: integer - minimum: 0 - maximum: 20 - default: 6 - description: Number of tower eigenvalue modes to calculate - tol: + default: 1 + description: Number of load cases + wind: + type: string + enum: [PowerWind, LogisticWind] + default: PowerWind + description: Wind scaling relationship with height + gamma_f: *gamma_f + gamma_m: *gamma_m + gamma_n: *gamma_n + gamma_b: &gamma_b type: number - minimum: 1e-12 - maximum: 1e-1 - default: 1e-9 - description: Convergence tolerance for modal eigenvalue solution - BOS: - type: object - default: {} - properties: - flag: - type: boolean - default: False - description: Whether or not to run balance of station cost models (LandBOSSE or ORBIT) - FloatingSE: - type: object - default: {} - properties: - flag: - type: boolean - default: False - description: Whether or not to run the floating design modules (FloatingSE) - Loading: - type: object - # DO NOT PUT IN A DEFAULT HERE! This is a field we only want present if the user specifically includes it. - description: This is only used if not running the full WISDEM turbine Group and you need to input the mass properties, forces, and moments for a tower-only or nacelle-only analysis - properties: - mass: - type: number - default: 0.0 - units: kilogram - description: Mass at external boundary of the system. For the tower, this would be the RNA mass. - center_of_mass: - type: array - default: [0.0, 0.0, 0.0] - items: - type: number - unit: meter - minItems: 3 - maxItems: 3 - uniqueItems: false - description: Distance from system boundary to center of mass of the applied load. For the tower, this would be the RNA center of mass in tower-top coordinates. - moment_of_inertia: - type: array - default: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - items: - type: number - unit: kg*m^2 - minItems: 6 - maxItems: 6 - uniqueItems: false - description: Moment of inertia of external mass in coordinate system at the system boundary. For the tower, this would be the RNA MoI in tower-top coordinates. - loads: - type: array - default: {} - description: The loading scenarios associated with the applied mass. For the tower, this would be operating, parked, etc. - items: - type: object - properties: - force: - type: array - default: [0.0, 0.0, 0.0] - description: Force vector applied at system boundary - items: - type: number - unit: Newton - minItems: 3 - maxItems: 3 - uniqueItems: false - moment: - type: array - default: [0.0, 0.0, 0.0] - description: Force vector applied at system boundary - items: + description: Partial safety factor for buckling + minimum: 1.0 + maximum: 5.0 + default: 1.1 + unit: none + gamma_freq: *gamma_freq + gamma_fatigue: &gamma_fatigue + type: number + description: Partial safety factor for fatigue failure + minimum: 1.0 + maximum: 5.0 + default: 1.0 + unit: none + buckling_length: + type: number + description: Buckling length factor in Eurocode safety check + minimum: 1.0 + maximum: 100.0 + default: 10.0 + unit: m + frame3dd: &frame3dd + type: object + description: Set of Frame3DD options used for tower analysis + default: {} + properties: + shear: + type: boolean + default: True + description: Inclusion of shear area for symmetric sections + geom: + type: boolean + default: True + description: Inclusion of shear stiffening through axial loading + modal: + type: boolean + default: True + description: Whether or not to compute eigenvalues and natural frequencies + tol: type: number - unit: N*m - minItems: 3 - maxItems: 3 - uniqueItems: false - velocity: + minimum: 1e-12 + maximum: 1e-1 + default: 1e-9 + description: Convergence tolerance for modal eigenvalue solution + soil_springs: + type: boolean + default: False + description: If False, then a monopile is modeled with a perfectly clamped foundation. If True, then spring-stiffness equivalents are computed from soil properties for all DOF. + gravity_foundation: + type: boolean + default: False + description: Model the monopile base as a gravity-based foundation with no pile embedment + BOS: + type: object + default: {} + properties: + flag: + type: boolean + default: False + description: Whether or not to run balance of station cost models (LandBOSSE or ORBIT) + FloatingSE: + type: object + default: {} + properties: + flag: + type: boolean + default: False + description: Whether or not to run the floating design modules (FloatingSE) + frame3dd: *frame3dd + gamma_f: *gamma_f + + Loading: + type: object + # DO NOT PUT IN A DEFAULT HERE! This is a field we only want present if the user specifically includes it. + description: This is only used if not running the full WISDEM turbine Group and you need to input the mass properties, forces, and moments for a tower-only or nacelle-only analysis + properties: + mass: + type: number + default: 0.0 + units: kilogram + description: Mass at external boundary of the system. For the tower, this would be the RNA mass. + center_of_mass: + type: array + default: [0.0, 0.0, 0.0] + items: type: number - description: Applied wind reference velocity, if necessary - default: 0.0 unit: meter + minItems: 3 + maxItems: 3 + uniqueItems: false + description: Distance from system boundary to center of mass of the applied load. For the tower, this would be the RNA center of mass in tower-top coordinates. + moment_of_inertia: + type: array + default: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + items: + type: number + unit: kg*m^2 + minItems: 6 + maxItems: 6 + uniqueItems: false + description: Moment of inertia of external mass in coordinate system at the system boundary. For the tower, this would be the RNA MoI in tower-top coordinates. + loads: + type: array + default: {} + description: The loading scenarios associated with the applied mass. For the tower, this would be operating, parked, etc. + items: + type: object + properties: + force: + type: array + default: [0.0, 0.0, 0.0] + description: Force vector applied at system boundary + items: + type: number + unit: Newton + minItems: 3 + maxItems: 3 + uniqueItems: false + moment: + type: array + default: [0.0, 0.0, 0.0] + description: Force vector applied at system boundary + items: + type: number + unit: N*m + minItems: 3 + maxItems: 3 + uniqueItems: false + velocity: + type: number + description: Applied wind reference velocity, if necessary + default: 0.0 + unit: meter diff --git a/wisdem/landbosse/landbosse_omdao/OpenMDAODataframeCache.py b/wisdem/landbosse/landbosse_omdao/OpenMDAODataframeCache.py index e8450822b..3ca20eaed 100644 --- a/wisdem/landbosse/landbosse_omdao/OpenMDAODataframeCache.py +++ b/wisdem/landbosse/landbosse_omdao/OpenMDAODataframeCache.py @@ -1,5 +1,4 @@ import os - import warnings with warnings.catch_warnings(): @@ -9,7 +8,10 @@ # The library path is where to find the default input data for LandBOSSE. ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..")) -library_path = os.path.join(ROOT, "library", "landbosse") +if ROOT.endswith("wisdem"): + library_path = os.path.join(ROOT, "library", "landbosse") +else: + library_path = os.path.join(ROOT, "project_input_template", "project_data") class OpenMDAODataframeCache: @@ -79,10 +81,10 @@ def read_all_sheets_from_xlsx(cls, xlsx_basename, xlsx_path=None): else: xlsx_filename = os.path.join(xlsx_path, f"{xlsx_basename}.xlsx") - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", category=PendingDeprecationWarning) - xlsx = pd.ExcelFile(xlsx_filename) + xlsx = pd.ExcelFile(xlsx_filename, engine="openpyxl") sheets_dict = {sheet_name: xlsx.parse(sheet_name) for sheet_name in xlsx.sheet_names} + for sheet_name in xlsx.sheet_names: + sheets_dict[sheet_name].dropna(inplace=True, how="all") cls._cache[xlsx_basename] = sheets_dict return cls.copy_dataframes(sheets_dict) diff --git a/wisdem/landbosse/landbosse_omdao/XlsxValidator.py b/wisdem/landbosse/landbosse_omdao/XlsxValidator.py index e2360ea4c..00878a87f 100644 --- a/wisdem/landbosse/landbosse_omdao/XlsxValidator.py +++ b/wisdem/landbosse/landbosse_omdao/XlsxValidator.py @@ -37,7 +37,8 @@ def compare_expected_to_actual(self, expected_xlsx, actual_module_type_operation # the raw_cost and raw_cost_total_or_per_turbine columns. actual_df = pd.DataFrame(actual_module_type_operation_list) actual_df.drop(["raw_cost", "raw_cost_total_or_per_turbine"], axis=1, inplace=True) - expected_df = pd.read_excel(expected_xlsx, "costs_by_module_type_operation") + expected_df = pd.read_excel(expected_xlsx, "costs_by_module_type_operation", engine="openpyxl") + # expected_df = expected_df.dropna(inplace=True, how='all') expected_df.rename( columns={ "Project ID with serial": "project_id_with_serial", diff --git a/wisdem/landbosse/landbosse_omdao/landbosse.py b/wisdem/landbosse/landbosse_omdao/landbosse.py index fdde535cb..3b164395c 100644 --- a/wisdem/landbosse/landbosse_omdao/landbosse.py +++ b/wisdem/landbosse/landbosse_omdao/landbosse.py @@ -1,17 +1,18 @@ -import openmdao.api as om -from math import pi, ceil -import numpy as np import warnings +from math import ceil -with warnings.catch_warnings(): - warnings.filterwarnings("ignore", message="numpy.ufunc size changed") - import pandas as pd - +import numpy as np +import openmdao.api as om from wisdem.landbosse.model.Manager import Manager from wisdem.landbosse.model.DefaultMasterInputDict import DefaultMasterInputDict from wisdem.landbosse.landbosse_omdao.OpenMDAODataframeCache import OpenMDAODataframeCache from wisdem.landbosse.landbosse_omdao.WeatherWindowCSVReader import read_weather_window +with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message="numpy.ufunc size changed") + import pandas as pd + + use_default_component_data = -1.0 @@ -445,6 +446,9 @@ def prepare_master_input_dictionary(self, inputs, discrete_inputs): discrete_inputs["num_turbines"] * inputs["turbine_rating_MW"] ) + # Needed to avoid distributed wind keys + incomplete_input_dict["road_distributed_wind"] = False + defaults = DefaultMasterInputDict() master_input_dict = defaults.populate_input_dict(incomplete_input_dict) @@ -764,7 +768,7 @@ def make_tower_sections(tower_mass_tonnes, tower_height_m, default_tower_section tower_section_mass = tower_mass_tonnes / number_of_sections - tower_section_surface_area_m2 = pi * tower_section_height_m * (tower_radius ** 2) + tower_section_surface_area_m2 = np.pi * tower_section_height_m * (tower_radius ** 2) sections = [] for i in range(number_of_sections): diff --git a/wisdem/landbosse/model/CollectionCost.py b/wisdem/landbosse/model/CollectionCost.py index a8cae16b7..8e0ed3823 100644 --- a/wisdem/landbosse/model/CollectionCost.py +++ b/wisdem/landbosse/model/CollectionCost.py @@ -13,10 +13,10 @@ """ import math -import numpy as np import traceback -import pandas as pd +import numpy as np +import pandas as pd from wisdem.landbosse.model.CostModule import CostModule from wisdem.landbosse.model.WeatherDelay import WeatherDelay as WD diff --git a/wisdem/landbosse/model/DevelopmentCost.py b/wisdem/landbosse/model/DevelopmentCost.py index ba84dd707..acd048e3b 100644 --- a/wisdem/landbosse/model/DevelopmentCost.py +++ b/wisdem/landbosse/model/DevelopmentCost.py @@ -1,7 +1,8 @@ +import math import traceback -from wisdem.landbosse.model.CostModule import CostModule + import pandas as pd -import math +from wisdem.landbosse.model.CostModule import CostModule class DevelopmentCost(CostModule): diff --git a/wisdem/landbosse/model/ErectionCost.py b/wisdem/landbosse/model/ErectionCost.py index a74bce8e1..ccd5c1efc 100644 --- a/wisdem/landbosse/model/ErectionCost.py +++ b/wisdem/landbosse/model/ErectionCost.py @@ -1,12 +1,11 @@ -import pandas as pd -import numpy as np +import traceback from math import ceil +import numpy as np +import pandas as pd from wisdem.landbosse.model.CostModule import CostModule from wisdem.landbosse.model.WeatherDelay import WeatherDelay -import traceback - # constants km_per_m = 0.001 hr_per_min = 1 / 60 @@ -15,7 +14,7 @@ class Point(object): def __init__(self, x, y): - if type(x) == type(pd.Series(dtype=np.float64)): + if type(x) == type(pd.Series()): self.x = float(x.values[0]) self.y = float(y.values[0]) elif type(x) == type(np.array([])): diff --git a/wisdem/landbosse/model/FoundationCost.py b/wisdem/landbosse/model/FoundationCost.py index a5b2ac7ea..2583ea4a4 100644 --- a/wisdem/landbosse/model/FoundationCost.py +++ b/wisdem/landbosse/model/FoundationCost.py @@ -1,13 +1,11 @@ -import traceback import math +import traceback -import pandas as pd import numpy as np -import math +import pandas as pd from scipy.optimize import root_scalar - -from wisdem.landbosse.model.WeatherDelay import WeatherDelay as WD from wisdem.landbosse.model.CostModule import CostModule +from wisdem.landbosse.model.WeatherDelay import WeatherDelay as WD class FoundationCost(CostModule): diff --git a/wisdem/landbosse/model/GridConnectionCost.py b/wisdem/landbosse/model/GridConnectionCost.py index 72cdbe1b3..65579efeb 100644 --- a/wisdem/landbosse/model/GridConnectionCost.py +++ b/wisdem/landbosse/model/GridConnectionCost.py @@ -1,8 +1,7 @@ -import traceback -import pandas as pd import math +import traceback - +import pandas as pd from wisdem.landbosse.model.CostModule import CostModule diff --git a/wisdem/landbosse/model/ManagementCost.py b/wisdem/landbosse/model/ManagementCost.py index 075eaa232..6a6ae364d 100644 --- a/wisdem/landbosse/model/ManagementCost.py +++ b/wisdem/landbosse/model/ManagementCost.py @@ -1,7 +1,7 @@ import math import traceback + import pytest -import traceback class ManagementCost: diff --git a/wisdem/landbosse/model/Manager.py b/wisdem/landbosse/model/Manager.py index 525a59bce..ac0d902df 100644 --- a/wisdem/landbosse/model/Manager.py +++ b/wisdem/landbosse/model/Manager.py @@ -1,14 +1,14 @@ -import traceback import math +import traceback -from wisdem.landbosse.model.ManagementCost import ManagementCost +from wisdem.landbosse.model.ErectionCost import ErectionCost +from wisdem.landbosse.model.CollectionCost import Array, Cable, ArraySystem from wisdem.landbosse.model.FoundationCost import FoundationCost +from wisdem.landbosse.model.ManagementCost import ManagementCost from wisdem.landbosse.model.SubstationCost import SubstationCost +from wisdem.landbosse.model.DevelopmentCost import DevelopmentCost from wisdem.landbosse.model.GridConnectionCost import GridConnectionCost from wisdem.landbosse.model.SitePreparationCost import SitePreparationCost -from wisdem.landbosse.model.CollectionCost import Cable, Array, ArraySystem -from wisdem.landbosse.model.ErectionCost import ErectionCost -from wisdem.landbosse.model.DevelopmentCost import DevelopmentCost class Manager: diff --git a/wisdem/landbosse/model/SitePreparationCost.py b/wisdem/landbosse/model/SitePreparationCost.py index 1ae20479e..ffa392338 100644 --- a/wisdem/landbosse/model/SitePreparationCost.py +++ b/wisdem/landbosse/model/SitePreparationCost.py @@ -1,9 +1,10 @@ -import pandas as pd -import numpy as np import math -from wisdem.landbosse.model.WeatherDelay import WeatherDelay as WD import traceback + +import numpy as np +import pandas as pd from wisdem.landbosse.model.CostModule import CostModule +from wisdem.landbosse.model.WeatherDelay import WeatherDelay as WD class SitePreparationCost(CostModule): @@ -210,9 +211,7 @@ def calculate_road_properties(self, road_properties_input, road_properties_outpu """ - if ( - False - ): # road_properties_input['road_distributed_wind'] == True or road_properties_input['turbine_rating_MW'] < 0.1: + if road_properties_input["road_distributed_wind"] == True or road_properties_input["turbine_rating_MW"] < 0.1: road_properties_output["road_volume"] = ( road_properties_input["road_length_adder_m"] * (road_properties_input["road_width_ft"] * self._meters_per_foot) @@ -307,9 +306,9 @@ def estimate_construction_time(self, estimate_construction_time_input, estimate_ list_units = operation_data["Units"].unique() if ( - False - ): # (estimate_construction_time_input['road_distributed_wind'] == True and estimate_construction_time_input[ - #'turbine_rating_MW'] >= 0.1): + estimate_construction_time_input["road_distributed_wind"] == True + and estimate_construction_time_input["turbine_rating_MW"] >= 0.1 + ): estimate_construction_time_output["topsoil_volume"] = ( estimate_construction_time_input["site_prep_area_m2"] * (estimate_construction_time_output["depth_to_subgrade_m"]) @@ -326,9 +325,9 @@ def estimate_construction_time(self, estimate_construction_time_input, estimate_ ) / 100000 # where 10.76391 sq ft = 1 sq m elif ( - False - ): # (estimate_construction_time_input['road_distributed_wind'] == True and estimate_construction_time_input[ - #'turbine_rating_MW'] < 0.1): + estimate_construction_time_input["road_distributed_wind"] == True + and estimate_construction_time_input["turbine_rating_MW"] < 0.1 + ): estimate_construction_time_output["topsoil_volume"] = ( estimate_construction_time_input["road_length_adder_m"] * (estimate_construction_time_input["road_width_ft"] * 0.3048) @@ -587,9 +586,7 @@ def calculate_costs(self, calculate_cost_input_dict, calculate_cost_output_dict) + labor_per_diem ) * calculate_cost_output_dict["wind_multiplier"] - if ( - False - ): # calculate_cost_input_dict['road_distributed_wind'] and calculate_cost_input_dict['turbine_rating_MW'] >= 0.1: + if calculate_cost_input_dict["road_distributed_wind"] and calculate_cost_input_dict["turbine_rating_MW"] >= 0.1: labor_for_new_roads_cost_usd = (labor_data["Cost USD"].sum()) + calculate_cost_output_dict[ "managament_crew_cost_before_wind_delay" @@ -602,8 +599,8 @@ def calculate_costs(self, calculate_cost_input_dict, calculate_cost_output_dict) ) elif ( - False - ): # calculate_cost_input_dict['road_distributed_wind'] and calculate_cost_input_dict['turbine_rating_MW'] < 0.1: # small DW + calculate_cost_input_dict["road_distributed_wind"] and calculate_cost_input_dict["turbine_rating_MW"] < 0.1 + ): # small DW labor_for_new_roads_cost_usd = labor_data["Cost USD"].sum() labor_for_new_and_old_roads_cost_usd = self.new_and_existing_total_road_cost(labor_for_new_roads_cost_usd) @@ -812,7 +809,7 @@ def outputs_for_detailed_tab(self, input_dict, output_dict): } ) - if True: # not input_dict['road_distributed_wind']: + if not input_dict["road_distributed_wind"]: result.append( { "unit": "m", diff --git a/wisdem/landbosse/model/SubstationCost.py b/wisdem/landbosse/model/SubstationCost.py index 14e161855..478fcc02d 100644 --- a/wisdem/landbosse/model/SubstationCost.py +++ b/wisdem/landbosse/model/SubstationCost.py @@ -1,7 +1,7 @@ -import traceback -import pandas as pd import math +import traceback +import pandas as pd from wisdem.landbosse.model.CostModule import CostModule diff --git a/wisdem/orbit/_version.py b/wisdem/orbit/_version.py index 4d4385b3d..24d24ba1c 100644 --- a/wisdem/orbit/_version.py +++ b/wisdem/orbit/_version.py @@ -9,11 +9,11 @@ """Git implementation of _version.py.""" -import errno import os import re -import subprocess import sys +import errno +import subprocess def get_keywords(): diff --git a/wisdem/orbit/api/wisdem.py b/wisdem/orbit/api/wisdem.py index 5f1941a1e..2241bb157 100644 --- a/wisdem/orbit/api/wisdem.py +++ b/wisdem/orbit/api/wisdem.py @@ -7,7 +7,6 @@ import openmdao.api as om - from wisdem.orbit import ProjectManager @@ -136,6 +135,8 @@ def setup(self): self.add_input("mooring_line_diameter", 0.1, units="m", desc="Cross-sectional diameter of a mooring line") self.add_input("mooring_line_length", 1e3, units="m", desc="Unstretched mooring line length") self.add_input("anchor_mass", 1e4, units="kg", desc="Total mass of an anchor") + self.add_input("mooring_line_cost", 0.5e6, units="USD", desc="Mooring line unit cost.") + self.add_input("mooring_anchor_cost", 0.1e6, units="USD", desc="Mooring line unit cost.") self.add_discrete_input("anchor_type", "drag_embedment", desc="Number of mooring lines per platform.") # Port @@ -152,10 +153,14 @@ def setup(self): desc="Number of cranes used at the port to load feeders / WTIVS when assembly occurs on-site or assembly cranes when assembling at port.", ) + # Floating Substructures + self.add_input("floating_substructure_cost", 10e6, units="USD", desc="Floating substructure unit cost.") + # Monopile self.add_input("monopile_length", 100.0, units="m", desc="Length of monopile.") self.add_input("monopile_diameter", 7.0, units="m", desc="Diameter of monopile.") self.add_input("monopile_mass", 900.0, units="t", desc="mass of an individual monopile.") + self.add_input("monopile_cost", 4e6, units="USD", desc="Monopile unit cost.") self.add_input( "monopile_deck_space", 0.0, @@ -169,6 +174,7 @@ def setup(self): units="m**2", desc="Deck space required to transport a transition piece. Defaults to 0 in order to not be a constraint on installation.", ) + self.add_input("transition_piece_cost", 1.5e6, units="USD", desc="Transition piece unit cost.") # Project self.add_input("site_auction_price", 100e6, units="USD", desc="Cost to secure site lease") @@ -261,6 +267,8 @@ def compile_orbit_config_file(self, inputs, outputs, discrete_inputs, discrete_o "type": "Transition Piece", "deck_space": float(inputs["transition_piece_deck_space"]), "mass": float(inputs["transition_piece_mass"]), + # No double counting of cost with TowerSE + "unit_cost": 0.0, # float(inputs["transition_piece_cost"]), }, # Electrical "array_system_design": { @@ -293,11 +301,6 @@ def compile_orbit_config_file(self, inputs, outputs, discrete_inputs, discrete_o # Phases # Putting monopile or semisub here would override the inputs we assume to get from WISDEM "design_phases": [ - "ProjectDevelopment", - #'MonopileDesign', - #'SemiSubmersibleDesign', - #'MooringSystemDesign', - #'ScourProtectionDesign', "ArraySystemDesign", "ExportSystemDesign", "OffshoreSubstationDesign", @@ -306,6 +309,11 @@ def compile_orbit_config_file(self, inputs, outputs, discrete_inputs, discrete_o # Unique design phases if floating: + config["design_phases"] += [ + "MooringSystemDesign", + # "SparDesign", + # "SemiSubmersibleDesign", + ] config["install_phases"] = { "ExportCableInstallation": 0, "OffshoreSubstationInstallation": 0, @@ -351,7 +359,11 @@ def compile_orbit_config_file(self, inputs, outputs, discrete_inputs, discrete_o "monthly_rate": float(inputs["port_cost_per_month"]), } - config["substructure"] = {"takt_time": float(inputs["takt_time"]), "towing_speed": 6} # km/h + config["substructure"] = { + "takt_time": float(inputs["takt_time"]), + "unit_cost": float(inputs["floating_substructure_cost"]), + "towing_speed": 6, # km/h + } anchorstr_in = discrete_inputs["anchor_type"].lower() if anchorstr_in.find("drag") >= 0: @@ -364,8 +376,10 @@ def compile_orbit_config_file(self, inputs, outputs, discrete_inputs, discrete_o "line_mass": 1e-3 * float(inputs["mooring_line_mass"]), "line_diam": float(inputs["mooring_line_diameter"]), "line_length": float(inputs["mooring_line_length"]), + "line_cost": float(inputs["mooring_line_cost"]), "anchor_mass": 1e-3 * float(inputs["anchor_mass"]), "anchor_type": anchorstr, + "anchor_cost": float(inputs["mooring_anchor_cost"]), } else: config["port"] = { @@ -379,6 +393,8 @@ def compile_orbit_config_file(self, inputs, outputs, discrete_inputs, discrete_o "diameter": float(inputs["monopile_diameter"]), "deck_space": float(inputs["monopile_deck_space"]), "mass": float(inputs["monopile_mass"]), + # No double counting of cost with TowerSE + "unit_cost": 0.0, # float(inputs["monopile_cost"]), } config["scour_protection_design"] = { diff --git a/wisdem/orbit/core/_defaults.py b/wisdem/orbit/core/_defaults.py deleted file mode 100644 index 40ddd6e87..000000000 --- a/wisdem/orbit/core/_defaults.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -Jake Nunemaker -National Renewable Energy Lab -07/11/2019 - -This module contains default vessel process times. -""" - - -process_times = { - # Export Cable Installation - "onshore_construction_time": 48, # hr - "trench_dig_speed": 0.1, # km/hr - "pull_winch_speed": 5, # km/hr - "tow_plow_speed": 5, # km/hr - # Array Cable Installation - # General Cable Installation - "plgr_speed": 1, # km/hr - "cable_load_time": 6, # hr - "cable_prep_time": 1, # hr - "cable_lower_time": 1, # hr - "cable_pull_in_time": 5.5, # hr - "cable_termination_time": 5.5, # hr - "cable_lay_speed": 1, # km/hr - "cable_lay_bury_speed": 0.3, # km/hr - "cable_bury_speed": 0.5, # km/hr - "cable_splice_time": 48, # hr - "cable_raise_time": 0.5, # hr - # Offshore Substation - "topside_fasten_time": 12, # hr - "topside_release_time": 2, # hr - "topside_attach_time": 6, # hr - # Monopiles - "mono_embed_len": 30, # m - "mono_drive_rate": 20, # m/hr - "mono_fasten_time": 12, # hr - "mono_release_time": 3, # hr - "tp_fasten_time": 8, # hr - "tp_release_time": 2, # hr - "tp_bolt_time": 4, # hr - "grout_cure_time": 24, # hr - "grout_pump_time": 2, # hr - # Scour Protection - "drop_rocks_time": 10, # hr - "load_rocks_time": 4, # hr - # Turbines - "tower_section_fasten_time": 4, # hr, applies to all sections - "tower_section_release_time": 3, # hr, applies to all sections - "tower_section_attach_time": 6, # hr, applies to all sections - "nacelle_fasten_time": 4, # hr - "nacelle_release_time": 3, # hr - "nacelle_attach_time": 6, # hr - "blade_fasten_time": 1.5, # hr - "blade_release_time": 1, # hr - "blade_attach_time": 3.5, # hr - # Mooring System - "mooring_system_load_time": 5, # hr - "mooring_site_survey_time": 4, # hr - "suction_pile_install_time": 11, # hr - "drag_embed_install_time": 5, # hr - # Misc. - "site_position_time": 2, # hr - "rov_survey_time": 1, # hr - "crane_reequip_time": 1, # hr -} diff --git a/wisdem/orbit/core/components.py b/wisdem/orbit/core/components.py index 5f90b298b..997a5a5fd 100644 --- a/wisdem/orbit/core/components.py +++ b/wisdem/orbit/core/components.py @@ -6,14 +6,8 @@ __email__ = "jake.nunemaker@nrel.gov" import simpy - from wisdem.orbit.core.defaults import process_times as pt -from wisdem.orbit.core.exceptions import ( - ItemNotFound, - CargoMassExceeded, - DeckSpaceExceeded, - InsufficientCable, -) +from wisdem.orbit.core.exceptions import ItemNotFound, CargoMassExceeded, DeckSpaceExceeded, InsufficientCable # TODO: __str__ methods for Components @@ -43,10 +37,6 @@ def extract_crane_specs(self, crane_specs): Dictionary of crane specifications. """ - # Physical Dimensions - self.boom_length = crane_specs.get("boom_length", None) - self.radius = crane_specs.get("radius", None) - # Operational Parameters self.max_lift = crane_specs.get("max_lift", None) self.max_hook_height = crane_specs.get("max_hook_height", None) @@ -80,6 +70,34 @@ def reequip(**kwargs): return duration +class DynamicPositioning: + """Base Dynamic Positioning Class""" + + def __init__(self, dp_specs): + """ + Creates an instance of DynamicPositioning. + + Parameters + ---------- + dp_specs : dict + Dictionary containing dynamic positioning specs. + """ + + self.extract_dp_specs(dp_specs) + + def extract_dp_specs(self, dp_specs): + """ + Extracts and defines jacking system specifications. + + Parameters + ---------- + jacksys_specs : dict + Dictionary containing jacking system specifications. + """ + + self.dp_class = dp_specs.get("class", 1) + + class JackingSys: """Base Jacking System Class""" @@ -106,7 +124,6 @@ def extract_jacksys_specs(self, jacksys_specs): """ # Physical Dimensions - self.num_legs = jacksys_specs.get("num_legs", None) self.leg_length = jacksys_specs.get("leg_length", None) self.air_gap = jacksys_specs.get("air_gap", None) self.leg_pen = jacksys_specs.get("leg_pen", None) diff --git a/wisdem/orbit/core/library.py b/wisdem/orbit/core/library.py index 6381312e7..6dd3febdb 100644 --- a/wisdem/orbit/core/library.py +++ b/wisdem/orbit/core/library.py @@ -38,7 +38,6 @@ import yaml import pandas as pd from yaml import Dumper - from wisdem.orbit.core.exceptions import LibraryItemNotFoundError ROOT = os.path.abspath(os.path.join(os.path.abspath(__file__), "../../..")) diff --git a/wisdem/orbit/core/logic/__init__.py b/wisdem/orbit/core/logic/__init__.py index 340d9403f..156444016 100644 --- a/wisdem/orbit/core/logic/__init__.py +++ b/wisdem/orbit/core/logic/__init__.py @@ -7,7 +7,9 @@ from .vessel_logic import ( # shuttle_items_to_queue + stabilize, position_onsite, + jackdown_if_required, shuttle_items_to_queue, prep_for_site_operations, get_list_of_items_from_port, diff --git a/wisdem/orbit/core/logic/vessel_logic.py b/wisdem/orbit/core/logic/vessel_logic.py index 5615d7780..c320b48cb 100644 --- a/wisdem/orbit/core/logic/vessel_logic.py +++ b/wisdem/orbit/core/logic/vessel_logic.py @@ -7,9 +7,8 @@ from marmot import process - from wisdem.orbit.core.defaults import process_times as pt -from wisdem.orbit.core.exceptions import ItemNotFound, VesselCapacityError +from wisdem.orbit.core.exceptions import ItemNotFound, MissingComponent, VesselCapacityError @process @@ -31,12 +30,8 @@ def prep_for_site_operations(vessel, survey_required=False, **kwargs): List of tasks included in preperation process. """ - site_depth = kwargs.get("site_depth", 40) - extension = kwargs.get("extension", site_depth + 10) - jackup_time = vessel.jacksys.jacking_time(extension, site_depth) - yield position_onsite(vessel, **kwargs) - yield vessel.task("Jackup", jackup_time, constraints=vessel.transit_limits, **kwargs) + yield stabilize(vessel, **kwargs) if survey_required: survey_time = kwargs.get("rov_survey_time", pt["rov_survey_time"]) @@ -48,6 +43,67 @@ def prep_for_site_operations(vessel, survey_required=False, **kwargs): ) +@process +def stabilize(vessel, **kwargs): + """ + Task representing time required to stabilize the vessel. If the vessel + has a dynamic positioning system, this task does not take any time. If the + vessel has a jacking system, the vessel will jackup. + + Parameters + ---------- + vessel : Vessel + Performing vessel. Requires configured `dynamic_positioning` or + `jacksys`. + """ + + try: + _ = vessel.dynamic_positioning + return + + except MissingComponent: + pass + + try: + jacksys = vessel.jacksys + site_depth = kwargs.get("site_depth", 40) + extension = kwargs.get("extension", site_depth + 10) + jackup_time = jacksys.jacking_time(extension, site_depth) + yield vessel.task("Jackup", jackup_time, constraints=vessel.transit_limits, **kwargs) + + except MissingComponent: + raise MissingComponent(vessel, ["Dynamic Positioning", "Jacking System"]) + + +@process +def jackdown_if_required(vessel, **kwargs): + """ + Task representing time required to jackdown the vessel, if jacking system + is configured. If not, this task does not take anytime and the vessel is + released. + + Parameters + ---------- + vessel : Vessel + Performing vessel. + """ + + try: + jacksys = vessel.jacksys + site_depth = kwargs.get("site_depth", 40) + extension = kwargs.get("extension", site_depth + 10) + jackdown_time = jacksys.jacking_time(extension, site_depth) + yield vessel.task( + "Jackdown", + jackdown_time, + constraints=vessel.transit_limits, + **kwargs, + ) + + except MissingComponent: + return + + @process def position_onsite(vessel, **kwargs): """ @@ -84,9 +140,6 @@ def shuttle_items_to_queue(vessel, port, queue, distance, items, **kwargs): """ transit_time = vessel.transit_time(distance) - site_depth = kwargs.get("site_depth", 40) - extension = kwargs.get("extension", site_depth + 10) - jackup_time = vessel.jacksys.jacking_time(extension, site_depth) while True: @@ -112,12 +165,7 @@ def shuttle_items_to_queue(vessel, port, queue, distance, items, **kwargs): vessel.update_trip_data() vessel.at_port = False yield vessel.task("Transit", transit_time, constraints=vessel.transit_limits) - yield vessel.task( - "Jackup", - jackup_time, - constraints=vessel.transit_limits, - **kwargs, - ) + yield stabilize(vessel, **kwargs) vessel.at_site = True if vessel.at_site: @@ -148,12 +196,7 @@ def shuttle_items_to_queue(vessel, port, queue, distance, items, **kwargs): # Transit back to port vessel.at_site = False - yield vessel.task( - "Jackdown", - jackup_time, - constraints=vessel.transit_limits, - **kwargs, - ) + yield jackdown_if_required(vessel, **kwargs) yield vessel.task("Transit", transit_time, constraints=vessel.transit_limits) vessel.at_port = True diff --git a/wisdem/orbit/core/vessel.py b/wisdem/orbit/core/vessel.py index 95b3b939c..e3534d80a 100644 --- a/wisdem/orbit/core/vessel.py +++ b/wisdem/orbit/core/vessel.py @@ -10,12 +10,12 @@ import numpy as np from marmot import Agent, le, process from marmot._exceptions import AgentNotRegistered - from wisdem.orbit.core.components import ( Crane, JackingSys, CableCarousel, VesselStorage, + DynamicPositioning, ScourProtectionStorage, ) from wisdem.orbit.core.exceptions import ItemNotFound, MissingComponent @@ -132,6 +132,15 @@ def jacksys(self): except AttributeError: raise MissingComponent(self, "Jacking System") + @property + def dynamic_positioning(self): + """Returns configured `DynamicPositioning` or raises `MissingComponent`.""" + try: + return self._dp_system + + except AttributeError: + raise MissingComponent(self, "Dynamic Positioning") + @property def storage(self): """Returns configured `VesselStorage` or raises `MissingComponent`.""" @@ -168,6 +177,7 @@ def initialize(self, mobilize=True): self._vessel_specs = self.config.get("vessel_specs", {}) self.extract_transport_specs() self.extract_jacksys_specs() + self.extract_dp_specs() self.extract_crane_specs() self.extract_storage_specs() self.extract_cable_storage_specs() @@ -196,6 +206,13 @@ def extract_jacksys_specs(self): if self._jacksys_specs: self._jacksys = JackingSys(self._jacksys_specs) + def extract_dp_specs(self): + """Extracts dynamic positioning system specifications if found.""" + + self._dp_specs = self.config.get("dynamic_positioning_specs", {}) + if self._dp_specs: + self._dp_system = DynamicPositioning(self._dp_specs) + def extract_storage_specs(self): """Extracts storage system specifications if found.""" diff --git a/wisdem/orbit/manager.py b/wisdem/orbit/manager.py index e4c07865e..7ea4cb2b4 100644 --- a/wisdem/orbit/manager.py +++ b/wisdem/orbit/manager.py @@ -14,19 +14,13 @@ import numpy as np import pandas as pd - from wisdem.orbit.phases import DesignPhase, InstallPhase -from wisdem.orbit.core.library import ( - initialize_library, - export_library_specs, - extract_library_data, -) +from wisdem.orbit.core.library import initialize_library, export_library_specs, extract_library_data from wisdem.orbit.phases.design import ( SparDesign, MonopileDesign, ArraySystemDesign, ExportSystemDesign, - ProjectDevelopment, MooringSystemDesign, ScourProtectionDesign, SemiSubmersibleDesign, @@ -44,11 +38,7 @@ ScourProtectionInstallation, OffshoreSubstationInstallation, ) -from wisdem.orbit.core.exceptions import ( - PhaseNotFound, - WeatherProfileError, - PhaseDependenciesInvalid, -) +from wisdem.orbit.core.exceptions import PhaseNotFound, WeatherProfileError, PhaseDependenciesInvalid class ProjectManager: @@ -58,7 +48,6 @@ class ProjectManager: date_format_long = "%m/%d/%Y %H:%M" _design_phases = [ - ProjectDevelopment, MonopileDesign, ArraySystemDesign, CustomArraySystemDesign, @@ -105,17 +94,20 @@ def __init__(self, config, library_path=None, weather=None): *config.get("install_phases", []), ], ) + self._phases = {} self.config = self.resolve_project_capacity(config) self.weather = self.transform_weather_input(weather) + self.design_results = {} + self.detailed_outputs = {} + + self.system_costs = {} + self.installation_costs = {} + + # TODO: Revise: self.phase_starts = {} self.phase_times = {} - self.phase_costs = {} self._output_logs = [] - self._phases = {} - - self.design_results = {} - self.detailed_outputs = {} def run_project(self, **kwargs): """ @@ -206,6 +198,10 @@ def compile_input_dict(cls, phases): "contingency": "$/kW (optional, default: 316)", "commissioning": "$/kW (optional, default: 44)", "decommissioning": "$/kW (optional, default: 58)", + "site_auction_price": "$ (optional, default: 100e6)", + "site_assessment_cost": "$ (optional, default: 50e6)", + "construction_plan_cost": "$ (optional, default: 1e6)", + "installation_plan_cost": "$ (optional, default: 0.25e6)", } config["design_phases"] = [*design_phases.keys()] @@ -418,8 +414,6 @@ def run_install_phase(self, name, start, **kwargs): ------- time : int | float Total phase time. - cost : int | float - Total phase cost. logs : list List of phase logs. """ @@ -442,10 +436,7 @@ def run_install_phase(self, name, start, **kwargs): except Exception as e: print(f"\n\t - {name}: {e}") - self.phase_costs[name] = e.__class__.__name__ - self.phase_times[name] = e.__class__.__name__ - - return None, None, None + return None, None else: phase = _class(_config, weather=weather, phase_name=name, **processes) @@ -454,15 +445,19 @@ def run_install_phase(self, name, start, **kwargs): self._phases[name] = phase time = phase.total_phase_time - cost = phase.total_phase_cost logs = deepcopy(phase.env.logs) self.phase_starts[name] = start - self.phase_costs[name] = cost self.phase_times[name] = time self.detailed_outputs = self.merge_dicts(self.detailed_outputs, phase.detailed_output) - return cost, time, logs + if phase.system_capex: + self.system_costs[name] = phase.system_capex + + if phase.installation_capex: + self.installation_costs[name] = phase.installation_capex + + return time, logs def get_phase_class(self, phase): """ @@ -517,8 +512,6 @@ def run_design_phase(self, name, **kwargs): except Exception as e: print(f"\n\t - {name}: {e}") - self.phase_costs[name] = e.__class__.__name__ - self.phase_times[name] = e.__class__.__name__ return else: @@ -527,8 +520,6 @@ def run_design_phase(self, name, **kwargs): self._phases[name] = phase - self.phase_costs[name] = phase.total_phase_cost - self.phase_times[name] = phase.total_phase_time self.design_results = self.merge_dicts(self.design_results, phase.design_result, overwrite=False) self.config = self.merge_dicts(self.config, phase.design_result, overwrite=False) @@ -547,7 +538,7 @@ def run_multiple_phases_in_serial(self, phase_list, **kwargs): start = 0 for name in phase_list: - _, time, logs = self.run_install_phase(name, start, **kwargs) + time, logs = self.run_install_phase(name, start, **kwargs) if logs is None: continue @@ -579,7 +570,7 @@ def run_multiple_phases_overlapping(self, phases, **kwargs): # Run defined for name, start in defined.items(): - _, _, logs = self.run_install_phase(name, start, **kwargs) + _, logs = self.run_install_phase(name, start, **kwargs) if logs is None: continue @@ -625,7 +616,7 @@ def run_dependent_phases(self, _phases, zero, **kwargs): try: start = self.get_dependency_start_time(target, perc) - cost, time, logs = self.run_install_phase(name, start, **kwargs) + _, logs = self.run_install_phase(name, start, **kwargs) progress = True @@ -1015,39 +1006,31 @@ def _diff_dates_long(self, a, b): return abs((a - b).days) @property - def phase_costs_per_kw(self): + def overnight_capex_per_kw(self): """ - Returns phase costs in CAPEX/kW. + Returns overnight CAPEX/kW. """ - _dict = {} - for k, capex in self.phase_costs.items(): - - try: - _dict[k] = capex / (self.capacity * 1000) + try: + capex = self.overnight_capex / (self.capacity * 1000) - except TypeError: - pass + except TypeError: + capex = None - return _dict + return capex @property - def overnight_capex(self): - """Returns the overnight capital cost of the project.""" - - design_phases = [p.__name__ for p in self._design_phases] - design_cost = sum([v for k, v in self.phase_costs.items() if k in design_phases]) + def system_capex(self): + """Returns total system procurement CapEx.""" - return design_cost + self.turbine_capex + return np.nansum([c for _, c in self.system_costs.items()]) @property - def overnight_capex_per_kw(self): - """ - Returns overnight CAPEX/kW. - """ + def system_capex_per_kw(self): + """Returns system CapEx/kW.""" try: - capex = self.overnight_capex / (self.capacity * 1000) + capex = self.system_capex / (self.capacity * 1000) except TypeError: capex = None @@ -1056,20 +1039,13 @@ def overnight_capex_per_kw(self): @property def installation_capex(self): - """ - Returns installation related CAPEX. - """ + """Returns total installation related CapEx.""" - res = sum( - [v for k, v in self.phase_costs.items() if k in self.config["install_phases"] and isinstance(v, Number)] - ) - return res + return np.nansum([c for _, c in self.installation_costs.items()]) @property def installation_capex_per_kw(self): - """ - Returns installation related CAPEX/kW. - """ + """Returns installation CapEx/kW.""" try: capex = self.installation_capex / (self.capacity * 1000) @@ -1081,17 +1057,13 @@ def installation_capex_per_kw(self): @property def bos_capex(self): - """ - Returns BOS CAPEX not including commissioning and decommissioning. - """ + """Returns total balance of system CapEx.""" - return sum([v for _, v in self.phase_costs.items()]) + return self.system_capex + self.installation_capex @property def bos_capex_per_kw(self): - """ - Returns BOS CAPEX/kW not including commissioning and decommissioning. - """ + """Returns balance of system CapEx/kW.""" try: capex = self.bos_capex / (self.capacity * 1000) @@ -1125,34 +1097,16 @@ def turbine_capex(self): @property def turbine_capex_per_kw(self): - """ - Returns the turbine CAPEX/kW. - """ + """Returns the turbine CapEx/kW.""" _capex = self.project_params.get("turbine_capex", None) return _capex @property - def total_capex(self): - """ - Returns total project CAPEX including commissioning and decommissioning. - """ - - return self.bos_capex + self.turbine_capex + self.soft_capex - - @property - def total_capex_per_kw(self): - """ - Returns total BOS CAPEX/kW including commissioning and decommissioning. - """ - - try: - capex = self.total_capex / (self.capacity * 1000) - - except TypeError: - capex = None + def overnight_capex(self): + """Returns the overnight capital cost of the project.""" - return capex + return self.system_capex + self.turbine_capex @property def soft_capex(self): @@ -1181,6 +1135,57 @@ def soft_capex_per_kw(self): return sum([insurance, financing, contingency, commissioning, decommissioning]) + @property + def project_capex(self): + """ + Returns project related CapEx line items. To override the defaults, + the keys below should be passed to the 'project_parameters' subdict. + """ + + site_auction = self.project_params.get("site_auction_price", 100e6) + site_assessment = self.project_params.get("site_assessment_cost", 50e6) + construction_plan = self.project_params.get("construction_plan_cost", 1e6) + installation_plan = self.project_params.get("installation_plan_cost", 0.25e6) + + return sum( + [ + site_auction, + site_assessment, + construction_plan, + installation_plan, + ] + ) + + @property + def project_capex_per_kw(self): + """Returns project related CapEx per kW.""" + + try: + capex = self.project_capex / (self.capacity * 1000) + + except TypeError: + capex = None + + return capex + + @property + def total_capex(self): + """Returns total project CapEx including soft costs.""" + + return self.bos_capex + self.turbine_capex + self.soft_capex + + @property + def total_capex_per_kw(self): + """Returns total CapEx/kW.""" + + try: + capex = self.total_capex / (self.capacity * 1000) + + except TypeError: + capex = None + + return capex + def export_configuration(self, file_name): """ Exports the configuration settings for the project to diff --git a/wisdem/orbit/phases/base.py b/wisdem/orbit/phases/base.py index e49f9d1ce..051314450 100644 --- a/wisdem/orbit/phases/base.py +++ b/wisdem/orbit/phases/base.py @@ -9,10 +9,7 @@ from abc import ABC, abstractmethod from copy import deepcopy -from wisdem.orbit.core.library import ( - initialize_library, - extract_library_data, -) +from wisdem.orbit.core.library import initialize_library, extract_library_data from wisdem.orbit.core.exceptions import MissingInputs @@ -24,17 +21,6 @@ class BasePhase(ABC): interfaces for all phases defined by subclasses. Many of the methods below should be overwritten in subclasses. - Attributes - ---------- - phase : str - Name of the phase that is being used. - total_phase_cost : float - Calculates the total phase cost. Should be implemented in each subclass. - detailed_output : dict - Creates the detailed output dictionary. Should be implemented in each - subclass. - phase_dataframe : pd.DataFrame - Methods ------- run() @@ -136,24 +122,3 @@ def run(self): """Main run function for phase.""" pass - - @property - @abstractmethod - def total_phase_cost(self): - """Returns total phase cost in $USD.""" - - pass - - @property - @abstractmethod - def total_phase_time(self): - """Returns total phase time in hours.""" - - pass - - @property - @abstractmethod - def detailed_output(self): - """Returns detailed phase information.""" - - pass diff --git a/wisdem/orbit/phases/design/__init__.py b/wisdem/orbit/phases/design/__init__.py index c7d85801e..d234df4c7 100644 --- a/wisdem/orbit/phases/design/__init__.py +++ b/wisdem/orbit/phases/design/__init__.py @@ -11,7 +11,6 @@ from .spar_design import SparDesign from .monopile_design import MonopileDesign from .array_system_design import ArraySystemDesign, CustomArraySystemDesign -from .project_development import ProjectDevelopment from .export_system_design import ExportSystemDesign from .mooring_system_design import MooringSystemDesign from .scour_protection_design import ScourProtectionDesign diff --git a/wisdem/orbit/phases/design/_cables.py b/wisdem/orbit/phases/design/_cables.py index 23f6aa517..73e3d10da 100644 --- a/wisdem/orbit/phases/design/_cables.py +++ b/wisdem/orbit/phases/design/_cables.py @@ -11,7 +11,6 @@ import numpy as np from scipy.optimize import fsolve - from wisdem.orbit.core.library import extract_library_specs from wisdem.orbit.phases.design import DesignPhase @@ -405,7 +404,7 @@ def cost_by_type(self): return cost @property - def total_phase_cost(self): + def total_cost(self): """ Calculates the cost of the array cabling system. @@ -417,15 +416,9 @@ def total_phase_cost(self): return sum(self.cost_by_type.values()) - @property - def total_phase_time(self): - return self._design.get("design_time", 0.0) - @property def detailed_output(self): - """ - Returns detailed design outputs. - """ + """Returns detailed design outputs.""" _output = { "length": self.total_cable_length_by_type, @@ -458,7 +451,7 @@ def design_result(self): raise Exception(f"Has {self.__class__.__name__} been ran?") system = "_".join((self.cable_type, "system")) - output = {system: {"cables": {}}} + output = {system: {"cables": {}, "system_cost": self.total_cost}} _temp = output[system]["cables"] for name, cable in self.cables.items(): diff --git a/wisdem/orbit/phases/design/array_system_design.py b/wisdem/orbit/phases/design/array_system_design.py index 33321127d..bad4308da 100644 --- a/wisdem/orbit/phases/design/array_system_design.py +++ b/wisdem/orbit/phases/design/array_system_design.py @@ -12,7 +12,6 @@ import numpy as np import pandas as pd import matplotlib.pyplot as plt - from wisdem.orbit.core.library import export_library_specs, extract_library_specs from wisdem.orbit.phases.design._cables import Plant, CableSystem @@ -77,14 +76,13 @@ class ArraySystemDesign(CableSystem): }, "turbine": {"rotor_diameter": "m", "turbine_rating": "MW"}, "array_system_design": { - "design_time": "hrs (optional)", "cables": "list | str", "touchdown_distance": "m (optional, default: 0)", "average_exclusion_percent": "float (optional)", }, } - output_config = {"array_system": {"cables": "dict"}} + output_config = {"array_system": {"cables": "dict", "system_cost": "USD"}} def __init__(self, config, **kwargs): """ @@ -506,7 +504,6 @@ class CustomArraySystemDesign(ArraySystemDesign): "plant": {"layout": "str", "num_turbines": "int"}, "turbine": {"turbine_rating": "int | float"}, "array_system_design": { - "design_time": "int | float (optional)", "cables": "list | str", "location_data": "str", "distance": "bool (optional)", diff --git a/wisdem/orbit/phases/design/export_system_design.py b/wisdem/orbit/phases/design/export_system_design.py index 484788d18..d62f8288c 100644 --- a/wisdem/orbit/phases/design/export_system_design.py +++ b/wisdem/orbit/phases/design/export_system_design.py @@ -6,7 +6,6 @@ __email__ = "robert.hammond@nrel.gov" import numpy as np - from wisdem.orbit.phases.design._cables import CableSystem @@ -203,7 +202,12 @@ def design_result(self): if self.cables is None: raise Exception(f"Has {self.__class__.__name__} been ran?") - output = {"export_system": {"interconnection_distance": self._distance_to_interconnection}} + output = { + "export_system": { + "interconnection_distance": self._distance_to_interconnection, + "system_cost": self.total_cost, + } + } for name, cable in self.cables.items(): diff --git a/wisdem/orbit/phases/design/monopile_design.py b/wisdem/orbit/phases/design/monopile_design.py index 474c7b03a..e2fdc6c1b 100644 --- a/wisdem/orbit/phases/design/monopile_design.py +++ b/wisdem/orbit/phases/design/monopile_design.py @@ -9,7 +9,6 @@ from math import pi, log from scipy.optimize import fsolve - from wisdem.orbit.core.defaults import common_costs from wisdem.orbit.phases.design import DesignPhase @@ -40,8 +39,6 @@ class MonopileDesign(DesignPhase): "weibull_scale_factor": "float (optional)", "weibull_shape_factor": "float (optional)", "turb_length_scale": "m (optional)", - "design_time": "h (optional)", - "design_cost": "USD (optional)", "monopile_steel_cost": "USD/t (optional)", "tp_steel_cost": "USD/t (optional)", }, @@ -56,8 +53,14 @@ class MonopileDesign(DesignPhase): "length": "m", "mass": "t", "deck_space": "m2", + "unit_cost": "USD", + }, + "transition_piece": { + "length": "m", + "mass": "t", + "deck_space": "m2", + "unit_cost": "USD", }, - "transition_piece": {"length": "m", "mass": "t", "deck_space": "m2"}, } def __init__(self, config, **kwargs): @@ -125,8 +128,8 @@ def design_monopile( Returns ------- - sizing : dict - Dictionary of monopile sizing. + monopile : dict + Dictionary of monopile sizing and costs. - ``diameter`` - Pile diameter (m) - ``thickness`` - Pile wall thickness (m) @@ -158,33 +161,34 @@ def design_monopile( ) data = (yield_stress, material_factor, M_50y) - sizing = {} + monopile = {} # Monopile sizing - sizing["diameter"] = fsolve(self.pile_diam_equation, 10, args=data)[0] - sizing["thickness"] = self.pile_thickness(sizing["diameter"]) - sizing["moment"] = self.pile_moment(sizing["diameter"], sizing["thickness"]) - sizing["embedment_length"] = self.pile_embedment_length(sizing["moment"], **kwargs) + monopile["diameter"] = fsolve(self.pile_diam_equation, 10, args=data)[0] + monopile["thickness"] = self.pile_thickness(monopile["diameter"]) + monopile["moment"] = self.pile_moment(monopile["diameter"], monopile["thickness"]) + monopile["embedment_length"] = self.pile_embedment_length(monopile["moment"], **kwargs) # Total length airgap = kwargs.get("airgap", 10) # m - sizing["length"] = sizing["embedment_length"] + site_depth + airgap - sizing["mass"] = self.pile_mass( - Dp=sizing["diameter"], - tp=sizing["thickness"], - Lt=sizing["length"], + monopile["length"] = monopile["embedment_length"] + site_depth + airgap + monopile["mass"] = self.pile_mass( + Dp=monopile["diameter"], + tp=monopile["thickness"], + Lt=monopile["length"], **kwargs, ) # Deck space - sizing["deck_space"] = sizing["diameter"] ** 2 + monopile["deck_space"] = monopile["diameter"] ** 2 - self.monopile_sizing = sizing + # Costs + monopile["unit_cost"] = monopile["mass"] * self.monopile_steel_cost - return sizing + self.monopile_sizing = monopile + return monopile - @staticmethod - def design_transition_piece(D_p, t_p, **kwargs): + def design_transition_piece(self, D_p, t_p, **kwargs): """ Designs transition piece given the results of the monopile design. @@ -218,15 +222,14 @@ def design_transition_piece(D_p, t_p, **kwargs): "mass": m_tp, "length": L_tp, "deck_space": D_tp ** 2, + "unit_cost": m_tp * self.tp_steel_cost, } return tp_design @property def design_result(self): - """ - Returns the results of :py:meth:`.design_monopile`. - """ + """Returns the results of :py:meth:`.design_monopile`.""" if not self._outputs: raise Exception("Has MonopileDesign been ran yet?") @@ -234,22 +237,10 @@ def design_result(self): return self._outputs @property - def total_phase_cost(self): - """Returns total phase cost in $USD.""" - - _design = self.config.get("monopile_design", {}) - design_cost = _design.get("design_cost", 0.0) - material_cost = sum([v for _, v in self.material_cost.items()]) - - return design_cost + material_cost + def total_cost(self): + """Returns total cost of the substructures and transition pieces.""" - @property - def total_phase_time(self): - """Returns total phase time in hours.""" - - _design = self.config.get("monopile_design", {}) - phase_time = _design.get("design_time", 0.0) - return phase_time + return sum([v for _, v in self.material_cost.items()]) @property def detailed_output(self): diff --git a/wisdem/orbit/phases/design/mooring_system_design.py b/wisdem/orbit/phases/design/mooring_system_design.py index 53dc2f5a9..a0b0f381c 100644 --- a/wisdem/orbit/phases/design/mooring_system_design.py +++ b/wisdem/orbit/phases/design/mooring_system_design.py @@ -31,9 +31,11 @@ class MooringSystemDesign(DesignPhase): "num_lines": "int", "line_diam": "m, float", "line_mass": "t", + "line_cost": "USD", "line_length": "m", "anchor_mass": "t", "anchor_type": "str", + "anchor_cost": "USD", } } @@ -130,43 +132,17 @@ def calculate_anchor_mass_cost(self): self.anchor_mass = 50 self.anchor_cost = sqrt(self.breaking_load / 9.81 / 1250) * 150000 - def calculate_total_cost(self): - """ - Returns the total cost of the mooring system. - """ - - return self.num_lines * self.num_turbines * (self.anchor_cost + self.line_length * self.line_cost_rate) - - @property - def design_result(self): - """Returns the results of the design phase.""" - - return { - "mooring_system": { - "num_lines": self.num_lines, - "line_diam": self.line_diam, - "line_mass": self.line_mass, - "line_length": self.line_length, - "anchor_mass": self.anchor_mass, - "anchor_type": self.anchor_type, - } - } - @property - def total_phase_cost(self): - """Returns total phase cost in $USD.""" + def line_cost(self): + """Returns cost of one line mooring line.""" - _design = self.config.get("mooring_system_design", {}) - design_cost = _design.get("design_cost", 0.0) - return self.calculate_total_cost() + design_cost + return self.line_length * self.line_cost_rate @property - def total_phase_time(self): - """Returns total phase time in hours.""" + def total_cost(self): + """Returns the total cost of the mooring system.""" - _design = self.config.get("mooring_system_design", {}) - phase_time = _design.get("design_time", 0.0) - return phase_time + return self.num_lines * self.num_turbines * (self.anchor_cost + self.line_length * self.line_cost_rate) @property def detailed_output(self): @@ -177,8 +153,15 @@ def detailed_output(self): "line_diam": self.line_diam, "line_mass": self.line_mass, "line_length": self.line_length, + "line_cost": self.line_cost, "anchor_type": self.anchor_type, "anchor_mass": self.anchor_mass, "anchor_cost": self.anchor_cost, - "system_cost": self.calculate_total_cost(), + "system_cost": self.total_cost, } + + @property + def design_result(self): + """Returns the results of the design phase.""" + + return {"mooring_system": self.detailed_output} diff --git a/wisdem/orbit/phases/design/oss_design.py b/wisdem/orbit/phases/design/oss_design.py index 9a04631df..8b17c0fd0 100644 --- a/wisdem/orbit/phases/design/oss_design.py +++ b/wisdem/orbit/phases/design/oss_design.py @@ -7,7 +7,6 @@ import numpy as np - from wisdem.orbit.phases.design import DesignPhase @@ -31,7 +30,6 @@ class OffshoreSubstationDesign(DesignPhase): "oss_substructure_cost_rate": "USD/t (optional)", "oss_pile_cost_rate": "USD/t (optional)", "num_substations": "int (optional)", - "design_time": "h (optional)", }, } @@ -69,22 +67,45 @@ def run(self): self.calc_ancillary_system_cost() self.calc_assembly_cost() self.calc_substructure_mass_and_cost() - self.calc_substation_cost() self._outputs["offshore_substation_substructure"] = { "type": "Monopile", # Substation install only supports monopiles "deck_space": self.substructure_deck_space, "mass": self.substructure_mass, "length": self.substructure_length, + "unit_cost": self.substructure_cost, } self._outputs["offshore_substation_topside"] = { "deck_space": self.topside_deck_space, "mass": self.topside_mass, + "unit_cost": self.substation_cost, } self._outputs["num_substations"] = self.num_substations + @property + def substation_cost(self): + """Returns total procuremet cost of the topside.""" + + return ( + self.mpt_cost + + self.topside_cost + + self.shunt_reactor_cost + + self.switchgear_costs + + self.ancillary_system_costs + + self.land_assembly_cost + ) + + @property + def total_cost(self): + """Returns total procurement cost of the substation(s).""" + + if not self._outputs: + raise Exception("Has OffshoreSubstationDesign been ran yet?") + + return (self.substructure_cost + self.substation_cost) * self.num_substations + def calc_substructure_length(self): """ Calculates substructure length as the site depth + 10m @@ -245,27 +266,6 @@ def calc_substructure_mass_and_cost(self): self.substructure_mass = substructure_mass + substructure_pile_mass - def calc_substation_cost(self): - """ - Calculates the total cost of the substation solution, based on the - number of configured substations. - """ - - self.substation_cost = ( - sum( - [ - self.mpt_cost, - self.topside_cost, - self.shunt_reactor_cost, - self.switchgear_costs, - self.ancillary_system_costs, - self.land_assembly_cost, - self.substructure_cost, - ] - ) - * self.num_substations - ) - @property def design_result(self): """ @@ -277,23 +277,6 @@ def design_result(self): return self._outputs - @property - def total_phase_cost(self): - """Returns total phase cost in $USD.""" - - if not self._outputs: - raise Exception("Has OffshoreSubstationDesign been ran yet?") - - return self.substation_cost - - @property - def total_phase_time(self): - """Returns total phase time in hours.""" - - _design = self.config.get("substation_design", {}) - phase_time = _design.get("design_time", 0.0) - return phase_time - @property def detailed_output(self): """Returns detailed phase information.""" diff --git a/wisdem/orbit/phases/design/project_development.py b/wisdem/orbit/phases/design/project_development.py deleted file mode 100644 index f79ae7172..000000000 --- a/wisdem/orbit/phases/design/project_development.py +++ /dev/null @@ -1,241 +0,0 @@ -"""Provides the `ProjectDevelopment` class.""" - -__author__ = "Jake Nunemaker" -__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" -__maintainer__ = "Jake Nunemaker" -__email__ = "jake.nunemaker@nrel.gov" - - -from .design_phase import DesignPhase - - -class ProjectDevelopment(DesignPhase): - """Project Development Class.""" - - expected_config = { - "project_development": { - "site_auction_duration": "h (optional)", - "site_auction_price": "USD(optional)", - "site_assessment_plan_duration": "h (optional)", - "site_assessment_plan_cost": "USD (optional)", - "site_assessment_duration": "h (optional)", - "site_assessment_cost": "USD (optional)", - "construction_operations_plan_duration": "h (optional)", - "construction_operations_plan_cost": "USD (optional)", - "boem_review_duration": "h (optional)", - "boem_review_cost": "USD (optional)", - "design_install_plan_duration": "h (optional)", - "design_install_plan_cost": "USD (optional)", - } - } - - output_config = {} - - def __init__(self, config, **kwargs): - """ - Creates an instance of ProjectDevelopment. - - Parameters - ---------- - config : dict - """ - - config = self.initialize_library(config, **kwargs) - self.config = self.validate_config(config) - self._outputs = {} - - def run(self): - """ - Main run function. Passes ``self.config['project_development']`` to the - following methods: - - - :py:meth:`.site_auction` - - :py:meth:`.site_assessment_plan_development` - - :py:meth:`.site_assessment` - - :py:meth:`.construction_operations_plan_development` - - :py:meth:`.boem_review` - - :py:meth:`.design_install_plan_development` - """ - - dev_specs = self.config.get("project_development", {}) - - self.site_auction(**dev_specs) - self.site_assessment_plan_development(**dev_specs) - self.site_assessment(**dev_specs) - self.construction_operations_plan_development(**dev_specs) - self.boem_review(**dev_specs) - self.design_install_plan_development(**dev_specs) - - @property - def design_result(self): - """ - Returns design results for ProjectDevelopment. This method currently - returns an empty dictionary as ProjectDevelopment does not produce any - additional config keys. - """ - - return {} - - @property - def total_phase_cost(self): - """Returns total phase cost in $USD.""" - - phase_cost = sum([v["cost"] for v in self._outputs.values()]) - return phase_cost - - @property - def total_phase_time(self): - """Returns total phase time in hours.""" - - phase_time = sum([v["duration"] for v in self._outputs.values()]) - return phase_time - - @property - def detailed_output(self): - """Returns detailed phase information.""" - - if not self._outputs: - raise Exception("Has ProjectDevelopment been ran yet?") - - return self._outputs - - def site_auction(self, **development_specs): - """ - Cost and duration associated with lease area auction. - - Parameters - ---------- - development_specs : dict - Dicitonary containing development specifications. Keys: - - - ``site_auction_duration`` - Auction duration in hours. - - ``site_auction_cost`` - Auction price. - """ - - t = "site_auction_duration" - c = "site_auction_cost" - self._outputs["site_auction"] = { - "duration": development_specs.get(t, 0), - "cost": development_specs.get(c, 100e6), - } - - def site_assessment_plan_development(self, **development_specs): - """ - Cost and duration associated with developing a site assessment plan. - - Parameters - ---------- - development_specs : dict - Dicitonary containing development specifications. Keys: - - - ``site_assessment_plan_duration`` - Site assesment plan - development duration in hours. - - ``site_assessment_plan_cost`` - Site assessment plan development - cost. - """ - - t = "site_assessment_plan_duration" - c = "site_assessment_plan_cost" - self._outputs["site_assessment_plan"] = { - "duration": development_specs.get(t, 8760), - "cost": development_specs.get(c, 0.5e6), - } - - def site_assessment(self, **development_specs): - """ - Cost and duration for conducting site assessments/surveys and - obtaining permits. - - Parameters - ---------- - development_specs : dict - Dicitonary containing development specifications. Keys: - - - ``site_assessment_duration`` - Site assesment duration in hours. - - ``site_assessment_cost`` - Site assessment costs. - """ - - t = "site_assessment_duration" - c = "site_assessment_cost" - self._outputs["site_assessment"] = { - "duration": development_specs.get(t, 43800), - "cost": development_specs.get(c, 50e6), - } - - def construction_operations_plan_development(self, **development_specs): - """ - Cost and duration for developing the Construction and Operations Plan - (COP). Typically occurs in parallel to Site Assessment. - - Parameters - ---------- - development_specs : dict - Dicitonary containing development specifications. Keys: - - - ``construction_operations_plan_duration`` - Construction and - operations plan development duration in hours. - - ``construction_operations_plan_cost`` - Construction and - operations plan development cost. - """ - - t = "construction_operations_plan_duration" - c = "construction_operations_plan_cost" - self._outputs["construction_operations_plan"] = { - "duration": development_specs.get(t, 43800), - "cost": development_specs.get(c, 1e6), - } - - def boem_review(self, **development_specs): - """ - Cost and duration for BOEM to review the Construction and Operations - Plan. - - Parameters - ---------- - development_specs : dict - Dicitonary containing development specifications. Keys: - - - ``boem_review_duration`` - BOEM review duration in hours. - - ``boem_review_cost`` - BOEM review cost. Typically not a cost to - developers. - """ - - t = "boem_review_duration" - c = "boem_review_cost" - self._outputs["boem_review"] = { - "duration": development_specs.get(t, 8760), - "cost": development_specs.get(c, 0), - } - - def design_install_plan_development(self, **development_specs): - """ - Cost and duration for developing the Design and Installation Plan. - Typically occurs in parallel with BOEM Review. - - Parameters - ---------- - development_specs : dict - Dicitonary containing development specifications. Keys: - - - ``design_install_plan_duration`` - Design and installation plan - development duration in hours. - - ``design_install_plan_cost`` - Design and installation plan - development cost. - """ - - t = "design_install_plan_duration" - c = "design_install_plan_cost" - self._outputs["design_install_plan"] = { - "duration": development_specs.get(t, 8760), - "cost": development_specs.get(c, 0.25e6), - } - - @property - def design_result(self): - """ - Returns design results for ProjectDevelopment. This method currently - returns an empty dictionary as ProjectDevelopment does not produce any - additional config keys. - """ - - return {} diff --git a/wisdem/orbit/phases/design/scour_protection_design.py b/wisdem/orbit/phases/design/scour_protection_design.py index 60d01df25..dd707ca8f 100644 --- a/wisdem/orbit/phases/design/scour_protection_design.py +++ b/wisdem/orbit/phases/design/scour_protection_design.py @@ -8,7 +8,6 @@ from math import ceil import numpy as np - from wisdem.orbit.phases.design import DesignPhase @@ -55,14 +54,18 @@ class ScourProtectionDesign(DesignPhase): "scour_protection_design": { "cost_per_tonne": "USD/t", "rock_density": "kg/m3 (optional)", - "design_time": "h (optional)", "soil_friction_angle": "float (optional)", "scour_depth_equilibrium": "float (optional)", "scour_protection_depth": "m (optional)", }, } - output_config = {"scour_protection": {"tons_per_substructure": "int"}} + output_config = { + "scour_protection": { + "tonnes_per_substructure": "t", + "cost_per_tonne": "USD/t", + } + } def __init__(self, config, **kwargs): """ @@ -122,29 +125,20 @@ def run(self): self.compute_scour_protection_tonnes_to_install() @property - def total_phase_cost(self): - """ - Returns the total cost of the phase in $USD - """ + def total_cost(self): + """Returns the total cost of the phase in $USD""" cost = self._design["cost_per_tonne"] * self.scour_protection_tonnes * self.num_turbines return cost - @property - def total_phase_time(self): - """ - Returns the total time requried for the phase, in hours. - """ - - return self._design.get("design_time", 0.0) - @property def detailed_output(self): - """ - Returns detailed outputs of the design. - """ + """Returns detailed outputs of the design.""" - _out = {"scour_protection_per_substructure": self.scour_protection_tonnes} + _out = { + "tonnes_per_substructure": self.scour_protection_tonnes, + "cost_per_tonne": self._design["cost_per_tonne"], + } return _out @property @@ -160,5 +154,5 @@ def design_result(self): - ``tonnes_per_substructure`` : `int` """ - output = {"scour_protection": {"tons_per_substructure": self.scour_protection_tonnes}} + output = {"scour_protection": self.detailed_output} return output diff --git a/wisdem/orbit/phases/design/semi_submersible_design.py b/wisdem/orbit/phases/design/semi_submersible_design.py index 29a14b552..1cbf3eac2 100644 --- a/wisdem/orbit/phases/design/semi_submersible_design.py +++ b/wisdem/orbit/phases/design/semi_submersible_design.py @@ -22,12 +22,16 @@ class SemiSubmersibleDesign(DesignPhase): "heave_plate_CR": "$/t (optional, default: 6250)", "secondary_steel_CR": "$/t (optional, default: 7250)", "towing_speed": "km/h (optional, default: 6)", - "design_time": "h, (optional, default: 0)", - "design_cost": "h, (optional, default: 0)", }, } - output_config = {} + output_config = { + "substructure": { + "mass": "t", + "unit_cost": "USD", + "towing_speed": "km/h", + } + } def __init__(self, config, **kwargs): """ @@ -49,11 +53,11 @@ def run(self): substructure = { "mass": self.substructure_mass, - "cost": self.substructure_cost, + "unit_cost": self.substructure_cost, "towing_speed": self._design.get("towing_speed", 6), } - self._outputs["semisubmersible"] = substructure + self._outputs["substructure"] = substructure @property def stiffened_column_mass(self): @@ -162,13 +166,6 @@ def total_substructure_mass(self): num = self.config["plant"]["num_turbines"] return num * self.substructure_mass - @property - def total_substructure_cost(self): - """Retruns cost of all substructures.""" - - num = self.config["plant"]["num_turbines"] - return num * self.substructure_cost - @property def design_result(self): """Returns the result of `self.run()`""" @@ -179,21 +176,11 @@ def design_result(self): return self._outputs @property - def total_phase_cost(self): + def total_cost(self): """Returns total phase cost in $USD.""" - _design = self.config.get("semisubmersible_design", {}) - design_cost = _design.get("design_cost", 0.0) - - return design_cost + self.total_substructure_cost - - @property - def total_phase_time(self): - """Returns total phase time in hours.""" - - _design = self.config.get("semisubmersible_design", {}) - phase_time = _design.get("design_time", 0.0) - return phase_time + num = self.config["plant"]["num_turbines"] + return num * self.substructure_cost @property def detailed_output(self): diff --git a/wisdem/orbit/phases/design/spar_design.py b/wisdem/orbit/phases/design/spar_design.py index 50b2b01f7..36e26aafd 100644 --- a/wisdem/orbit/phases/design/spar_design.py +++ b/wisdem/orbit/phases/design/spar_design.py @@ -7,7 +7,6 @@ from numpy import exp, log - from wisdem.orbit.phases.design import DesignPhase @@ -24,12 +23,17 @@ class SparDesign(DesignPhase): "ballast_material_CR": "$/t (optional, default: 100)", "secondary_steel_CR": "$/t (optional, default: 7250)", "towing_speed": "km/h (optional, default: 6)", - "design_time": "h, (optional, default: 0)", - "design_cost": "h, (optional, default: 0)", }, } - output_config = {} + output_config = { + "substructure": { + "mass": "t", + "ballasted_mass": "t", + "unit_cost": "USD", + "towing_speed": "km/h", + } + } def __init__(self, config, **kwargs): """ @@ -52,11 +56,11 @@ def run(self): substructure = { "mass": self.unballasted_mass, "ballasted_mass": self.ballasted_mass, - "cost": self.substructure_cost, + "unit_cost": self.substructure_cost, "towing_speed": self._design.get("towing_speed", 6), } - self._outputs["spar"] = substructure + self._outputs["substructure"] = substructure @property def stiffened_column_mass(self): @@ -181,29 +185,12 @@ def detailed_output(self): return _outputs @property - def total_substructure_cost(self): - """Retruns cost of all substructures.""" + def total_cost(self): + """Returns total phase cost in $USD.""" num = self.config["plant"]["num_turbines"] return num * self.substructure_cost - @property - def total_phase_cost(self): - """Returns total phase cost in $USD.""" - - _design = self.config.get("spar_design", {}) - design_cost = _design.get("design_cost", 0.0) - - return design_cost + self.total_substructure_cost - - @property - def total_phase_time(self): - """Returns total phase time in hours.""" - - _design = self.config.get("spar_design", {}) - phase_time = _design.get("design_time", 0.0) - return phase_time - @property def design_result(self): """Returns the result of `self.run()`""" diff --git a/wisdem/orbit/phases/install/cable_install/array.py b/wisdem/orbit/phases/install/cable_install/array.py index c75b20f0a..c4fbe8c2e 100644 --- a/wisdem/orbit/phases/install/cable_install/array.py +++ b/wisdem/orbit/phases/install/cable_install/array.py @@ -10,7 +10,6 @@ import numpy as np from marmot import process - from wisdem.orbit.core import Vessel from wisdem.orbit.core.logic import position_onsite from wisdem.orbit.phases.install import InstallPhase @@ -42,6 +41,7 @@ class ArrayCableInstallation(InstallPhase): "array_cable_trench_vessel": "str (optional)", "site": {"distance": "km", "depth": "m"}, "array_system": { + "system_cost": "USD", "num_strings": "int (optional, default: 10)", "free_cable_length": "km (optional, default: 'depth')", "cables": { @@ -105,6 +105,12 @@ def setup_simulation(self, **kwargs): **kwargs, ) + @property + def system_capex(self): + """Returns total procurement cost of the array system.""" + + return self.config["array_system"]["system_cost"] + def initialize_installation_vessel(self): """Creates the array cable installation vessel.""" diff --git a/wisdem/orbit/phases/install/cable_install/common.py b/wisdem/orbit/phases/install/cable_install/common.py index c6ca15aff..bf3dd2fb6 100644 --- a/wisdem/orbit/phases/install/cable_install/common.py +++ b/wisdem/orbit/phases/install/cable_install/common.py @@ -7,7 +7,6 @@ from marmot import process - from wisdem.orbit.core.logic import position_onsite from wisdem.orbit.core.defaults import process_times as pt diff --git a/wisdem/orbit/phases/install/cable_install/export.py b/wisdem/orbit/phases/install/cable_install/export.py index b6a1f9195..c1339e1bd 100644 --- a/wisdem/orbit/phases/install/cable_install/export.py +++ b/wisdem/orbit/phases/install/cable_install/export.py @@ -10,7 +10,6 @@ from math import ceil from marmot import process - from wisdem.orbit.core import Vessel from wisdem.orbit.core.logic import position_onsite from wisdem.orbit.phases.install import InstallPhase @@ -114,6 +113,12 @@ def setup_simulation(self, **kwargs): **kwargs, ) + @property + def system_capex(self): + """Returns total procurement cost of the array system.""" + + return self.config["export_system"]["system_cost"] + def extract_distances(self): """Extracts distances from input configuration or default values.""" diff --git a/wisdem/orbit/phases/install/install_phase.py b/wisdem/orbit/phases/install/install_phase.py index 281c5f80d..a022e204d 100644 --- a/wisdem/orbit/phases/install/install_phase.py +++ b/wisdem/orbit/phases/install/install_phase.py @@ -11,7 +11,7 @@ import numpy as np import simpy - +import pandas as pd from wisdem.orbit.core import Port, Environment from wisdem.orbit.phases import BasePhase from wisdem.orbit.core.defaults import common_costs @@ -26,7 +26,7 @@ def __init__(self, weather, **kwargs): Parameters ---------- - weather : np.ndarray + weather : pd.DataFrame | np.ndarray Weather profile at site. """ @@ -43,6 +43,9 @@ def initialize_environment(self, weather, **kwargs): Weather profile at site. """ + if isinstance(weather, pd.DataFrame): + weather = weather.to_records() + env_name = kwargs.get("env_name", "Environment") self.env = Environment(name=env_name, state=weather, **kwargs) @@ -110,16 +113,10 @@ def port_costs(self): return months * rate @property - def total_phase_cost(self): - """Returns total phase cost in $USD.""" - - return self.action_costs + self.port_costs - - @property - def action_costs(self): - """Returns sum cost of all actions.""" + def installation_capex(self): + """Returns sum of all installation costs in `self.env.actions`.""" - return np.nansum([a["cost"] for a in self.env.actions]) + return np.nansum([a["cost"] for a in self.env.actions]) + self.port_costs @property def total_phase_time(self): diff --git a/wisdem/orbit/phases/install/monopile_install/common.py b/wisdem/orbit/phases/install/monopile_install/common.py index c6aefea2f..85c2b62dd 100644 --- a/wisdem/orbit/phases/install/monopile_install/common.py +++ b/wisdem/orbit/phases/install/monopile_install/common.py @@ -7,8 +7,8 @@ from marmot import process - from wisdem.orbit.core import Cargo +from wisdem.orbit.core.logic import jackdown_if_required from wisdem.orbit.core.defaults import process_times as pt @@ -122,7 +122,7 @@ def lower_monopile(vessel, **kwargs): depth = kwargs.get("site_depth", None) rate = vessel.crane.crane_rate(**kwargs) - height = (vessel.jacksys.air_gap + vessel.jacksys.leg_pen + depth) / rate + height = (depth + 10) / rate # Assumed 10m deck height added to site depth lower_time = height / rate yield vessel.task( @@ -182,10 +182,7 @@ def lower_transition_piece(vessel, **kwargs): vessel.task representing time to "Lower Transition Piece". """ - rate = vessel.crane.crane_rate(**kwargs) - lower_time = vessel.jacksys.air_gap / rate - - yield vessel.task("Lower TP", lower_time, constraints=vessel.operational_limits, **kwargs) + yield vessel.task("Lower TP", 1, constraints=vessel.operational_limits, **kwargs) @process @@ -298,7 +295,7 @@ def install_transition_piece(vessel, tp, **kwargs): - Reequip crane, ``vessel.crane.reequip()`` - Lower transition piece, ``tasks.lower_transition_piece()`` - Install connection, see below. - - Jackdown, ``vessel.jacksys.jacking_time()`` + - Jackdown, ``vessel.jacksys.jacking_time()`` (if a jackup vessel) The transition piece can either be installed with a bolted or a grouted connection. By default, ORBIT uses the bolted connection with the following @@ -322,9 +319,6 @@ def install_transition_piece(vessel, tp, **kwargs): connection = kwargs.get("tp_connection_type", "bolted") reequip_time = vessel.crane.reequip(**kwargs) - site_depth = kwargs.get("site_depth", None) - extension = kwargs.get("extension", site_depth + 10) - jackdown_time = vessel.jacksys.jacking_time(extension, site_depth) yield vessel.task( "Crane Reequip", @@ -347,4 +341,4 @@ def install_transition_piece(vessel, tp, **kwargs): f"Transition piece connection type '{connection}'" "not recognized. Must be 'bolted' or 'grouted'." ) - yield vessel.task("Jackdown", jackdown_time, constraints=vessel.transit_limits, **kwargs) + yield jackdown_if_required(vessel, **kwargs) diff --git a/wisdem/orbit/phases/install/monopile_install/standard.py b/wisdem/orbit/phases/install/monopile_install/standard.py index be83f8803..e0092b382 100644 --- a/wisdem/orbit/phases/install/monopile_install/standard.py +++ b/wisdem/orbit/phases/install/monopile_install/standard.py @@ -8,23 +8,12 @@ import simpy from marmot import process - from wisdem.orbit.core import Vessel -from wisdem.orbit.core.logic import ( - shuttle_items_to_queue, - prep_for_site_operations, - get_list_of_items_from_port, -) +from wisdem.orbit.core.logic import shuttle_items_to_queue, prep_for_site_operations, get_list_of_items_from_port from wisdem.orbit.phases.install import InstallPhase from wisdem.orbit.core.exceptions import ItemNotFound -from .common import ( - Monopile, - TransitionPiece, - upend_monopile, - install_monopile, - install_transition_piece, -) +from .common import Monopile, TransitionPiece, upend_monopile, install_monopile, install_transition_piece class MonopileInstallation(InstallPhase): @@ -56,8 +45,13 @@ class MonopileInstallation(InstallPhase): "diameter": "m", "deck_space": "m2", "mass": "t", + "unit_cost": "USD", + }, + "transition_piece": { + "deck_space": "m2", + "mass": "t", + "unit_cost": "USD", }, - "transition_piece": {"deck_space": "m2", "mass": "t"}, } def __init__(self, config, weather=None, **kwargs): @@ -83,6 +77,14 @@ def __init__(self, config, weather=None, **kwargs): self.initialize_monopiles() self.setup_simulation(**kwargs) + @property + def system_capex(self): + """Returns procurement cost of the substructures.""" + + return (self.config["monopile"]["unit_cost"] + self.config["transition_piece"]["unit_cost"]) * self.config[ + "plant" + ]["num_turbines"] + def setup_simulation(self, **kwargs): """ Sets up simulation infrastructure, routing to specific methods dependent diff --git a/wisdem/orbit/phases/install/mooring_install/mooring.py b/wisdem/orbit/phases/install/mooring_install/mooring.py index 0a7e51466..c8767add9 100644 --- a/wisdem/orbit/phases/install/mooring_install/mooring.py +++ b/wisdem/orbit/phases/install/mooring_install/mooring.py @@ -7,7 +7,6 @@ from marmot import process - from wisdem.orbit.core import Cargo, Vessel from wisdem.orbit.core.logic import position_onsite, get_list_of_items_from_port from wisdem.orbit.core.defaults import process_times as pt @@ -28,7 +27,9 @@ class MooringSystemInstallation(InstallPhase): "mooring_system": { "num_lines": "int", "line_mass": "t", + "line_cost": "USD", "anchor_mass": "t", + "anchor_cost": "USD", "anchor_type": "str (optional, default: 'Suction Pile')", }, } @@ -67,14 +68,17 @@ def setup_simulation(self, **kwargs): depth = self.config["site"]["depth"] distance = self.config["site"]["distance"] - install_mooring_systems( - self.vessel, - self.port, - distance, - depth, - self.number_systems, - **kwargs, - ) + self.num_lines = self.config["mooring_system"]["num_lines"] + self.line_cost = self.config["mooring_system"]["line_cost"] + self.anchor_cost = self.config["mooring_system"]["anchor_cost"] + + install_mooring_systems(self.vessel, self.port, distance, depth, self.num_systems, **kwargs) + + @property + def system_capex(self): + """Returns total procurement cost of all mooring systems.""" + + return self.num_systems * self.num_lines * (self.line_cost + self.anchor_cost) def initialize_installation_vessel(self): """Initializes the mooring system installation vessel.""" @@ -94,9 +98,9 @@ def initialize_components(self): """Initializes the Cargo components at port.""" system = MooringSystem(**self.config["mooring_system"]) - self.number_systems = self.config["plant"]["num_turbines"] + self.num_systems = self.config["plant"]["num_turbines"] - for _ in range(self.number_systems): + for _ in range(self.num_systems): self.port.put(system) @property diff --git a/wisdem/orbit/phases/install/oss_install/common.py b/wisdem/orbit/phases/install/oss_install/common.py index c0246d309..9c27abb26 100644 --- a/wisdem/orbit/phases/install/oss_install/common.py +++ b/wisdem/orbit/phases/install/oss_install/common.py @@ -7,8 +7,8 @@ from marmot import process - from wisdem.orbit.core import Cargo +from wisdem.orbit.core.logic import stabilize, jackdown_if_required from wisdem.orbit.core.defaults import process_times as pt from wisdem.orbit.phases.install.monopile_install.common import ( bolt_transition_piece, @@ -120,9 +120,6 @@ def install_topside(vessel, topside, **kwargs): connection = kwargs.get("topside_connection_type", "bolted") reequip_time = vessel.crane.reequip(**kwargs) - site_depth = kwargs.get("site_depth", None) - extension = kwargs.get("extension", site_depth + 10) - jackdown_time = vessel.jacksys.jacking_time(extension, site_depth) yield vessel.task( "Crane Reequip", @@ -146,4 +143,4 @@ def install_topside(vessel, topside, **kwargs): f"Transition piece connection type '{connection}'" "not recognized. Must be 'bolted' or 'grouted'." ) - yield vessel.task("Jackdown", jackdown_time, constraints=vessel.transit_limits, **kwargs) + yield jackdown_if_required(vessel, **kwargs) diff --git a/wisdem/orbit/phases/install/oss_install/standard.py b/wisdem/orbit/phases/install/oss_install/standard.py index 9a383e331..c8a9e9cdb 100644 --- a/wisdem/orbit/phases/install/oss_install/standard.py +++ b/wisdem/orbit/phases/install/oss_install/standard.py @@ -8,15 +8,10 @@ import simpy from marmot import process - from wisdem.orbit.core import Vessel from wisdem.orbit.core.logic import shuttle_items_to_queue, prep_for_site_operations from wisdem.orbit.phases.install import InstallPhase -from wisdem.orbit.phases.install.monopile_install.common import ( - Monopile, - upend_monopile, - install_monopile, -) +from wisdem.orbit.phases.install.monopile_install.common import Monopile, upend_monopile, install_monopile from .common import Topside, install_topside @@ -33,7 +28,7 @@ class OffshoreSubstationInstallation(InstallPhase): expected_config = { "num_substations": "int", "oss_install_vessel": "dict | str", - "num_feeders": "int", + "num_feeders": "int (optional, default: 1)", "feeder": "dict | str", "site": {"distance": "km", "depth": "m"}, "port": { @@ -41,12 +36,17 @@ class OffshoreSubstationInstallation(InstallPhase): "monthly_rate": "USD/mo (optional)", "name": "str (optional)", }, - "offshore_substation_topside": {"deck_space": "m2", "mass": "t"}, + "offshore_substation_topside": { + "deck_space": "m2", + "mass": "t", + "unit_cost": "USD", + }, "offshore_substation_substructure": { "type": "Monopile", "deck_space": "m2", "mass": "t", "length": "m", + "unit_cost": "USD", }, } @@ -70,6 +70,15 @@ def __init__(self, config, weather=None, **kwargs): self.initialize_port() self.setup_simulation(**kwargs) + @property + def system_capex(self): + """Returns procurement CapEx of the offshore substations.""" + + return self.config["num_substations"] * ( + self.config["offshore_substation_topside"]["unit_cost"] + + self.config["offshore_substation_substructure"]["unit_cost"] + ) + def setup_simulation(self, **kwargs): """ Initializes required objects for simulation. @@ -142,7 +151,7 @@ def initialize_feeders(self): Initializes feeder barge objects. """ - number = self.config.get("num_feeders", None) + number = self.config.get("num_feeders", 1) feeder_specs = self.config.get("feeder", None) self.feeders = [] diff --git a/wisdem/orbit/phases/install/quayside_assembly_tow/gravity_base.py b/wisdem/orbit/phases/install/quayside_assembly_tow/gravity_base.py index ad3a16fa6..9d6438e16 100644 --- a/wisdem/orbit/phases/install/quayside_assembly_tow/gravity_base.py +++ b/wisdem/orbit/phases/install/quayside_assembly_tow/gravity_base.py @@ -8,7 +8,6 @@ import simpy from marmot import le, process - from wisdem.orbit.core import Vessel, WetStorage from wisdem.orbit.phases.install import InstallPhase @@ -35,6 +34,7 @@ class GravityBasedInstallation(InstallPhase): "substructure": { "takt_time": "int | float (optional, default: 0)", "towing_speed": "int | float (optional, default: 6 km/h)", + "unit_cost": "USD", }, "site": {"depth": "m", "distance": "km"}, "plant": {"num_turbines": "int"}, @@ -85,6 +85,12 @@ def setup_simulation(self, **kwargs): self.initialize_towing_groups() self.initialize_support_vessel() + @property + def system_capex(self): + """Returns total procurement cost of the substructures.""" + + return self.num_turbines * self.config["substructure"]["unit_cost"] + def initialize_substructure_production(self): """ Initializes the production of substructures at port. The number of diff --git a/wisdem/orbit/phases/install/quayside_assembly_tow/moored.py b/wisdem/orbit/phases/install/quayside_assembly_tow/moored.py index c090a797b..ad952cfef 100644 --- a/wisdem/orbit/phases/install/quayside_assembly_tow/moored.py +++ b/wisdem/orbit/phases/install/quayside_assembly_tow/moored.py @@ -8,7 +8,6 @@ import simpy from marmot import le, process - from wisdem.orbit.core import Vessel, WetStorage from wisdem.orbit.phases.install import InstallPhase @@ -35,6 +34,7 @@ class MooredSubInstallation(InstallPhase): "substructure": { "takt_time": "int | float (optional, default: 0)", "towing_speed": "int | float (optional, default: 6 km/h)", + "unit_cost": "USD", }, "site": {"depth": "m", "distance": "km"}, "plant": {"num_turbines": "int"}, @@ -85,6 +85,12 @@ def setup_simulation(self, **kwargs): self.initialize_towing_groups() self.initialize_support_vessel() + @property + def system_capex(self): + """Returns total procurement cost of the substructures.""" + + return self.num_turbines * self.config["substructure"]["unit_cost"] + def initialize_substructure_production(self): """ Initializes the production of substructures at port. The number of diff --git a/wisdem/orbit/phases/install/scour_protection_install/standard.py b/wisdem/orbit/phases/install/scour_protection_install/standard.py index 611254304..b1a6ac41c 100644 --- a/wisdem/orbit/phases/install/scour_protection_install/standard.py +++ b/wisdem/orbit/phases/install/scour_protection_install/standard.py @@ -10,7 +10,6 @@ import simpy from marmot import process - from wisdem.orbit.core import Vessel from wisdem.orbit.core.defaults import process_times as pt from wisdem.orbit.phases.install import InstallPhase @@ -34,7 +33,10 @@ class ScourProtectionInstallation(InstallPhase): "monthly_rate": "USD/mo (optional)", "name": "str (optional)", }, - "scour_protection": {"tons_per_substructure": "float"}, + "scour_protection": { + "tonnes_per_substructure": "t", + "cost_per_tonne": "USD/t", + }, } phase = "Scour Protection Installation" @@ -77,7 +79,9 @@ def setup_simulation(self, **kwargs): if turbine_distance is None: turbine_distance = rotor_diameter * self.config["plant"]["turbine_spacing"] / 1000.0 - self.tons_per_substructure = ceil(self.config["scour_protection"]["tons_per_substructure"]) + self.tonnes_per_substructure = ceil(self.config["scour_protection"]["tonnes_per_substructure"]) + + self.cost_per_tonne = self.config["scour_protection"]["cost_per_tonne"] install_scour_protection( self.spi_vessel, @@ -85,10 +89,16 @@ def setup_simulation(self, **kwargs): site_distance=site_distance, turbines=self.num_turbines, turbine_distance=turbine_distance, - tons_per_substructure=self.tons_per_substructure, + tonnes_per_substructure=self.tonnes_per_substructure, **kwargs, ) + @property + def system_capex(self): + """Returns total procurement cost of scour protection material.""" + + return self.num_turbines * self.tonnes_per_substructure * self.cost_per_tonne + def initialize_port(self): """ Initializes a Port object with a simpy.Container of scour protection @@ -129,7 +139,7 @@ def install_scour_protection( site_distance, turbines, turbine_distance, - tons_per_substructure, + tonnes_per_substructure, **kwargs, ): """ @@ -148,8 +158,8 @@ def install_scour_protection( For now this assumes it traverses an edge and not a diagonal. turbines_to_install : int Number of turbines where scouring protection must be installed. - tons_per_substructure : int - Number of tons required to be installed at each substation + tonnes_per_substructure : int + Number of tonnes required to be installed at each substation """ while turbines > 0: @@ -163,13 +173,13 @@ def install_scour_protection( vessel.at_site = True elif vessel.at_site: - if vessel.rock_storage.level >= tons_per_substructure: + if vessel.rock_storage.level >= tonnes_per_substructure: # Drop scour protection material - yield drop_material(vessel, tons_per_substructure, **kwargs) + yield drop_material(vessel, tonnes_per_substructure, **kwargs) turbines -= 1 # Transit to another turbine - if vessel.rock_storage.level >= tons_per_substructure and turbines > 0: + if vessel.rock_storage.level >= tonnes_per_substructure and turbines > 0: yield vessel.transit(turbine_distance) else: diff --git a/wisdem/orbit/phases/install/turbine_install/standard.py b/wisdem/orbit/phases/install/turbine_install/standard.py index 412955651..140c75cdb 100644 --- a/wisdem/orbit/phases/install/turbine_install/standard.py +++ b/wisdem/orbit/phases/install/turbine_install/standard.py @@ -12,9 +12,9 @@ import numpy as np import simpy from marmot import process - from wisdem.orbit.core import Vessel from wisdem.orbit.core.logic import ( + jackdown_if_required, shuttle_items_to_queue, prep_for_site_operations, get_list_of_items_from_port, @@ -22,14 +22,7 @@ from wisdem.orbit.phases.install import InstallPhase from wisdem.orbit.core.exceptions import ItemNotFound -from .common import ( - Blade, - Nacelle, - TowerSection, - install_nacelle, - install_tower_section, - install_turbine_blade, -) +from .common import Blade, Nacelle, TowerSection, install_nacelle, install_tower_section, install_turbine_blade class TurbineInstallation(InstallPhase): @@ -90,6 +83,12 @@ def __init__(self, config, weather=None, **kwargs): self.initialize_turbines() self.setup_simulation(**kwargs) + @property + def system_capex(self): + """Returns 0 as turbine capex is handled at in ProjectManager.""" + + return 0 + def setup_simulation(self, **kwargs): """ Sets up simulation infrastructure, routing to specific methods dependent @@ -333,19 +332,8 @@ def solo_install_turbines(vessel, port, distance, turbines, tower_sections, num_ yield install_turbine_blade(vessel, blade, **kwargs) - # Jack-down - site_depth = kwargs.get("site_depth", None) - extension = kwargs.get("extension", site_depth + 10) - jackdown_time = vessel.jacksys.jacking_time(extension, site_depth) - - yield vessel.task( - "Jackdown", - jackdown_time, - constraints=vessel.transit_limits, - ) - + yield jackdown_if_required(vessel, **kwargs) vessel.submit_debug_log(progress="Turbine") - n += 1 else: @@ -421,15 +409,8 @@ def install_turbine_components_from_queue(wtiv, queue, distance, turbines, tower yield install_turbine_blade(wtiv, blade, **kwargs) - # Jack-down - site_depth = kwargs.get("site_depth", None) - extension = kwargs.get("extension", site_depth + 10) - jackdown_time = wtiv.jacksys.jacking_time(extension, site_depth) - - yield wtiv.task("Jackdown", jackdown_time, constraints=wtiv.transit_limits) - + yield jackdown_if_required(wtiv, **kwargs) wtiv.submit_debug_log(progress="Turbine") - n += 1 else: diff --git a/wisdem/postprocessing/compare_designs.py b/wisdem/postprocessing/compare_designs.py index 53badcc1c..a8dc8aa4b 100644 --- a/wisdem/postprocessing/compare_designs.py +++ b/wisdem/postprocessing/compare_designs.py @@ -65,9 +65,7 @@ def create_all_plots( axtw.plot( s_opt_twist, - np.array(analysis_options["design_variables"]["blade"]["aero_shape"]["twist"]["lower_bound"]) - * 180.0 - / np.pi, + np.array(analysis_options["design_variables"]["blade"]["aero_shape"]["twist"]["lower_bound"]) * 180.0 / np.pi, ":o", color=colors[idx + 1], markersize=3, @@ -75,9 +73,7 @@ def create_all_plots( ) axtw.plot( s_opt_twist, - np.array(analysis_options["design_variables"]["blade"]["aero_shape"]["twist"]["upper_bound"]) - * 180.0 - / np.pi, + np.array(analysis_options["design_variables"]["blade"]["aero_shape"]["twist"]["upper_bound"]) * 180.0 / np.pi, ":o", color=colors[idx + 1], markersize=3, @@ -149,10 +145,9 @@ def create_all_plots( for idx, (yaml_data, label) in enumerate(zip(list_of_sims, list_of_labels)): n_layers = len(yaml_data["blade.internal_structure_2d_fem.layer_thickness"][:, 0]) - spar_ss_name = "Spar_Cap_SS" - spar_ps_name = "Spar_Cap_PS" for i in range(n_layers): - if modeling_options["RotorSE"]["spar_cap_ss"] == spar_ss_name: + layer_name = modeling_options["WISDEM"]["RotorSE"]["layer_name"][i] + if modeling_options["WISDEM"]["RotorSE"]["spar_cap_ss"] == layer_name: axsc.plot( yaml_data["blade.outer_shape_bem.s"], yaml_data["blade.internal_structure_2d_fem.layer_thickness"][i, :] * 1.0e3, @@ -170,7 +165,8 @@ def create_all_plots( axsc.plot(s_opt_sc, sc_opt, "o", color=colors[idx], markersize=3) for i in range(n_layers): - if modeling_options["RotorSE"]["spar_cap_ss"] == spar_ss_name: + layer_name = modeling_options["WISDEM"]["RotorSE"]["layer_name"][i] + if modeling_options["WISDEM"]["RotorSE"]["spar_cap_ss"] == layer_name: sc_init = np.interp( s_opt_sc, list_of_sims[0]["blade.outer_shape_bem.s"], @@ -381,12 +377,12 @@ def simple_plot_results(x_axis_label, y_axis_label, x_axis_data_name, y_axis_dat def print_results_to_screen(list_of_sims, list_of_labels, values_to_print): - list_of_labels = [] + list_of_augmented_labels = [] max_label_length = 1 for label in list_of_labels: - list_of_labels.append(f"{label:15.15}") + list_of_augmented_labels.append(f"{label:15.15}") - case_headers = "| Data name | " + " | ".join(list_of_labels) + " | Units |" + case_headers = "| Data name | " + " | ".join(list_of_augmented_labels) + " | Units |" # Header describing what we are printing: title_string = "Comparison between WISDEM results from yaml files" spacing = (len(case_headers) - len(title_string) - 2) // 2 diff --git a/wisdem/rotorse/rail_transport.py b/wisdem/rotorse/rail_transport.py index 3e9980687..050f64cab 100644 --- a/wisdem/rotorse/rail_transport.py +++ b/wisdem/rotorse/rail_transport.py @@ -1,16 +1,12 @@ import numpy as np import scipy.constants as spc -from scipy.optimize import brentq, minimize_scalar, minimize -from openmdao.api import ExplicitComponent -import wisdem.pyframe3dd.pyframe3dd as pyframe3dd import wisdem.commonse.utilities as util +import wisdem.pyframe3dd.pyframe3dd as pyframe3dd +from openmdao.api import ExplicitComponent +from scipy.optimize import brentq, minimize, minimize_scalar from wisdem.commonse.constants import gravity -def find_nearest(array, value): - return (np.abs(array - value)).argmin() - - # This isn't used, but keeping around the code for now def enforce_length(x, y, z, L0): r0 = np.sum(L0) @@ -33,7 +29,7 @@ def initialize(self): self.options.declare("modeling_options") def setup(self): - rotorse_options = self.options["modeling_options"]["RotorSE"] + rotorse_options = self.options["modeling_options"]["WISDEM"]["RotorSE"] self.n_span = n_span = rotorse_options["n_span"] self.n_xy = n_xy = rotorse_options["n_xy"] # Number of coordinate points to describe the airfoil geometry @@ -90,12 +86,6 @@ def setup(self): val=np.zeros((n_span, n_xy, 2)), desc="3D array of the non-dimensional x and y airfoil coordinates of the airfoils interpolated along span for n_span stations. The leading edge is place at x=0 and y=0.", ) - self.add_input( - "coord_xy_dim", - val=np.zeros((n_span, n_xy, 2)), - units="m", - desc="3D array of the dimensional x and y airfoil coordinates of the airfoils interpolated along span for n_span stations. The origin is placed at the pitch axis.", - ) # Inputs - Distributed beam properties self.add_input("A", val=np.zeros(n_span), units="m**2", desc="airfoil cross section material area") @@ -562,7 +552,7 @@ def run_hcurve(FrIn, optFlag=True): # Compute node radii starting points to determine if within clearance boundary r_blade = np.sqrt(nodes.y**2 + nodes.z**2) - + # Initialize frame3dd object blade = pyframe3dd.Frame(nodes, reactions, elements, options) @@ -573,7 +563,7 @@ def run_hcurve(FrIn, optFlag=True): node_dr = np.minimum(r_envelopeV_outer - r_blade, 0) node_dy = node_dr*np.cos(arcsV) node_dz = node_dr*np.sin(arcsV) - + # Load case 1: gravity + hill dx = dM = np.zeros(ireact.size) load1 = pyframe3dd.StaticLoadCase(gx, gy, gz) @@ -586,7 +576,7 @@ def run_hcurve(FrIn, optFlag=True): node_dr = np.maximum(r_envelopeV_inner - r_blade, 0) node_dy = node_dr*np.cos(arcsV) node_dz = node_dr*np.sin(arcsV) - + # Load case 2: gravity + sag load2 = pyframe3dd.StaticLoadCase(gx, gy, gz) load2.changePrescribedDisplacements(ireact+1, dx, node_dy[ireact], node_dz[ireact], dM, dM, dM) @@ -616,7 +606,7 @@ def run_hcurve(FrIn, optFlag=True): # compute strain at the two points strainLE[:,k] = -(M1/EI11*le2 - M2/EI22*le1 + Fz/EA) strainTE[:,k] = -(M1/EI11*te2 - M2/EI22*te1 + Fz/EA) - + # Find best points for middle reaction and formulate as constraints constr_derailV_8axle = (np.abs(RF_derailV.T) / (0.5 * mass_car_8axle * gravity)) / max_LV constr_derailV_4axle = (np.abs(RF_derailV.T) / (0.5 * mass_car_4axle * gravity)) / max_LV diff --git a/wisdem/rotorse/rotor_cost.py b/wisdem/rotorse/rotor_cost.py index 25d9d79f7..92df76bef 100644 --- a/wisdem/rotorse/rotor_cost.py +++ b/wisdem/rotorse/rotor_cost.py @@ -1,9 +1,10 @@ -import numpy as np -import time import os +import time + +import numpy as np import matplotlib.pyplot as plt -from scipy.optimize import brentq from openmdao.api import ExplicitComponent +from scipy.optimize import brentq ### USING OLD NUMPY SRC FOR PMT-FUNCTION INSTEAD OF SWITCHING TO ANNOYING NUMPY-FINANCIAL _when_to_num = {"end": 0, "begin": 1, "e": 0, "b": 1, 0: 0, 1: 1, "beginning": 1, "start": 1, "finish": 0} @@ -4484,7 +4485,7 @@ def initialize(self): def setup(self): wt_init_options = self.options["wt_init_options"] - rotorse_options = wt_init_options["RotorSE"] + rotorse_options = wt_init_options["WISDEM"]["RotorSE"] self.n_span = n_span = blade_init_options["n_span"] opt_options = self.options["opt_options"] self.costs_verbosity = opt_options["costs_verbosity"] diff --git a/wisdem/rotorse/rotor_elasticity.py b/wisdem/rotorse/rotor_elasticity.py index 3b0abf94d..309a16ff7 100644 --- a/wisdem/rotorse/rotor_elasticity.py +++ b/wisdem/rotorse/rotor_elasticity.py @@ -1,11 +1,11 @@ import copy + import numpy as np -from scipy.optimize import curve_fit +from openmdao.api import Group, ExplicitComponent from scipy.interpolate import PchipInterpolator -from openmdao.api import ExplicitComponent, Group -from wisdem.commonse.utilities import rotate, arc_length -from wisdem.rotorse.precomp import PreComp, Profile, Orthotropic2DMaterial, CompositeSection +from wisdem.rotorse.precomp import PreComp, Profile, CompositeSection, Orthotropic2DMaterial from wisdem.commonse.csystem import DirectionVector +from wisdem.commonse.utilities import rotate, arc_length from wisdem.rotorse.rotor_cost import blade_cost_model from wisdem.rotorse.rail_transport import RailTransport @@ -17,7 +17,7 @@ def initialize(self): self.options.declare("opt_options") def setup(self): - rotorse_options = self.options["modeling_options"]["RotorSE"] + rotorse_options = self.options["modeling_options"]["WISDEM"]["RotorSE"] self.n_span = n_span = rotorse_options["n_span"] self.n_webs = n_webs = rotorse_options["n_webs"] self.n_layers = n_layers = rotorse_options["n_layers"] @@ -139,6 +139,10 @@ def setup(self): desc="1D array of the density of the materials. For composites, this is the density of the laminate.", ) + self.add_input("joint_position", val=0.0, desc="Spanwise position of the segmentation joint.",) + self.add_input("joint_mass", val=0.0, desc="Mass of the joint.") + self.add_input("joint_cost", val=0.0, units='USD', desc="Cost of the joint.") + # Outputs - Distributed beam properties self.add_output("z", val=np.zeros(n_span), units="m", desc="locations of properties along beam") self.add_output("A", val=np.zeros(n_span), units="m**2", desc="cross sectional area") @@ -500,8 +504,8 @@ def web_stacking( return sec ############################## - layer_name = self.options["modeling_options"]["RotorSE"]["layer_name"] - layer_mat = self.options["modeling_options"]["RotorSE"]["layer_mat"] + layer_name = self.options["modeling_options"]["WISDEM"]["RotorSE"]["layer_name"] + layer_mat = self.options["modeling_options"]["WISDEM"]["RotorSE"]["layer_mat"] upperCS = [None] * self.n_span lowerCS = [None] * self.n_span @@ -836,6 +840,13 @@ def web_stacking( if j in lowerCS[i].mat_idx[sector_idx_strain_te_ps[i]]: outputs["te_ps_mats"][i, j] = 1.0 + if inputs["joint_mass"] > 0.: + s = (inputs["r"] - inputs["r"][0]) / (inputs["r"][-1] - inputs["r"][0]) + id_station = np.argmin(abs(inputs["joint_position"] - s)) + span = np.average([inputs["r"][id_station] - inputs["r"][id_station - 1], + inputs["r"][id_station + 1] - inputs["r"][id_station]]) + rhoA[id_station] += inputs["joint_mass"] / span + outputs["z"] = inputs["r"] outputs["EIxx"] = EIxx outputs["EIyy"] = EIyy @@ -954,6 +965,9 @@ def web_stacking( bcm.le_location = inputs["pitch_axis"] blade_cost, blade_mass = bcm.execute_blade_cost_model() + if inputs["joint_mass"] > 0.: + blade_cost += inputs["joint_cost"] + outputs["total_blade_cost"] = blade_cost outputs["total_blade_mass"] = blade_mass diff --git a/wisdem/rotorse/rotor_power.py b/wisdem/rotorse/rotor_power.py index 1f76bc169..8fe3a02c0 100644 --- a/wisdem/rotorse/rotor_power.py +++ b/wisdem/rotorse/rotor_power.py @@ -8,11 +8,13 @@ import numpy as np from openmdao.api import Group, ExplicitComponent from scipy.optimize import brentq, minimize, minimize_scalar -from wisdem.ccblade.ccblade import CCBlade, CCAirfoil from scipy.interpolate import PchipInterpolator +from wisdem.ccblade.ccblade import CCBlade, CCAirfoil from wisdem.commonse.utilities import smooth_abs, smooth_min, linspace_with_deriv from wisdem.commonse.distribution import RayleighCDF, WeibullWithMeanCDF +TOL = 1e-3 + class RotorPower(Group): def initialize(self): @@ -59,8 +61,11 @@ def setup(self): ], ) self.add_subsystem("gust", GustETM()) - self.add_subsystem("cdf", WeibullWithMeanCDF(nspline=modeling_options["RotorSE"]["n_pc_spline"])) - self.add_subsystem("aep", AEP(nspline=modeling_options["RotorSE"]["n_pc_spline"]), promotes=["AEP"]) + self.add_subsystem("cdf", WeibullWithMeanCDF(nspline=modeling_options["WISDEM"]["RotorSE"]["n_pc_spline"])) + self.add_subsystem("aep", AEP(nspline=modeling_options["WISDEM"]["RotorSE"]["n_pc_spline"]), promotes=["AEP"]) + + # Connections to the gust calculation + self.connect("powercurve.rated_V", "gust.V_hub") # Connections to the Weibull CDF self.connect("powercurve.V_spline", "cdf.x") @@ -126,15 +131,15 @@ def initialize(self): def setup(self): modeling_options = self.options["modeling_options"] - self.n_span = n_span = modeling_options["RotorSE"]["n_span"] - self.n_aoa = n_aoa = modeling_options["RotorSE"]["n_aoa"] # Number of angle of attacks - self.n_Re = n_Re = modeling_options["RotorSE"]["n_Re"] # Number of Reynolds, so far hard set at 1 - self.n_tab = n_tab = modeling_options["RotorSE"][ + self.n_span = n_span = modeling_options["WISDEM"]["RotorSE"]["n_span"] + self.n_aoa = n_aoa = modeling_options["WISDEM"]["RotorSE"]["n_aoa"] # Number of angle of attacks + self.n_Re = n_Re = modeling_options["WISDEM"]["RotorSE"]["n_Re"] # Number of Reynolds, so far hard set at 1 + self.n_tab = n_tab = modeling_options["WISDEM"]["RotorSE"][ "n_tab" ] # Number of tabulated data. For distributed aerodynamic control this could be > 1 - self.regulation_reg_III = modeling_options["RotorSE"]["regulation_reg_III"] - self.n_pc = modeling_options["RotorSE"]["n_pc"] - self.n_pc_spline = modeling_options["RotorSE"]["n_pc_spline"] + self.regulation_reg_III = modeling_options["WISDEM"]["RotorSE"]["regulation_reg_III"] + self.n_pc = modeling_options["WISDEM"]["RotorSE"]["n_pc"] + self.n_pc_spline = modeling_options["WISDEM"]["RotorSE"]["n_pc_spline"] # parameters self.add_input("v_min", val=0.0, units="m/s", desc="cut-in wind speed") @@ -420,7 +425,7 @@ def maximizePower(pitch, Uhub, Omega_rpm): lambda x: maximizePower(x, Uhub[i], Omega_rpm[i]), bounds=bnds, method="bounded", - options={"disp": False, "xatol": 1e-2, "maxiter": 40}, + options={"disp": False, "xatol": TOL, "maxiter": 40}, )["x"] # Find associated power @@ -468,7 +473,7 @@ def const_Urated(x): const = {} const["type"] = "eq" const["fun"] = const_Urated - params_rated = minimize(lambda x: x[1], x0, method="slsqp", bounds=bnds, constraints=const, tol=1e-3) + params_rated = minimize(lambda x: x[1], x0, method="slsqp", bounds=bnds, constraints=const, tol=TOL) if params_rated.success and not np.isnan(params_rated.x[1]): U_rated = params_rated.x[1] @@ -484,8 +489,8 @@ def const_Urated(x): lambda x: const_Urated([0.0, x]), Uhub[i - 1], Uhub[i + 1], - xtol=1e-4, - rtol=1e-5, + xtol=1e-1 * TOL, + rtol=1e-2 * TOL, maxiter=40, disp=False, ) @@ -494,7 +499,7 @@ def const_Urated(x): lambda x: np.abs(const_Urated([0.0, x])), bounds=[Uhub[i - 1], Uhub[i + 1]], method="bounded", - options={"disp": False, "xatol": 1e-3, "maxiter": 40}, + options={"disp": False, "xatol": TOL, "maxiter": 40}, )["x"] Omega_rated = min([U_rated * tsr / R_tip, Omega_max]) @@ -544,8 +549,8 @@ def rated_power_dist(pitch_i, Uhub_i, Omega_rpm_i): lambda x: rated_power_dist(x, Uhub[i], Omega_rpm[i]), pitch0, pitch0 + 10.0, - xtol=1e-4, - rtol=1e-5, + xtol=1e-1 * TOL, + rtol=1e-2 * TOL, maxiter=40, disp=False, ) @@ -554,7 +559,7 @@ def rated_power_dist(pitch_i, Uhub_i, Omega_rpm_i): lambda x: np.abs(rated_power_dist(x, Uhub[i], Omega_rpm[i])), bounds=[pitch0 - 5.0, pitch0 + 15.0], method="bounded", - options={"disp": False, "xatol": 1e-3, "maxiter": 40}, + options={"disp": False, "xatol": TOL, "maxiter": 40}, )["x"] myout, _ = self.ccblade.evaluate([Uhub[i]], [Omega_rpm[i]], [pitch[i]], coefficients=True) @@ -617,8 +622,8 @@ def initialize(self): def setup(self): modeling_options = self.options["modeling_options"] - self.n_pc = modeling_options["RotorSE"]["n_pc"] - self.n_pc_spline = modeling_options["RotorSE"]["n_pc_spline"] + self.n_pc = modeling_options["WISDEM"]["RotorSE"]["n_pc"] + self.n_pc_spline = modeling_options["WISDEM"]["RotorSE"]["n_pc_spline"] self.add_input("v_min", val=0.0, units="m/s", desc="cut-in wind speed") self.add_input("v_max", val=0.0, units="m/s", desc="cut-out wind speed") @@ -672,10 +677,10 @@ def initialize(self): def setup(self): modeling_options = self.options["modeling_options"] - self.n_span = n_span = modeling_options["RotorSE"]["n_span"] - self.n_aoa = n_aoa = modeling_options["RotorSE"]["n_aoa"] # Number of angle of attacks - self.n_Re = n_Re = modeling_options["RotorSE"]["n_Re"] # Number of Reynolds, so far hard set at 1 - self.n_tab = n_tab = modeling_options["RotorSE"][ + self.n_span = n_span = modeling_options["WISDEM"]["RotorSE"]["n_span"] + self.n_aoa = n_aoa = modeling_options["WISDEM"]["RotorSE"]["n_aoa"] # Number of angle of attacks + self.n_Re = n_Re = modeling_options["WISDEM"]["RotorSE"]["n_Re"] # Number of Reynolds, so far hard set at 1 + self.n_tab = n_tab = modeling_options["WISDEM"]["RotorSE"][ "n_tab" ] # Number of tabulated data. For distributed aerodynamic control this could be > 1 diff --git a/wisdem/rotorse/rotor_structure.py b/wisdem/rotorse/rotor_structure.py index 72ee0c740..5797c206e 100644 --- a/wisdem/rotorse/rotor_structure.py +++ b/wisdem/rotorse/rotor_structure.py @@ -15,7 +15,7 @@ def initialize(self): self.options.declare("modeling_options") def setup(self): - n_span = self.options["modeling_options"]["RotorSE"]["n_span"] + n_span = self.options["modeling_options"]["WISDEM"]["RotorSE"]["n_span"] # Inputs self.add_input("r", val=np.zeros(n_span), units="m", desc="location in blade z-coordinate") @@ -68,7 +68,7 @@ def initialize(self): self.options.declare("modeling_options") def setup(self): - n_span = self.options["modeling_options"]["RotorSE"]["n_span"] + n_span = self.options["modeling_options"]["WISDEM"]["RotorSE"]["n_span"] # Inputs self.add_input("r", val=np.zeros(n_span), units="m", desc="radial positions along blade going toward tip") @@ -142,7 +142,7 @@ def initialize(self): self.options.declare("pbeam", default=False) # Recover old pbeam c.s. and accuracy def setup(self): - rotorse_options = self.options["modeling_options"]["RotorSE"] + rotorse_options = self.options["modeling_options"]["WISDEM"]["RotorSE"] self.n_span = n_span = rotorse_options["n_span"] self.n_freq = n_freq = rotorse_options["n_freq"] @@ -604,7 +604,7 @@ def initialize(self): self.options.declare("opt_options") def setup(self): - rotorse_options = self.options["modeling_options"]["RotorSE"] + rotorse_options = self.options["modeling_options"]["WISDEM"]["RotorSE"] self.n_span = n_span = rotorse_options["n_span"] self.n_freq = n_freq = rotorse_options["n_freq"] n_freq2 = int(n_freq / 2) @@ -718,7 +718,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): threeP = discrete_inputs["blade_number"] * inputs["rated_Omega"] / 60.0 flap_f = inputs["flap_mode_freqs"] edge_f = inputs["edge_mode_freqs"] - gamma = self.options["modeling_options"]["RotorSE"]["gamma_freq"] + gamma = self.options["modeling_options"]["WISDEM"]["RotorSE"]["gamma_freq"] outputs["constr_flap_f_margin"] = np.array( [min([threeP - (2 - gamma) * f, gamma * f - threeP]) for f in flap_f] ).flatten() diff --git a/wisdem/test/test_ccblade/test_om_gradients.py b/wisdem/test/test_ccblade/test_om_gradients.py index a8e3194ec..4b72a7f95 100644 --- a/wisdem/test/test_ccblade/test_om_gradients.py +++ b/wisdem/test/test_ccblade/test_om_gradients.py @@ -44,11 +44,12 @@ def test_ccblade_loads(self): n_Re = npzfile["Re"].size modeling_options = {} - modeling_options["RotorSE"] = {} - modeling_options["RotorSE"]["n_span"] = n_span - modeling_options["RotorSE"]["n_aoa"] = n_aoa - modeling_options["RotorSE"]["n_Re"] = n_Re - modeling_options["RotorSE"]["n_tab"] = 1 + modeling_options["WISDEM"] = {} + modeling_options["WISDEM"]["RotorSE"] = {} + modeling_options["WISDEM"]["RotorSE"]["n_span"] = n_span + modeling_options["WISDEM"]["RotorSE"]["n_aoa"] = n_aoa + modeling_options["WISDEM"]["RotorSE"]["n_Re"] = n_Re + modeling_options["WISDEM"]["RotorSE"]["n_tab"] = 1 n_span, n_aoa, n_Re, n_tab = np.moveaxis(npzfile["cl"][:, :, :, np.newaxis], 0, 1).shape modeling_options["airfoils"] = {} @@ -134,11 +135,12 @@ def test_aero_hub_loads(self): n_Re = npzfile["Re"].size modeling_options = {} - modeling_options["RotorSE"] = {} - modeling_options["RotorSE"]["n_span"] = n_span - modeling_options["RotorSE"]["n_aoa"] = n_aoa - modeling_options["RotorSE"]["n_Re"] = n_Re - modeling_options["RotorSE"]["n_tab"] = 1 + modeling_options["WISDEM"] = {} + modeling_options["WISDEM"]["RotorSE"] = {} + modeling_options["WISDEM"]["RotorSE"]["n_span"] = n_span + modeling_options["WISDEM"]["RotorSE"]["n_aoa"] = n_aoa + modeling_options["WISDEM"]["RotorSE"]["n_Re"] = n_Re + modeling_options["WISDEM"]["RotorSE"]["n_tab"] = 1 modeling_options["assembly"] = {} modeling_options["assembly"]["number_of_blades"] = 3 @@ -224,11 +226,12 @@ def test_ccblade_twist(self): n_Re = npzfile["Re"].size modeling_options = {} - modeling_options["RotorSE"] = {} - modeling_options["RotorSE"]["n_span"] = n_span - modeling_options["RotorSE"]["n_aoa"] = n_aoa - modeling_options["RotorSE"]["n_Re"] = n_Re - modeling_options["RotorSE"]["n_tab"] = 1 + modeling_options["WISDEM"] = {} + modeling_options["WISDEM"]["RotorSE"] = {} + modeling_options["WISDEM"]["RotorSE"]["n_span"] = n_span + modeling_options["WISDEM"]["RotorSE"]["n_aoa"] = n_aoa + modeling_options["WISDEM"]["RotorSE"]["n_Re"] = n_Re + modeling_options["WISDEM"]["RotorSE"]["n_tab"] = 1 modeling_options["assembly"] = {} modeling_options["assembly"]["number_of_blades"] = 3 @@ -319,11 +322,12 @@ def test_ccblade_standalone(self): n_Re = npzfile["Re"].size modeling_options = {} - modeling_options["RotorSE"] = {} - modeling_options["RotorSE"]["n_span"] = n_span - modeling_options["RotorSE"]["n_aoa"] = n_aoa - modeling_options["RotorSE"]["n_Re"] = n_Re - modeling_options["RotorSE"]["n_tab"] = 1 + modeling_options["WISDEM"] = {} + modeling_options["WISDEM"]["RotorSE"] = {} + modeling_options["WISDEM"]["RotorSE"]["n_span"] = n_span + modeling_options["WISDEM"]["RotorSE"]["n_aoa"] = n_aoa + modeling_options["WISDEM"]["RotorSE"]["n_Re"] = n_Re + modeling_options["WISDEM"]["RotorSE"]["n_tab"] = 1 modeling_options["assembly"] = {} modeling_options["assembly"]["number_of_blades"] = 3 diff --git a/wisdem/test/test_commonse/test_utilities.py b/wisdem/test/test_commonse/test_utilities.py index aa25eee62..9cf69a04c 100644 --- a/wisdem/test/test_commonse/test_utilities.py +++ b/wisdem/test/test_commonse/test_utilities.py @@ -3,6 +3,7 @@ import numpy as np import numpy.testing as npt import wisdem.commonse.utilities as util +from scipy.optimize import curve_fit npts = 100 myones = np.ones((npts,)) @@ -26,12 +27,22 @@ def testSectionalInterp(self): def testModalCoefficients(self): # Test exact 6-deg polynomial p = np.random.random((7,)) + p[:2] = 0.0 x = np.linspace(0, 1) y = np.polynomial.polynomial.polyval(x, p) pp = util.get_modal_coefficients(x, y) npt.assert_almost_equal(p[2:] / p[2:].sum(), pp) + # Test more complex and ensure get the same answer as curve fit + p = np.random.random((10,)) + y = np.polynomial.polynomial.polyval(x, p) + + pp = util.get_modal_coefficients(x, y) + cc, _ = curve_fit(util.mode_fit, x, y) + cc /= cc.sum() + npt.assert_almost_equal(pp, cc, 4) + def testGetXYModes(self): r = np.linspace(0, 1, 20) n = 10 diff --git a/wisdem/test/test_drivetrainse/test_components.py b/wisdem/test/test_drivetrainse/test_components.py index 3673a6f1e..809d53153 100644 --- a/wisdem/test/test_drivetrainse/test_components.py +++ b/wisdem/test/test_drivetrainse/test_components.py @@ -97,15 +97,21 @@ def testGeneratorSimple(self): inputs["machine_rating"] = 10e3 inputs["rated_torque"] = 10e6 inputs["lss_rpm"] = x = np.linspace(0.1, 10.0, 20) + inputs["L_generator"] = 3.6 * 1.5 inputs["generator_mass_user"] = 0.0 + inputs["generator_radius_user"] = 0.0 inputs["generator_efficiency_user"] = 0.0 myobj.compute(inputs, outputs) self.assertEqual(outputs["R_generator"], 1.5) m = 37.68 * 10e3 self.assertEqual(outputs["generator_mass"], m) + self.assertEqual(outputs["generator_rotor_mass"], 0.5 * m) + self.assertEqual(outputs["generator_stator_mass"], 0.5 * m) npt.assert_equal( outputs["generator_I"], m * np.r_[0.5 * 1.5 ** 2, (3 * 1.5 ** 2 + (3.6 * 1.5) ** 2) / 12 * np.ones(2)] ) + npt.assert_equal(outputs["generator_rotor_I"], 0.5 * outputs["generator_I"]) + npt.assert_equal(outputs["generator_stator_I"], 0.5 * outputs["generator_I"]) eff = 1.0 - (0.01007 / x * x[-1] + 0.02 + 0.06899 * x / x[-1]) eff = np.maximum(1e-3, eff) @@ -116,9 +122,13 @@ def testGeneratorSimple(self): self.assertEqual(outputs["R_generator"], 1.5) m = np.mean([6.4737, 10.51, 5.34]) * 10e3 ** 0.9223 self.assertEqual(outputs["generator_mass"], m) + self.assertEqual(outputs["generator_rotor_mass"], 0.5 * m) + self.assertEqual(outputs["generator_stator_mass"], 0.5 * m) npt.assert_equal( outputs["generator_I"], m * np.r_[0.5 * 1.5 ** 2, (3 * 1.5 ** 2 + (3.6 * 1.5) ** 2) / 12 * np.ones(2)] ) + npt.assert_equal(outputs["generator_rotor_I"], 0.5 * outputs["generator_I"]) + npt.assert_equal(outputs["generator_stator_I"], 0.5 * outputs["generator_I"]) eff = 1.0 - (0.01289 / x * x[-1] + 0.0851 + 0.0 * x / x[-1]) eff = np.maximum(1e-3, eff) @@ -129,6 +139,16 @@ def testGeneratorSimple(self): myobj.compute(inputs, outputs) npt.assert_almost_equal(outputs["generator_efficiency"], eff) + inputs["generator_mass_user"] = 2.0 + inputs["generator_radius_user"] = 3.0 + myobj.compute(inputs, outputs) + self.assertEqual(outputs["R_generator"], 3.0) + self.assertEqual(outputs["generator_mass"], 2.0) + self.assertEqual(outputs["generator_rotor_mass"], 1.0) + self.assertEqual(outputs["generator_stator_mass"], 1.0) + npt.assert_equal(outputs["generator_rotor_I"], 0.5 * outputs["generator_I"]) + npt.assert_equal(outputs["generator_stator_I"], 0.5 * outputs["generator_I"]) + def testElectronics(self): inputs = {} outputs = {} @@ -205,7 +225,7 @@ def testMiscDirect(self): self.assertEqual(outputs["hvac_cm"], 6.0) npt.assert_equal(outputs["hvac_I"], outputs["hvac_mass"] * 1.5 ** 2 * np.r_[1.0, 0.5, 0.5]) - t = 0.05 + t = 0.04 self.assertEqual(outputs["platform_mass"], t * 3e3 * 12 ** 2) npt.assert_equal(outputs["platform_cm"], 0.0) npt.assert_equal( @@ -259,7 +279,7 @@ def testMiscGeared(self): self.assertEqual(outputs["hvac_cm"], 6.0) npt.assert_equal(outputs["hvac_I"], outputs["hvac_mass"] * 1.5 ** 2 * np.r_[1.0, 0.5, 0.5]) - t = 0.05 + t = 0.04 self.assertEqual(outputs["platform_mass"], t * 3e3 * L * W) npt.assert_equal(outputs["platform_cm"], 0.0) npt.assert_equal( diff --git a/wisdem/test/test_drivetrainse/test_drivetrainse.py b/wisdem/test/test_drivetrainse/test_drivetrainse.py index d76abb1b6..99240b06a 100644 --- a/wisdem/test/test_drivetrainse/test_drivetrainse.py +++ b/wisdem/test/test_drivetrainse/test_drivetrainse.py @@ -4,6 +4,7 @@ import numpy as np import openmdao.api as om +import numpy.testing as npt from wisdem.drivetrainse.drivetrain import DrivetrainSE npts = 12 @@ -14,6 +15,7 @@ def set_common(prob, opt): prob["rotor_diameter"] = 120.0 prob["machine_rating"] = 5e3 prob["D_top"] = 6.5 + prob["rated_torque"] = 10.25e6 # rev 1 9.94718e6 prob["F_hub"] = np.array([2409.750e3, 0.0, 74.3529e2]).reshape((3, 1)) prob["M_hub"] = np.array([-1.83291e4, 6171.7324e2, 5785.82946e2]).reshape((3, 1)) @@ -63,20 +65,21 @@ class TestGroup(unittest.TestCase): def testDirectDrive_withGen(self): opt = {} - opt["DriveSE"] = {} - opt["DriveSE"]["direct"] = True - opt["DriveSE"]["hub"] = {} - opt["DriveSE"]["hub"]["hub_gamma"] = 2.0 - opt["DriveSE"]["hub"]["spinner_gamma"] = 1.5 - opt["DriveSE"]["gamma_f"] = 1.35 - opt["DriveSE"]["gamma_m"] = 1.3 - opt["DriveSE"]["gamma_n"] = 1.0 - opt["RotorSE"] = {} - opt["RotorSE"]["n_pc"] = 20 + opt["WISDEM"] = {} + opt["WISDEM"]["DriveSE"] = {} + opt["WISDEM"]["DriveSE"]["direct"] = True + opt["WISDEM"]["DriveSE"]["hub"] = {} + opt["WISDEM"]["DriveSE"]["hub"]["hub_gamma"] = 2.0 + opt["WISDEM"]["DriveSE"]["hub"]["spinner_gamma"] = 1.5 + opt["WISDEM"]["DriveSE"]["gamma_f"] = 1.35 + opt["WISDEM"]["DriveSE"]["gamma_m"] = 1.3 + opt["WISDEM"]["DriveSE"]["gamma_n"] = 1.0 + opt["WISDEM"]["RotorSE"] = {} + opt["WISDEM"]["RotorSE"]["n_pc"] = 20 opt["materials"] = {} opt["materials"]["n_mat"] = 1 - opt["GeneratorSE"] = {} - opt["GeneratorSE"]["type"] = "pmsg_outer" + opt["WISDEM"]["GeneratorSE"] = {} + opt["WISDEM"]["GeneratorSE"]["type"] = "pmsg_outer" opt["flags"] = {} opt["flags"]["generator"] = True @@ -106,7 +109,6 @@ def testDirectDrive_withGen(self): prob["generator.D_shaft"] = 3.3 prob["generator.D_nose"] = 2.2 - prob["rated_torque"] = 10.25e6 # rev 1 9.94718e6 prob["generator.P_mech"] = 10.71947704e6 # rev 1 9.94718e6 prob["generator.rad_ag"] = 4.0 # rev 1 4.92 prob["generator.len_s"] = 1.7 # rev 2.3 @@ -148,19 +150,33 @@ def testDirectDrive_withGen(self): traceback.print_exc(file=sys.stdout) self.assertTrue(False) + # Test that key outputs are filled + self.assertGreater(prob["nacelle_mass"], 100e3) + self.assertLess(prob["nacelle_cm"][0], 0.0) + self.assertGreater(prob["nacelle_cm"][2], 0.0) + self.assertGreater(prob["rna_mass"], 100e3) + self.assertLess(prob["rna_cm"][0], 0.0) + self.assertGreater(prob["rna_cm"][2], 0.0) + self.assertGreater(prob["generator_mass"], 100e3) + self.assertGreater(prob["generator_cost"], 100e3) + npt.assert_array_less(100e3, prob["generator_I"]) + npt.assert_array_less(0.8, prob["generator_efficiency"]) + npt.assert_array_less(prob["generator_efficiency"], 1.0) + def testDirectDrive_withSimpleGen(self): opt = {} - opt["DriveSE"] = {} - opt["DriveSE"]["direct"] = True - opt["DriveSE"]["hub"] = {} - opt["DriveSE"]["hub"]["hub_gamma"] = 2.0 - opt["DriveSE"]["hub"]["spinner_gamma"] = 1.5 - opt["DriveSE"]["gamma_f"] = 1.35 - opt["DriveSE"]["gamma_m"] = 1.3 - opt["DriveSE"]["gamma_n"] = 1.0 - opt["RotorSE"] = {} - opt["RotorSE"]["n_pc"] = 20 + opt["WISDEM"] = {} + opt["WISDEM"]["DriveSE"] = {} + opt["WISDEM"]["DriveSE"]["direct"] = True + opt["WISDEM"]["DriveSE"]["hub"] = {} + opt["WISDEM"]["DriveSE"]["hub"]["hub_gamma"] = 2.0 + opt["WISDEM"]["DriveSE"]["hub"]["spinner_gamma"] = 1.5 + opt["WISDEM"]["DriveSE"]["gamma_f"] = 1.35 + opt["WISDEM"]["DriveSE"]["gamma_m"] = 1.3 + opt["WISDEM"]["DriveSE"]["gamma_n"] = 1.0 + opt["WISDEM"]["RotorSE"] = {} + opt["WISDEM"]["RotorSE"]["n_pc"] = 20 opt["materials"] = {} opt["materials"]["n_mat"] = 1 opt["flags"] = {} @@ -199,21 +215,35 @@ def testDirectDrive_withSimpleGen(self): traceback.print_exc(file=sys.stdout) self.assertTrue(False) + # Test that key outputs are filled + self.assertGreater(prob["nacelle_mass"], 100e3) + self.assertLess(prob["nacelle_cm"][0], 0.0) + self.assertGreater(prob["nacelle_cm"][2], 0.0) + self.assertGreater(prob["rna_mass"], 100e3) + self.assertLess(prob["rna_cm"][0], 0.0) + self.assertGreater(prob["rna_cm"][2], 0.0) + self.assertGreater(prob["generator_mass"], 100e3) + # self.assertGreater(prob["generator_cost"], 100e3) + npt.assert_array_less(100e3, prob["generator_I"]) + npt.assert_array_less(0.8, prob["generator_efficiency"]) + npt.assert_array_less(prob["generator_efficiency"], 1.0) + def testGeared_withGen(self): opt = {} - opt["DriveSE"] = {} - opt["DriveSE"]["direct"] = False - opt["DriveSE"]["hub"] = {} - opt["DriveSE"]["hub"]["hub_gamma"] = 2.0 - opt["DriveSE"]["hub"]["spinner_gamma"] = 1.5 - opt["DriveSE"]["gamma_f"] = 1.35 - opt["DriveSE"]["gamma_m"] = 1.3 - opt["DriveSE"]["gamma_n"] = 1.0 - opt["GeneratorSE"] = {} - opt["GeneratorSE"]["type"] = "dfig" - opt["RotorSE"] = {} - opt["RotorSE"]["n_pc"] = 20 + opt["WISDEM"] = {} + opt["WISDEM"]["DriveSE"] = {} + opt["WISDEM"]["DriveSE"]["direct"] = False + opt["WISDEM"]["DriveSE"]["hub"] = {} + opt["WISDEM"]["DriveSE"]["hub"]["hub_gamma"] = 2.0 + opt["WISDEM"]["DriveSE"]["hub"]["spinner_gamma"] = 1.5 + opt["WISDEM"]["DriveSE"]["gamma_f"] = 1.35 + opt["WISDEM"]["DriveSE"]["gamma_m"] = 1.3 + opt["WISDEM"]["DriveSE"]["gamma_n"] = 1.0 + opt["WISDEM"]["GeneratorSE"] = {} + opt["WISDEM"]["GeneratorSE"]["type"] = "dfig" + opt["WISDEM"]["RotorSE"] = {} + opt["WISDEM"]["RotorSE"]["n_pc"] = 20 opt["materials"] = {} opt["materials"]["n_mat"] = 1 opt["flags"] = {} @@ -310,21 +340,35 @@ def testGeared_withGen(self): traceback.print_exc(file=sys.stdout) self.assertTrue(False) + # Test that key outputs are filled + self.assertGreater(prob["nacelle_mass"], 100e3) + self.assertGreater(prob["nacelle_cm"][0], 0.0) + self.assertGreater(prob["nacelle_cm"][2], 0.0) + self.assertGreater(prob["rna_mass"], 100e3) + self.assertGreater(prob["rna_cm"][0], 0.0) + self.assertGreater(prob["rna_cm"][2], 0.0) + self.assertGreater(prob["generator_mass"], 10e3) + self.assertGreater(prob["generator_cost"], 10e3) + npt.assert_array_less(1e3, prob["generator_I"]) + npt.assert_array_less(0.2, prob["generator_efficiency"][1:]) + npt.assert_array_less(prob["generator_efficiency"], 1.0) + def testGeared_withSimpleGen(self): opt = {} - opt["DriveSE"] = {} - opt["DriveSE"]["direct"] = False - opt["DriveSE"]["hub"] = {} - opt["DriveSE"]["hub"]["hub_gamma"] = 2.0 - opt["DriveSE"]["hub"]["spinner_gamma"] = 1.5 - opt["DriveSE"]["gamma_f"] = 1.35 - opt["DriveSE"]["gamma_m"] = 1.3 - opt["DriveSE"]["gamma_n"] = 1.0 + opt["WISDEM"] = {} + opt["WISDEM"]["DriveSE"] = {} + opt["WISDEM"]["DriveSE"]["direct"] = False + opt["WISDEM"]["DriveSE"]["hub"] = {} + opt["WISDEM"]["DriveSE"]["hub"]["hub_gamma"] = 2.0 + opt["WISDEM"]["DriveSE"]["hub"]["spinner_gamma"] = 1.5 + opt["WISDEM"]["DriveSE"]["gamma_f"] = 1.35 + opt["WISDEM"]["DriveSE"]["gamma_m"] = 1.3 + opt["WISDEM"]["DriveSE"]["gamma_n"] = 1.0 opt["flags"] = {} opt["flags"]["generator"] = False - opt["RotorSE"] = {} - opt["RotorSE"]["n_pc"] = 20 + opt["WISDEM"]["RotorSE"] = {} + opt["WISDEM"]["RotorSE"]["n_pc"] = 20 opt["materials"] = {} opt["materials"]["n_mat"] = 1 @@ -372,6 +416,19 @@ def testGeared_withSimpleGen(self): traceback.print_exc(file=sys.stdout) self.assertTrue(False) + # Test that key outputs are filled + self.assertGreater(prob["nacelle_mass"], 100e3) + self.assertGreater(prob["nacelle_cm"][0], 0.0) + self.assertGreater(prob["nacelle_cm"][2], 0.0) + self.assertGreater(prob["rna_mass"], 100e3) + self.assertGreater(prob["rna_cm"][0], 0.0) + self.assertGreater(prob["rna_cm"][2], 0.0) + self.assertGreater(prob["generator_mass"], 10e3) + # self.assertGreater(prob["generator_cost"], 10e3) + npt.assert_array_less(1e3, prob["generator_I"]) + npt.assert_array_less(0.8, prob["generator_efficiency"]) + npt.assert_array_less(prob["generator_efficiency"], 1.0) + def suite(): suite = unittest.TestSuite() diff --git a/wisdem/test/test_drivetrainse/test_generator_driver.py b/wisdem/test/test_drivetrainse/test_generator_driver.py new file mode 100644 index 000000000..b4e8c2f92 --- /dev/null +++ b/wisdem/test/test_drivetrainse/test_generator_driver.py @@ -0,0 +1,154 @@ +import unittest + +import numpy as np +import numpy.testing as npt +import wisdem.drivetrainse.generator as gen + + +class TestGenerators(unittest.TestCase): + def setUp(self): + self.inputs = {} + self.outputs = {} + self.discrete_inputs = {} + self.discrete_outputs = {} + + self.inputs["machine_rating"] = 5e6 + self.inputs["rated_torque"] = 4.143289e6 + + self.inputs["rho_Fe"] = 7700.0 + self.inputs["rho_Fes"] = 7850.0 + self.inputs["rho_Copper"] = 8900.0 + self.inputs["rho_PM"] = 7450.0 + + self.inputs["B_r"] = 1.2 + self.inputs["E"] = 2e11 + self.inputs["G"] = 79.3e9 + self.inputs["P_Fe0e"] = 1.0 + self.inputs["P_Fe0h"] = 4.0 + self.inputs["S_N"] = -0.002 + self.inputs["alpha_p"] = 0.5 * np.pi * 0.7 + self.inputs["b_r_tau_r"] = 0.45 + self.inputs["b_ro"] = 0.004 + self.inputs["b_s_tau_s"] = 0.45 + self.inputs["b_so"] = 0.004 + self.inputs["cofi"] = 0.85 + self.inputs["freq"] = 60 + self.inputs["h_i"] = 0.001 + self.inputs["h_sy0"] = 0.0 + self.inputs["h_w"] = 0.005 + self.inputs["k_fes"] = 0.9 + self.inputs["k_fillr"] = 0.7 + self.inputs["k_fills"] = 0.65 + self.inputs["k_s"] = 0.2 + self.discrete_inputs["m"] = 3 + self.inputs["mu_0"] = np.pi * 4e-7 + self.inputs["mu_r"] = 1.06 + self.inputs["p"] = 3.0 + self.inputs["phi"] = np.deg2rad(90) + self.discrete_inputs["q1"] = 6 + self.discrete_inputs["q2"] = 4 + self.inputs["ratio_mw2pp"] = 0.7 + self.inputs["resist_Cu"] = 1.8e-8 * 1.4 + self.inputs["sigma"] = 40e3 + self.inputs["v"] = 0.3 + self.inputs["y_tau_p"] = 1.0 + self.inputs["y_tau_pr"] = 10.0 / 12 + + def testConstraints(self): + pass + + def testMofI(self): + inputs = {} + outputs = {} + myobj = gen.MofI() + + inputs["R_out"] = 3.0 + inputs["stator_mass"] = 60.0 + inputs["rotor_mass"] = 40.0 + inputs["generator_mass"] = 100.0 + inputs["len_s"] = 2.0 + myobj.compute(inputs, outputs) + npt.assert_equal(outputs["generator_I"], np.r_[50.0 * 9, 25.0 * 9 + 100.0 / 3.0, 25.0 * 9 + 100.0 / 3.0]) + npt.assert_almost_equal(outputs["rotor_I"], 0.4 * outputs["generator_I"]) + npt.assert_almost_equal(outputs["stator_I"], 0.6 * outputs["generator_I"]) + + def testCost(self): + inputs = {} + outputs = {} + myobj = gen.Cost() + + inputs["C_Cu"] = 2.0 + inputs["C_Fe"] = 0.5 + inputs["C_Fes"] = 4.0 + inputs["C_PM"] = 3.0 + + inputs["Copper"] = 10.0 + inputs["Iron"] = 0.0 + inputs["mass_PM"] = 0.0 + inputs["Structural_mass"] = 0.0 + myobj.compute(inputs, outputs) + self.assertAlmostEqual(outputs["generator_cost"], (1.26 * 2.0 * 10 + 96.2 * 0.064 * 10.0) / 0.619) + + inputs["Copper"] = 0.0 + inputs["Iron"] = 10.0 + inputs["mass_PM"] = 0.0 + inputs["Structural_mass"] = 0.0 + myobj.compute(inputs, outputs) + self.assertAlmostEqual(outputs["generator_cost"], (1.21 * 0.5 * 10 + 26.9 * 0.064 * 10.0) / 0.684) + + inputs["Copper"] = 0.0 + inputs["Iron"] = 0.0 + inputs["mass_PM"] = 10.0 + inputs["Structural_mass"] = 0.0 + myobj.compute(inputs, outputs) + self.assertAlmostEqual(outputs["generator_cost"], (1.0 * 3.0 * 10 + 79.0 * 0.064 * 10.0) / 0.619) + + inputs["Copper"] = 0.0 + inputs["Iron"] = 0.0 + inputs["mass_PM"] = 0.0 + inputs["Structural_mass"] = 10.0 + myobj.compute(inputs, outputs) + self.assertAlmostEqual(outputs["generator_cost"], (1.21 * 4.0 * 10 + 15.9 * 0.064 * 10.0) / 0.684) + + def testEff(self): + inputs = {} + outputs = {} + myobj = gen.PowerElectronicsEff() + + inputs["machine_rating"] = 2e6 + inputs["shaft_rpm"] = np.arange(10.1) + inputs["shaft_rpm"][0] = 0.1 + inputs["eandm_efficiency"] = np.ones(inputs["shaft_rpm"].shape) + myobj.compute(inputs, outputs) + npt.assert_array_less(outputs["converter_efficiency"], 1.0) + npt.assert_array_less(0.97, outputs["converter_efficiency"][1:]) + npt.assert_array_less(outputs["transformer_efficiency"], 1.0) + npt.assert_array_less(0.97, outputs["transformer_efficiency"][1:]) + npt.assert_almost_equal( + outputs["generator_efficiency"], outputs["transformer_efficiency"] * outputs["converter_efficiency"] + ) + + inputs["machine_rating"] = 20e6 + myobj.compute(inputs, outputs) + npt.assert_array_less(outputs["converter_efficiency"], 1.0) + npt.assert_array_less(0.97, outputs["converter_efficiency"][1:]) + npt.assert_array_less(outputs["transformer_efficiency"], 1.0) + npt.assert_array_less(0.97, outputs["transformer_efficiency"][1:]) + npt.assert_almost_equal( + outputs["generator_efficiency"], outputs["transformer_efficiency"] * outputs["converter_efficiency"] + ) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestGenerators)) + return suite + + +if __name__ == "__main__": + result = unittest.TextTestRunner().run(suite()) + + if result.wasSuccessful(): + exit(0) + else: + exit(1) diff --git a/wisdem/test/test_drivetrainse/test_generator_models.py b/wisdem/test/test_drivetrainse/test_generator_models.py index 406a87930..3ae179342 100644 --- a/wisdem/test/test_drivetrainse/test_generator_models.py +++ b/wisdem/test/test_drivetrainse/test_generator_models.py @@ -117,8 +117,8 @@ def testPMSG_Outer(self): self.assertAlmostEqual(self.outputs["A_Cuscalc"], 389.46615218) self.assertAlmostEqual(self.outputs["J_actual"][-1], 3.125519048273891) self.assertAlmostEqual(self.outputs["A_1"][-1], 148362.95007673657) - self.assertAlmostEqual(len(self.outputs["generator_efficiency"]), 20) - self.assertAlmostEqual(self.outputs["generator_efficiency"][-1], 0.9543687904168252) + self.assertAlmostEqual(len(self.outputs["eandm_efficiency"]), 20) + self.assertAlmostEqual(self.outputs["eandm_efficiency"][-1], 0.9543687904168252) self.assertAlmostEqual(self.outputs["Iron"], 89073.14254723) self.assertAlmostEqual(self.outputs["mass_PM"], 1274.81620149) self.assertAlmostEqual(self.outputs["Copper"], 13859.17179278) @@ -173,8 +173,8 @@ def testPMSG_Arms(self): self.assertAlmostEqual(self.outputs["J_s"][-1], 3.337574160500578) self.assertAlmostEqual(self.outputs["Losses"][-1], 351360.55854497873) self.assertAlmostEqual(self.outputs["K_rad"], 0.245398773006135) - self.assertAlmostEqual(len(self.outputs["generator_efficiency"]), 20) - self.assertAlmostEqual(self.outputs["generator_efficiency"][-1], 0.9343418267745142) + self.assertAlmostEqual(len(self.outputs["eandm_efficiency"]), 20) + self.assertAlmostEqual(self.outputs["eandm_efficiency"][-1], 0.9343418267745142) self.assertAlmostEqual(self.outputs["S"], 768.0) self.assertAlmostEqual(self.outputs["Slot_aspect_ratio"], 5.832426544390113) self.assertAlmostEqual(self.outputs["Copper"], 6654.6915566955695) @@ -227,8 +227,8 @@ def testPMSG_disc(self): self.assertAlmostEqual(self.outputs["J_s"][-1], 3.5129208) self.assertAlmostEqual(self.outputs["Losses"][-1], 338164.5549178) self.assertAlmostEqual(self.outputs["K_rad"], 0.2148997) - self.assertAlmostEqual(len(self.outputs["generator_efficiency"]), 20) - self.assertAlmostEqual(self.outputs["generator_efficiency"][-1], 0.936651530) + self.assertAlmostEqual(len(self.outputs["eandm_efficiency"]), 20) + self.assertAlmostEqual(self.outputs["eandm_efficiency"][-1], 0.936651530) self.assertAlmostEqual(self.outputs["S"], 942.0000000) self.assertAlmostEqual(self.outputs["Slot_aspect_ratio"], 5.7277538) self.assertAlmostEqual(self.outputs["Copper"], 5588.6107806) @@ -280,8 +280,8 @@ def testEESG(self): self.assertAlmostEqual(self.outputs["J_s"][-1], 3.75697924800609) self.assertAlmostEqual(self.outputs["Losses"][-1], 444556.7203870894) self.assertAlmostEqual(self.outputs["K_rad"], 0.21874999999999997) - self.assertAlmostEqual(len(self.outputs["generator_efficiency"]), 20) - self.assertAlmostEqual(self.outputs["generator_efficiency"][-1], 0.918348408655116) + self.assertAlmostEqual(len(self.outputs["eandm_efficiency"]), 20) + self.assertAlmostEqual(self.outputs["eandm_efficiency"][-1], 0.918348408655116) self.assertAlmostEqual(self.outputs["S"], 708.0) self.assertAlmostEqual(self.outputs["Slot_aspect_ratio"], 4.695070821210912) self.assertAlmostEqual(self.outputs["Copper"], 16059.414075335235) @@ -341,8 +341,8 @@ def testSCIG(self): self.assertAlmostEqual(self.outputs["generator_mass"], 40729.31598698678) self.assertAlmostEqual(self.outputs["K_rad"], 1.1818181818181817) self.assertAlmostEqual(self.outputs["Losses"][-1], 75068.46569051298) - self.assertAlmostEqual(len(self.outputs["generator_efficiency"]), 20) - self.assertAlmostEqual(self.outputs["generator_efficiency"][-1], 0.9849562794756211) + self.assertAlmostEqual(len(self.outputs["eandm_efficiency"]), 20) + self.assertAlmostEqual(self.outputs["eandm_efficiency"][-1], 0.9849562794756211) self.assertAlmostEqual(self.outputs["Copper"], 1360.253987369221) self.assertAlmostEqual(self.outputs["Iron"], 11520.904698026085) self.assertAlmostEqual(self.outputs["R_out"], 0.7663579416108941) @@ -403,8 +403,8 @@ def testDFIG(self): self.assertAlmostEqual(self.outputs["generator_mass"], 19508.405570203206) self.assertAlmostEqual(self.outputs["K_rad"], 0.4016393442622951) self.assertAlmostEqual(self.outputs["Losses"][-1], 143901.77088462992) - self.assertAlmostEqual(len(self.outputs["generator_efficiency"]), 20) - self.assertAlmostEqual(self.outputs["generator_efficiency"][-1], 0.9654635749876888) + self.assertAlmostEqual(len(self.outputs["eandm_efficiency"]), 20) + self.assertAlmostEqual(self.outputs["eandm_efficiency"][-1], 0.9654635749876888) self.assertAlmostEqual(self.outputs["Copper"], 354.97950026786026) self.assertAlmostEqual(self.outputs["Iron"], 6077.944572132577) self.assertAlmostEqual(self.outputs["Structural_mass"], 13075.48149780277) diff --git a/wisdem/test/test_examples/test_examples.py b/wisdem/test/test_examples/test_examples.py index 89f06bdd0..30445899b 100644 --- a/wisdem/test/test_examples/test_examples.py +++ b/wisdem/test/test_examples/test_examples.py @@ -37,13 +37,14 @@ "09_floating/spar_example", "09_floating/spar_opt", "09_floating/tlp_example", - "09_weis_floating/wisdem_driver_oc3", - "09_weis_floating/wisdem_driver_oc4", + "09_floating/nrel5mw-spar_oc3_driver", + "09_floating/nrel5mw-semi_oc4_driver", "10_ccblade/example", "10_ccblade/gradients", "10_ccblade/precurve", "11_airfoilprep/example", "12_pyframe3dd/exB", + "13_design_of_experiments/doe_driver" ] diff --git a/wisdem/test/test_floatingse/test_column.py b/wisdem/test/test_floatingse/test_column.py deleted file mode 100644 index 88290d580..000000000 --- a/wisdem/test/test_floatingse/test_column.py +++ /dev/null @@ -1,699 +0,0 @@ -import unittest - -import numpy as np -import openmdao.api as om -import numpy.testing as npt -import wisdem.floatingse.column as column -from wisdem.commonse import gravity as g -from wisdem.commonse.utilities import nodal2sectional -from wisdem.commonse.vertical_cylinder import get_nfull - -NHEIGHT = 6 -NPTS = get_nfull(NHEIGHT) -myones = np.ones((NPTS,)) -secones = np.ones((NPTS - 1,)) - - -class TestInputs(unittest.TestCase): - def setUp(self): - self.inputs = {} - self.outputs = {} - self.discrete_inputs = {} - self.discrete_outputs = {} - - def testDiscYAML_1Material(self): - - # Test land based, 1 material - self.inputs["s"] = np.linspace(0, 1, 5) - self.inputs["layer_thickness"] = 0.25 * np.ones((1, 4)) - self.inputs["height"] = 1e2 - self.inputs["outer_diameter_in"] = 8 * np.ones(5) - self.discrete_inputs["layer_materials"] = ["steel"] - self.inputs["E_mat"] = 1e9 * np.ones((1, 3)) - self.inputs["G_mat"] = 1e8 * np.ones((1, 3)) - self.inputs["sigma_y_mat"] = np.array([1e7]) - self.inputs["rho_mat"] = np.array([1e4]) - self.inputs["unit_cost_mat"] = np.array([1e1]) - self.discrete_inputs["material_names"] = ["steel"] - myobj = column.DiscretizationYAML(n_height=5, n_layers=1, n_mat=1) - myobj.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - - npt.assert_equal(self.outputs["section_height"], 25.0 * np.ones(4)) - npt.assert_equal(self.outputs["outer_diameter"], self.inputs["outer_diameter_in"]) - npt.assert_equal(self.outputs["wall_thickness"], 0.25 * np.ones(4)) - npt.assert_equal(self.outputs["E"], 1e9 * np.ones(4)) - npt.assert_equal(self.outputs["G"], 1e8 * np.ones(4)) - npt.assert_equal(self.outputs["sigma_y"], 1e7 * np.ones(4)) - npt.assert_equal(self.outputs["rho"], 1e4 * np.ones(4)) - npt.assert_equal(self.outputs["unit_cost"], 1e1 * np.ones(4)) - - def testDiscYAML_2Materials(self): - # Test land based, 2 materials - self.inputs["s"] = np.linspace(0, 1, 5) - self.inputs["layer_thickness"] = np.array([[0.25, 0.25, 0.0, 0.0], [0.0, 0.0, 0.1, 0.1]]) - self.inputs["height"] = 1e2 - self.inputs["outer_diameter_in"] = 8 * np.ones(5) - self.discrete_inputs["layer_materials"] = ["steel", "other"] - self.inputs["E_mat"] = 1e9 * np.vstack((np.ones((1, 3)), 2 * np.ones((1, 3)))) - self.inputs["G_mat"] = 1e8 * np.vstack((np.ones((1, 3)), 2 * np.ones((1, 3)))) - self.inputs["sigma_y_mat"] = np.array([1e7, 2e7]) - self.inputs["rho_mat"] = np.array([1e4, 2e4]) - self.inputs["unit_cost_mat"] = np.array([1e1, 2e1]) - self.discrete_inputs["material_names"] = ["steel", "other"] - myobj = column.DiscretizationYAML(n_height=5, n_layers=1, n_mat=1) - myobj.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - - npt.assert_equal(self.outputs["section_height"], 25.0 * np.ones(4)) - npt.assert_equal(self.outputs["outer_diameter"], self.inputs["outer_diameter_in"]) - npt.assert_equal(self.outputs["wall_thickness"], np.array([0.25, 0.25, 0.1, 0.1])) - npt.assert_equal(self.outputs["E"], 1e9 * np.array([1, 1, 2, 2])) - npt.assert_equal(self.outputs["G"], 1e8 * np.array([1, 1, 2, 2])) - npt.assert_equal(self.outputs["sigma_y"], 1e7 * np.array([1, 1, 2, 2])) - npt.assert_equal(self.outputs["rho"], 1e4 * np.array([1, 1, 2, 2])) - npt.assert_equal(self.outputs["unit_cost"], 1e1 * np.array([1, 1, 2, 2])) - - -class TestBulk(unittest.TestCase): - def setUp(self): - self.inputs = {} - self.outputs = {} - self.resid = None - - self.inputs["z_full"] = np.linspace(0, 1, NPTS) - self.inputs["d_full"] = 10.0 * myones - self.inputs["t_full"] = 0.05 * secones - self.inputs["rho_full"] = 1e3 * secones - self.inputs["bulkhead_locations"] = np.array([0.0, 1.0, 3.0, 5.0]) / 5.0 - nbulk = len(self.inputs["bulkhead_locations"]) - self.inputs["bulkhead_thickness"] = 0.05 * np.ones(nbulk) - self.inputs["unit_cost_full"] = 1.0 * secones - self.inputs["painting_cost_rate"] = 10.0 - self.inputs["labor_cost_rate"] = 2.0 - self.inputs["shell_mass"] = 500.0 * np.ones(NPTS - 1) - - self.bulk = column.BulkheadProperties(n_height=NHEIGHT, n_bulkhead=nbulk) - - def testAll(self): - self.bulk.compute(self.inputs, self.outputs) - - R_i = 0.5 * 10 - 0.05 - m_bulk = np.pi * 1e3 * R_i ** 2 * 0.05 - expect = np.zeros(self.inputs["z_full"].size - 1) - expect[[0, 3, 9, NPTS - 2]] = m_bulk - ind = expect > 0.0 - npt.assert_almost_equal(self.outputs["bulkhead_mass"], expect) - - J0 = 0.50 * m_bulk * R_i ** 2 - I0 = 0.25 * m_bulk * R_i ** 2 - - z_bulk = s_bulk = self.inputs["bulkhead_locations"] - - I = np.zeros(6) - I[2] = 4.0 * J0 - I[0] = I0 + m_bulk * z_bulk[0] ** 2 - I[0] += I0 + m_bulk * z_bulk[1] ** 2 - I[0] += I0 + m_bulk * z_bulk[2] ** 2 - I[0] += I0 + m_bulk * z_bulk[3] ** 2 - I[1] = I[0] - npt.assert_almost_equal(self.outputs["bulkhead_I_keel"], I) - - A = np.pi * R_i ** 2 - Kp_exp = 10.0 * 2 * A * ind.sum() - self.inputs["painting_cost_rate"] = 10.0 - self.inputs["unit_cost_full"] = 1.0 * secones - self.inputs["labor_cost_rate"] = 0.0 - self.bulk.compute(self.inputs, self.outputs) - self.assertEqual(self.outputs["bulkhead_cost"], Kp_exp + m_bulk * ind.sum()) - - self.inputs["painting_cost_rate"] = 0.0 - self.inputs["unit_cost_full"] = 0.0 * secones - self.inputs["labor_cost_rate"] = 1.0 - self.bulk.compute(self.inputs, self.outputs) - self.assertGreater(self.outputs["bulkhead_cost"], 2e3) - - -class TestBuoyancyTank(unittest.TestCase): - def setUp(self): - self.inputs = {} - self.outputs = {} - self.resid = None - - self.inputs["d_full"] = 10.0 * myones - self.inputs["z_full"] = np.linspace(0, 1, NPTS) - 0.5 - self.inputs["rho_full"] = 1e3 * secones - - self.inputs["buoyancy_tank_diameter"] = 12.0 - self.inputs["buoyancy_tank_height"] = 0.25 - self.inputs["buoyancy_tank_location"] = 0.0 - self.inputs["unit_cost_full"] = 1.0 * secones - self.inputs["labor_cost_rate"] = 2.0 - self.inputs["painting_cost_rate"] = 10.0 - self.inputs["shell_mass"] = 500.0 * np.ones(NPTS - 1) - - self.box = column.BuoyancyTankProperties(n_height=NHEIGHT) - - def testNormal(self): - self.box.compute(self.inputs, self.outputs) - - A_box = np.pi * (6 * 6 - 5 * 5) - V_box = A_box * 0.25 - A_box = 2 * A_box + 0.25 * 2 * np.pi * 6 - m_expect = A_box * (6.0 / 50.0) * 1e3 - self.assertEqual(self.outputs["buoyancy_tank_mass"], m_expect) - self.assertEqual(self.outputs["buoyancy_tank_cg"], -0.5 + 0.5 * 0.25) - self.assertAlmostEqual(self.outputs["buoyancy_tank_displacement"], V_box) - # self.assertEqual(self.outputs['buoyancy_tank_I_keel'], 0.0) - - self.inputs["unit_cost_full"] = 1.0 * secones - self.inputs["labor_cost_rate"] = 0.0 - self.inputs["painting_cost_rate"] = 10.0 - self.box.compute(self.inputs, self.outputs) - self.assertEqual(self.outputs["buoyancy_tank_cost"], m_expect + 10 * 2 * 1.5 * A_box) - - self.inputs["unit_cost_full"] = 0.0 * secones - self.inputs["labor_cost_rate"] = 1.0 - self.inputs["painting_cost_rate"] = 0.0 - self.box.compute(self.inputs, self.outputs) - self.assertGreater(self.outputs["buoyancy_tank_cost"], 1e3) - - def testTopAbove(self): - self.inputs["buoyancy_tank_height"] = 0.75 - self.box.compute(self.inputs, self.outputs) - - A_box = np.pi * (6 * 6 - 5 * 5) - V_box = np.pi * (6 * 6 - 5 * 5) * 0.5 - m_expect = (2 * A_box + 0.75 * 2 * np.pi * 6) * (6.0 / 50.0) * 1e3 - self.assertAlmostEqual(self.outputs["buoyancy_tank_mass"], m_expect) - self.assertAlmostEqual(self.outputs["buoyancy_tank_cg"], -0.5 + 0.5 * 0.75) - self.assertAlmostEqual(self.outputs["buoyancy_tank_displacement"], V_box) - - def testBottomAbove(self): - self.inputs["buoyancy_tank_location"] = 0.6 - self.box.compute(self.inputs, self.outputs) - - A_box = np.pi * (6 * 6 - 5 * 5) - V_box = np.pi * (6 * 6 - 5 * 5) * 0.0 - m_expect = (2 * A_box + 0.25 * 2 * np.pi * 6) * (6.0 / 50.0) * 1e3 - self.assertAlmostEqual(self.outputs["buoyancy_tank_mass"], m_expect) - self.assertAlmostEqual(self.outputs["buoyancy_tank_cg"], 0.1 + 0.5 * 0.25) - self.assertAlmostEqual(self.outputs["buoyancy_tank_displacement"], V_box) - - def testTooNarrow(self): - self.inputs["buoyancy_tank_diameter"] = 8.0 - self.box.compute(self.inputs, self.outputs) - - A_box = np.pi * (6 * 6 - 5 * 5) - V_box = np.pi * (6 * 6 - 5 * 5) * 0.0 - m_expect = (2 * A_box + 0.25 * 2 * np.pi * 6) * (6.0 / 50.0) * 1e3 - self.assertAlmostEqual(self.outputs["buoyancy_tank_mass"], 0.0) - self.assertEqual(self.outputs["buoyancy_tank_cg"], -0.5 + 0.5 * 0.25) - self.assertAlmostEqual(self.outputs["buoyancy_tank_displacement"], 0.0) - - -class TestStiff(unittest.TestCase): - def testAll(self): - inputs = {} - outputs = {} - resid = None - - inputs["t_web"] = 0.5 * secones - inputs["t_flange"] = 0.3 * secones - inputs["h_web"] = 1.0 * secones - inputs["w_flange"] = 2.0 * secones - inputs["L_stiffener"] = 0.1 * secones - inputs["L_stiffener"][int(NPTS / 2) :] = 0.05 - inputs["rho_full"] = 1e3 * secones - inputs["unit_cost_full"] = 1.0 * secones - inputs["labor_cost_rate"] = 2.0 - inputs["painting_cost_rate"] = 10.0 - inputs["shell_mass"] = 500.0 * np.ones(NPTS - 1) - - inputs["t_full"] = 0.5 * secones - inputs["d_full"] = 2 * 10.0 * myones - inputs["d_full"][1::2] = 2 * 8.0 - inputs["z_full"] = np.linspace(0, 1, NPTS) - 0.5 - inputs["z_param"] = np.linspace(0, 1, NHEIGHT) - 0.5 - - stiff = column.StiffenerProperties(n_height=NHEIGHT) - - stiff.compute(inputs, outputs) - - Rwo = 9 - 0.5 - Rwi = Rwo - 1.0 - Rfi = Rwi - 0.3 - V1 = np.pi * (Rwo ** 2 - Rwi ** 2) * 0.5 - V2 = np.pi * (Rwi ** 2 - Rfi ** 2) * 2.0 - V = V1 + V2 - expect = V * 1e3 - actual = outputs["stiffener_mass"] - - # Test Mass - self.assertAlmostEqual(actual.sum(), expect * (0.5 / 0.1 + 0.5 / 0.05)) - - # Test cost - A = 2 * (np.pi * (Rwo ** 2 - Rwi ** 2) + 2 * np.pi * 0.5 * (Rfi + Rwi) * (0.3 + 2)) - 2 * np.pi * Rwi * 0.5 - inputs["unit_cost_full"] = 1.0 * secones - inputs["labor_cost_rate"] = 0.0 - inputs["painting_cost_rate"] = 10.0 - stiff.compute(inputs, outputs) - self.assertAlmostEqual(outputs["stiffener_cost"], (expect + 10 * 2 * A) * (0.5 / 0.1 + 0.5 / 0.05)) - - inputs["unit_cost_full"] = 0.0 * secones - inputs["labor_cost_rate"] = 1.0 - inputs["painting_cost_rate"] = 0.0 - stiff.compute(inputs, outputs) - self.assertGreater(outputs["stiffener_cost"], 1e3) - - # Test moment - inputs["L_stiffener"] = 1.2 * secones - stiff.compute(inputs, outputs) - I_web = column.I_tube(Rwi, Rwo, 0.5, V1 * 1e3) - I_fl = column.I_tube(Rfi, Rwi, 2.0, V2 * 1e3) - I_sec = I_web + I_fl - z_sec = 0.6 + 1e-6 - - I = np.zeros(6) - I[2] = I_sec[0, 2] - I[0] += I_sec[0, 0] + expect * z_sec ** 2.0 - I[1] = I[0] - - npt.assert_almost_equal(outputs["stiffener_I_keel"], I) - npt.assert_equal(outputs["flange_spacing_ratio"], 2 * 2.0 / 1.2) - npt.assert_equal(outputs["stiffener_radius_ratio"], 1.8 / 9.0) - - -class TestBallast(unittest.TestCase): - def testAll(self): - inputs = {} - outputs = {} - resid = None - inputs["t_full"] = 0.5 * secones - inputs["d_full"] = 2 * 10.0 * myones - inputs["z_full"] = np.linspace(0, 1, NPTS) - 0.5 - inputs["permanent_ballast_height"] = 1.0 - inputs["permanent_ballast_density"] = 2e3 - inputs["ballast_cost_rate"] = 10.0 - inputs["rho_water"] = 1e3 - - ball = column.BallastProperties(n_height=NHEIGHT) - - ball.compute(inputs, outputs) - - area = np.pi * 9.5 ** 2 - m_perm = area * 1.0 * 2e3 - cg_perm = inputs["z_full"][0] + 0.5 - - I_perm = np.zeros(6) - I_perm[2] = 0.5 * m_perm * 9.5 ** 2 - I_perm[0] = m_perm * (3 * 9.5 ** 2 + 1.0 ** 2) / 12.0 + m_perm * 0.5 ** 2 - I_perm[1] = I_perm[0] - - # Unused! - h_expect = 1e6 / area / 1000.0 - m_expect = m_perm + 1e6 - cg_water = inputs["z_full"][0] + 1.0 + 0.5 * h_expect - cg_expect = (m_perm * cg_perm + 1e6 * cg_water) / m_expect - - self.assertAlmostEqual(outputs["ballast_mass"].sum(), m_perm) - self.assertAlmostEqual(outputs["ballast_z_cg"], cg_perm) - npt.assert_almost_equal(outputs["ballast_I_keel"], I_perm) - - -class TestGeometry(unittest.TestCase): - def testAll(self): - inputs = {} - outputs = {} - resid = None - - this_nheight = 3 - this_npts = column.get_nfull(this_nheight) - this_ones = np.ones(this_npts - 1) - - inputs["z_full_in"] = np.linspace(0, 50.0, this_npts) - inputs["z_param_in"] = np.array([0.0, 20.0, 50.0]) - inputs["section_height"] = np.array([20.0, 30.0]) - inputs["freeboard"] = 15.0 - inputs["water_depth"] = 100.0 - inputs["stiffener_web_thickness"] = np.array([0.5, 0.5]) - inputs["stiffener_flange_thickness"] = np.array([0.3, 0.3]) - inputs["stiffener_web_height"] = np.array([1.0, 1.0]) - inputs["stiffener_flange_width"] = np.array([2.0, 2.0]) - inputs["stiffener_spacing"] = np.array([0.1, 0.1]) - inputs["Hsig_wave"] = 5.0 - inputs["max_draft"] = 70.0 - inputs["unit_cost"] = 1.0 * np.ones(2) - inputs["E"] = 2e9 * np.ones(2) - inputs["G"] = 2e7 * np.ones(2) - inputs["rho"] = 7850 * np.ones(2) - inputs["sigma_y"] = 3e9 * np.ones(2) - - geom = column.ColumnGeometry(n_height=this_nheight) - - geom.compute(inputs, outputs) - self.assertEqual(outputs["draft"], 35.0) - self.assertEqual(outputs["draft"], np.sum(inputs["section_height"]) - inputs["freeboard"]) - self.assertEqual(outputs["draft"], -1 * outputs["z_full"][0]) - self.assertEqual(outputs["draft"], -1 * outputs["z_param"][0]) - self.assertEqual(outputs["draft_margin"], 0.5) - npt.assert_equal(outputs["z_param"], np.array([-35.0, -15.0, 15.0])) - npt.assert_equal(outputs["z_full"], inputs["z_full_in"] - 35) - npt.assert_equal(outputs["t_web"], 0.5 * this_ones) - npt.assert_equal(outputs["t_flange"], 0.3 * this_ones) - npt.assert_equal(outputs["h_web"], 1.0 * this_ones) - npt.assert_equal(outputs["w_flange"], 2.0 * this_ones) - npt.assert_equal(outputs["L_stiffener"], 0.1 * this_ones) - - -class TestProperties(unittest.TestCase): - def setUp(self): - self.inputs = {} - self.outputs = {} - self.resid = None - - # For Geometry call - this_nheight = 3 - this_npts = column.get_nfull(this_nheight) - this_sec = np.ones(this_npts - 1) - this_ones = np.ones(this_npts) - self.inputs["z_full_in"] = np.linspace(0, 50.0, this_npts) - self.inputs["z_section"], _ = nodal2sectional(self.inputs["z_full_in"]) - - self.inputs["z_param_in"] = np.array([0.0, 20.0, 50.0]) - self.inputs["section_height"] = np.array([20.0, 30.0]) - self.inputs["freeboard"] = 15.0 - self.inputs["fairlead"] = 10.0 - self.inputs["water_depth"] = 100.0 - self.inputs["Hsig_wave"] = 5.0 - self.inputs["max_draft"] = 70.0 - - self.inputs["t_full"] = 0.5 * this_sec - self.inputs["d_full"] = 2 * 10.0 * this_ones - - self.inputs["stack_mass_in"] = 0.0 - - self.inputs["shell_I_keel"] = 1e5 * np.array([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]) - self.inputs["stiffener_I_keel"] = 2e5 * np.array([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]) - self.inputs["bulkhead_I_keel"] = 3e5 * np.array([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]) - self.inputs["buoyancy_tank_I_keel"] = 5e6 * np.array([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]) - self.inputs["ballast_I_keel"] = 2e3 * np.array([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]) - - self.inputs["buoyancy_tank_diameter"] = 15.0 - - self.inputs["rho_water"] = 1e3 - self.inputs["bulkhead_mass"] = 10.0 * this_sec - self.inputs["bulkhead_z_cg"] = -10.0 - self.inputs["shell_mass"] = 500.0 * this_sec - self.inputs["stiffener_mass"] = 100.0 * this_sec - self.inputs["ballast_mass"] = 20.0 * this_sec - self.inputs["ballast_z_cg"] = -35.0 - - self.inputs["buoyancy_tank_mass"] = 20.0 - self.inputs["buoyancy_tank_cg"] = -15.0 - self.inputs["buoyancy_tank_location"] = 0.3 - self.inputs["buoyancy_tank_displacement"] = 300.0 - self.inputs["outfitting_factor"] = 1.05 - - self.inputs["shell_cost"] = 1.0 - self.inputs["stiffener_cost"] = 2.0 - self.inputs["bulkhead_cost"] = 3.0 - self.inputs["buoyancy_tank_cost"] = 4.0 - self.inputs["ballast_cost"] = 5.0 - - self.inputs["mooring_mass"] = 50.0 - self.inputs["mooring_vertical_load"] = 25.0 - self.inputs["mooring_restoring_force"] = 1e5 - self.inputs["mooring_cost"] = 1e4 - - self.inputs["outfitting_cost_rate"] = 1.0 - - self.inputs["unit_cost"] = 1.0 * np.ones(2) - self.inputs["E"] = 2e9 * np.ones(2) - self.inputs["G"] = 2e7 * np.ones(2) - self.inputs["rho"] = 7850 * np.ones(2) - self.inputs["sigma_y"] = 3e9 * np.ones(2) - - self.inputs["stiffener_web_thickness"] = np.array([0.5, 0.5]) - self.inputs["stiffener_flange_thickness"] = np.array([0.3, 0.3]) - self.inputs["stiffener_web_height"] = np.array([1.0, 1.0]) - self.inputs["stiffener_flange_width"] = np.array([2.0, 2.0]) - self.inputs["stiffener_spacing"] = np.array([0.1, 0.1]) - - self.geom = column.ColumnGeometry(n_height=this_nheight) - self.set_geometry() - - self.mycolumn = column.ColumnProperties(n_height=this_nheight) - - def set_geometry(self): - tempUnknowns = {} - self.geom.compute(self.inputs, tempUnknowns) - for pairs in tempUnknowns.items(): - self.inputs[pairs[0]] = pairs[1] - self.inputs["z_section"], _ = nodal2sectional(self.inputs["z_full"]) - - def testColumnMassCG(self): - self.mycolumn.compute_column_mass_cg(self.inputs, self.outputs) - ibox = self.mycolumn.ibox - - bulk = self.inputs["bulkhead_mass"] - bulkcg = self.inputs["bulkhead_z_cg"] - stiff = self.inputs["stiffener_mass"] - shell = self.inputs["shell_mass"] - box = self.inputs["buoyancy_tank_mass"] - boxcg = self.inputs["buoyancy_tank_cg"] - m_ballast = self.inputs["ballast_mass"] - cg_ballast = self.inputs["ballast_z_cg"] - - m_column = bulk.sum() + stiff.sum() + shell.sum() + box - m_out = 0.05 * m_column - m_expect = m_column + m_ballast.sum() + m_out - - mysec = stiff + shell + bulk - mysec[ibox] += box - mysec += m_ballast - mysec += m_out / len(mysec) - - mycg = (box * boxcg + bulk.sum() * bulkcg + np.dot(stiff + shell, self.inputs["z_section"])) / m_column - cg_system = ((m_column + m_out) * mycg + m_ballast.sum() * cg_ballast) / m_expect - - Iones = np.r_[np.ones(3), np.zeros(3)] - I_expect = 1.05 * 5.6e6 * Iones + 2e3 * Iones - I_expect[0] = I_expect[1] = I_expect[0] - m_expect * (cg_system - self.inputs["z_full"][0]) ** 2 - - self.assertAlmostEqual(self.outputs["column_total_mass"].sum(), m_expect) - self.assertAlmostEqual(self.outputs["z_center_of_mass"], cg_system) - - self.assertAlmostEqual(self.outputs["column_structural_mass"], m_column + m_out) - self.assertAlmostEqual(self.outputs["column_outfitting_mass"], m_out) - npt.assert_almost_equal(self.outputs["column_total_mass"], mysec) - npt.assert_almost_equal(self.outputs["I_column"], I_expect) - - def testBalance(self): - rho_w = self.inputs["rho_water"] - - self.mycolumn.compute_column_mass_cg(self.inputs, self.outputs) - self.mycolumn.balance_column(self.inputs, self.outputs) - - V_column = np.pi * 100.0 * 35.0 - V_box = self.inputs["buoyancy_tank_displacement"] - box_cg = self.inputs["buoyancy_tank_cg"] - V_expect = V_column + V_box - cb_expect = (-17.5 * V_column + V_box * box_cg) / V_expect - Ixx = 0.25 * np.pi * 1e4 - Axx = np.pi * 1e2 - self.assertAlmostEqual(self.outputs["displaced_volume"].sum(), V_expect) - self.assertAlmostEqual(self.outputs["hydrostatic_force"].sum(), V_expect * rho_w * g) - self.assertAlmostEqual(self.outputs["z_center_of_buoyancy"], cb_expect) - self.assertAlmostEqual(self.outputs["Iwater"], Ixx) - self.assertAlmostEqual(self.outputs["Awater"], Axx) - - m_a = np.zeros(6) - m_a[:2] = V_expect * rho_w - m_a[2] = 0.5 * (8.0 / 3.0) * rho_w * 10.0 ** 3 - m_a[3:5] = np.pi * rho_w * 100.0 * ((0 - cb_expect) ** 3.0 - (-35 - cb_expect) ** 3.0) / 3.0 - npt.assert_almost_equal(self.outputs["column_added_mass"], m_a, decimal=-4) - - # Test if everything under water - dz = -1.5 * self.inputs["z_full"][-1] - self.inputs["z_section"] += dz - self.inputs["z_full"] += dz - self.mycolumn.balance_column(self.inputs, self.outputs) - V_column = np.pi * 100.0 * 50.0 - V_expect = V_column + V_box - cb_expect = (V_column * (-25.0 + self.inputs["z_full"][-1]) + V_box * box_cg) / V_expect - self.assertAlmostEqual(self.outputs["displaced_volume"].sum(), V_expect) - self.assertAlmostEqual(self.outputs["hydrostatic_force"].sum(), V_expect * rho_w * g) - self.assertAlmostEqual(self.outputs["z_center_of_buoyancy"], cb_expect) - - # Test taper- check hydrostatic via Archimedes within 1% - self.inputs["d_full"][5] -= 8.0 - self.mycolumn.balance_column(self.inputs, self.outputs) - self.assertAlmostEqual( - self.outputs["hydrostatic_force"].sum() / (self.outputs["displaced_volume"].sum() * rho_w * g), - 1.0, - delta=1e-2, - ) - - def testCheckCost(self): - self.outputs["column_outfitting_mass"] = 25.0 - self.outputs["column_total_mass"] = 25 * np.ones(10) - self.mycolumn.compute_cost(self.inputs, self.outputs) - - self.assertEqual(self.outputs["column_structural_cost"], (1 + 2 + 3 + 4)) - self.assertEqual(self.outputs["column_outfitting_cost"], 1.0 * 25.0) - self.assertEqual(self.outputs["column_total_cost"], (1 + 2 + 3 + 4) + 1.0 * (25.0) + 5) - - -class TestBuckle(unittest.TestCase): - def setUp(self): - self.inputs = {} - self.outputs = {} - self.discrete_inputs = {} - self.discrete_outputs = {} - - # Use the API 2U Appendix B as a big unit test! - ksi_to_si = 6894757.29317831 - lbperft3_to_si = 16.0185 - ft_to_si = 0.3048 - in_to_si = ft_to_si / 12.0 - kip_to_si = 4.4482216 * 1e3 - - onepts = np.ones((NPTS,)) - onesec = np.ones((NPTS - 1,)) - # onesec0 = np.ones((NHEIGHT,)) - self.inputs["d_full"] = 600 * onepts * in_to_si - self.inputs["t_full"] = 0.75 * onesec * in_to_si - self.inputs["t_web"] = 5.0 / 8.0 * onesec * in_to_si - self.inputs["h_web"] = 14.0 * onesec * in_to_si - self.inputs["t_flange"] = 1.0 * onesec * in_to_si - self.inputs["w_flange"] = 10.0 * onesec * in_to_si - self.inputs["L_stiffener"] = 5.0 * onesec * ft_to_si - # self.inputs['section_height'] = 50.0 * onesec0 * ft_to_si - self.inputs["pressure"] = (64.0 * lbperft3_to_si) * g * (60 * ft_to_si) * onepts - self.inputs["E_full"] = 29e3 * ksi_to_si * onesec - self.inputs["nu_full"] = 0.3 * onesec - self.inputs["sigma_y_full"] = 50 * ksi_to_si * onesec - self.inputs["wave_height"] = 0.0 # gives only static pressure - self.inputs["stack_mass_in"] = 9000 * kip_to_si / g - self.inputs["section_mass"] = 0.0 * np.ones((NPTS - 1,)) - self.discrete_inputs["loading"] = "radial" - self.inputs["z_full"] = np.linspace(0, 1, NPTS) - self.inputs["z_section"], _ = nodal2sectional(self.inputs["z_full"]) - self.inputs["z_param"] = np.linspace(0, 1, NHEIGHT) - opt = {} - opt["gamma_f"] = 1.0 - opt["gamma_b"] = 1.0 - - self.buckle = column.ColumnBuckling(n_height=NHEIGHT, modeling_options=opt) - - def testAppliedAxial(self): - t = self.inputs["t_full"][0] - d = self.inputs["d_full"][0] - kip_to_si = 4.4482216 * 1e3 - expect = 9000 * kip_to_si / (2 * np.pi * t * (0.5 * d - 0.5 * t)) - npt.assert_almost_equal(self.buckle.compute_applied_axial(self.inputs), expect, decimal=4) - - def testCheckStresses(self): - self.buckle.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - - npt.assert_almost_equal(self.outputs["web_compactness"], 24.1 / 22.4, decimal=3) - npt.assert_almost_equal(self.outputs["flange_compactness"], 9.03 / 5.0, decimal=3) - self.assertAlmostEqual(self.outputs["axial_local_api"][1], 1.07, 1) - self.assertAlmostEqual(self.outputs["axial_general_api"][1], 0.34, 1) - self.assertAlmostEqual(self.outputs["external_local_api"][1], 1.07, 1) - self.assertAlmostEqual(self.outputs["external_general_api"][1], 0.59, 1) - - -class TestGroup(unittest.TestCase): - def testAll(self): - opt = {} - opt["gamma_f"] = 1.0 - opt["gamma_b"] = 1.0 - opt["materials"] = {} - opt["materials"]["n_mat"] = 1 - colopt = {} - colopt["n_height"] = 3 - colopt["n_bulkhead"] = 3 - colopt["n_layers"] = 1 - - prob = om.Problem() - - prob.model.add_subsystem( - "col", column.Column(column_options=colopt, modeling_options=opt, n_mat=1), promotes=["*"] - ) - - prob.setup() - prob["freeboard"] = 15.0 - prob["height"] = 50.0 - prob["s"] = np.array([0.0, 0.4, 1.0]) - prob["outer_diameter_in"] = 10.0 * np.ones(3) - prob["layer_thickness"] = 0.05 * np.ones((1, 2)) - prob["stiffener_web_height"] = np.ones(2) - prob["stiffener_web_thickness"] = 0.5 * np.ones(2) - prob["stiffener_flange_width"] = 2.0 * np.ones(2) - prob["stiffener_flange_thickness"] = 0.3 * np.ones(2) - prob["stiffener_spacing"] = 0.1 * np.ones(2) - prob["bulkhead_thickness"] = 0.05 * np.ones(3) - prob["bulkhead_locations"] = np.array([0.0, 0.5, 1.0]) - prob["permanent_ballast_height"] = 1.0 - prob["buoyancy_tank_diameter"] = 15.0 - prob["buoyancy_tank_height"] = 0.25 - prob["buoyancy_tank_location"] = 0.3 - prob["rho_water"] = 1e3 - prob["mu_water"] = 1e-5 - prob["water_depth"] = 100.0 - prob["permanent_ballast_density"] = 2e4 - prob["beta_wave"] = 0.0 - prob["wave_z0"] = -100.0 - prob["Hsig_wave"] = 5.0 - prob["wind_z0"] = 0.0 - prob["zref"] = 100.0 - prob["Uref"] = 10.0 - prob["rho_air"] = 1.0 - prob["mu_air"] = 1e-5 - - prob["Tsig_wave"] = 10.0 - prob["outfitting_factor"] = 1.05 - prob["ballast_cost_rate"] = 5.0 - prob["unit_cost_mat"] = np.array([2.0]) - prob["labor_cost_rate"] = 10.0 - prob["painting_cost_rate"] = 20.0 - prob["outfitting_cost_rate"] = 300.0 - prob["loading"] = "hydrostatic" - prob["shearExp"] = 0.1 - prob["beta_wind"] = 0.0 - prob["cd_usr"] = -1.0 - prob["cm"] = 0.0 - prob["Uc"] = 0.0 - prob["yaw"] = 0.0 - prob["rho_mat"] = np.array([1e4]) - prob["E_mat"] = 2e9 * np.ones((1, 3)) - nu = 0.3 - prob["G_mat"] = 0.5 * prob["E_mat"] / (1 + nu) - prob["sigma_y_mat"] = np.array([3e6]) - prob["max_draft"] = 70.0 - - prob.run_model() - self.assertTrue(True) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestInputs)) - suite.addTest(unittest.makeSuite(TestBulk)) - suite.addTest(unittest.makeSuite(TestBuoyancyTank)) - suite.addTest(unittest.makeSuite(TestStiff)) - suite.addTest(unittest.makeSuite(TestBallast)) - suite.addTest(unittest.makeSuite(TestGeometry)) - suite.addTest(unittest.makeSuite(TestProperties)) - suite.addTest(unittest.makeSuite(TestBuckle)) - suite.addTest(unittest.makeSuite(TestGroup)) - return suite - - -if __name__ == "__main__": - result = unittest.TextTestRunner().run(suite()) - - if result.wasSuccessful(): - exit(0) - else: - exit(1) diff --git a/wisdem/test/test_floatingse/test_floating.py b/wisdem/test/test_floatingse/test_floating.py index d14ac1617..0a8ce733d 100644 --- a/wisdem/test/test_floatingse/test_floating.py +++ b/wisdem/test/test_floatingse/test_floating.py @@ -1,194 +1,141 @@ import unittest import numpy as np -import pytest import numpy.testing as npt from openmdao.api import Problem from wisdem.floatingse.floating import FloatingSE npts = 5 -nsection = npts - 1 class TestOC3Mass(unittest.TestCase): def testMassPropertiesSpar(self): opt = {} - opt["platform"] = {} - opt["platform"]["columns"] = {} - opt["platform"]["columns"]["main"] = {} - opt["platform"]["columns"]["offset"] = {} - opt["platform"]["columns"]["main"]["n_height"] = npts - opt["platform"]["columns"]["main"]["n_layers"] = 1 - opt["platform"]["columns"]["main"]["n_bulkhead"] = 4 - opt["platform"]["columns"]["main"]["buckling_length"] = 30.0 - opt["platform"]["columns"]["offset"]["n_height"] = npts - opt["platform"]["columns"]["offset"]["n_layers"] = 1 - opt["platform"]["columns"]["offset"]["n_bulkhead"] = 4 - opt["platform"]["columns"]["offset"]["buckling_length"] = 30.0 - opt["platform"]["tower"] = {} - opt["platform"]["tower"]["buckling_length"] = 30.0 - opt["platform"]["frame3dd"] = {} - opt["platform"]["frame3dd"]["shear"] = True - opt["platform"]["frame3dd"]["geom"] = False - opt["platform"]["frame3dd"]["dx"] = -1 - # opt['platform']['frame3dd']['nM'] = 2 - opt["platform"]["frame3dd"]["Mmethod"] = 1 - opt["platform"]["frame3dd"]["lump"] = 0 - opt["platform"]["frame3dd"]["tol"] = 1e-6 - # opt['platform']['frame3dd']['shift'] = 0.0 - opt["platform"]["gamma_f"] = 1.35 # Safety factor on loads - opt["platform"]["gamma_m"] = 1.3 # Safety factor on materials - opt["platform"]["gamma_n"] = 1.0 # Safety factor on consequence of failure - opt["platform"]["gamma_b"] = 1.1 # Safety factor on buckling - opt["platform"]["gamma_fatigue"] = 1.755 # Not used - opt["platform"]["run_modal"] = True # Not used - - opt["TowerSE"] = {} - opt["TowerSE"]["n_height_tower"] = npts - opt["TowerSE"]["n_layers_tower"] = 1 - opt["materials"] = {} - opt["materials"]["n_mat"] = 1 + opt["floating"] = {} + opt["WISDEM"] = {} + opt["WISDEM"]["FloatingSE"] = {} + opt["floating"]["members"] = {} + opt["floating"]["members"]["n_members"] = 1 + opt["floating"]["members"]["n_height"] = [npts] + opt["floating"]["members"]["n_bulkheads"] = [4] + opt["floating"]["members"]["n_layers"] = [1] + opt["floating"]["members"]["n_ballasts"] = [0] + opt["floating"]["members"]["n_axial_joints"] = [1] + opt["floating"]["tower"] = {} + opt["floating"]["tower"]["n_height"] = [npts] + opt["floating"]["tower"]["n_bulkheads"] = [0] + opt["floating"]["tower"]["n_layers"] = [1] + opt["floating"]["tower"]["n_ballasts"] = [0] + opt["floating"]["tower"]["n_axial_joints"] = [0] + opt["WISDEM"]["FloatingSE"]["frame3dd"] = {} + opt["WISDEM"]["FloatingSE"]["frame3dd"]["shear"] = True + opt["WISDEM"]["FloatingSE"]["frame3dd"]["geom"] = True + opt["WISDEM"]["FloatingSE"]["frame3dd"]["modal"] = False + opt["WISDEM"]["FloatingSE"]["frame3dd"]["tol"] = 1e-6 + opt["WISDEM"]["FloatingSE"]["gamma_f"] = 1.35 # Safety factor on loads + opt["WISDEM"]["FloatingSE"]["gamma_m"] = 1.3 # Safety factor on materials + opt["WISDEM"]["FloatingSE"]["gamma_n"] = 1.0 # Safety factor on consequence of failure + opt["WISDEM"]["FloatingSE"]["gamma_b"] = 1.1 # Safety factor on buckling + opt["WISDEM"]["FloatingSE"]["gamma_fatigue"] = 1.755 # Not used + opt["WISDEM"]["FloatingSE"]["run_modal"] = True # Not used + opt["mooring"] = {} + opt["mooring"]["n_attach"] = 3 + opt["mooring"]["n_anchors"] = 3 - opt["flags"] = {} - opt["flags"]["monopile"] = False + opt["materials"] = {} + opt["materials"]["n_mat"] = 2 prob = Problem() prob.model = FloatingSE(modeling_options=opt) prob.setup() - # Remove all offset columns - prob["number_of_offset_columns"] = 0 - prob["cross_attachment_pontoons_int"] = 0 - prob["lower_attachment_pontoons_int"] = 0 - prob["upper_attachment_pontoons_int"] = 0 - prob["lower_ring_pontoons_int"] = 0 - prob["upper_ring_pontoons_int"] = 0 - prob["outer_cross_pontoons_int"] = 0 - - # Wind and water properties - prob["rho_air"] = 1.226 # Density of air [kg/m^3] - prob["mu_air"] = 1.78e-5 # Viscosity of air [kg/m/s] - prob["rho_water"] = 1025.0 # Density of water [kg/m^3] - prob["mu_water"] = 1.08e-3 # Viscosity of water [kg/m/s] - # Material properties - prob["rho_mat"] = np.array([7850.0]) # Steel [kg/m^3] - prob["E_mat"] = 200e9 * np.ones((1, 3)) # Young's modulus [N/m^2] - prob["G_mat"] = 79.3e9 * np.ones((1, 3)) # Shear modulus [N/m^2] - prob["sigma_y_mat"] = np.array([3.45e8]) # Elastic yield stress [N/m^2] - prob["permanent_ballast_density"] = 5000.0 # [kg/m^3] + prob["rho_mat"] = np.array([7850.0, 5000.0]) # Steel, ballast slurry [kg/m^3] + prob["E_mat"] = 200e9 * np.ones((2, 3)) # Young's modulus [N/m^2] + prob["G_mat"] = 79.3e9 * np.ones((2, 3)) # Shear modulus [N/m^2] + prob["sigma_y_mat"] = 3.45e8 * np.ones(2) # Elastic yield stress [N/m^2] + prob["unit_cost_mat"] = np.array([2.0, 1.0]) + prob["material_names"] = ["steel", "slurry"] # Mass and cost scaling factors - prob["outfitting_factor"] = 0.0 # Fraction of additional outfitting mass for each column - prob["ballast_cost_rate"] = 0.1 # Cost factor for ballast mass [$/kg] - prob["material_cost_rate"] = 1.1 # Cost factor for column mass [$/kg] prob["labor_cost_rate"] = 1.0 # Cost factor for labor time [$/min] prob["painting_cost_rate"] = 14.4 # Cost factor for column surface finishing [$/m^2] - prob["outfitting_cost_rate"] = 1.5 * 1.1 # Cost factor for outfitting mass [$/kg] - prob["mooring_cost_factor"] = 1.1 # Cost factor for mooring mass [$/kg] + prob["member0.outfitting_factor_in"] = 1.0 # Fraction of additional outfitting mass for each column # Column geometry - prob["main.permanent_ballast_height"] = 0.0 # Height above keel for permanent ballast [m] - prob["main_freeboard"] = 10.0 # Height extension above waterline [m] - - prob["main.height"] = np.sum([49.0, 59.0, 8.0, 14.0]) # Length of each section [m] - prob["main.s"] = np.cumsum([0.0, 49.0, 59.0, 8.0, 14.0]) / prob["main.height"] - prob["main.outer_diameter_in"] = np.array( - [9.4, 9.4, 9.4, 6.5, 6.5] - ) # Diameter at each section node (linear lofting between) [m] - prob["main.layer_thickness"] = 0.05 * np.ones( - (1, nsection) - ) # Shell thickness at each section node (linear lofting between) [m] - - prob["main.bulkhead_thickness"] = 0.05 * np.ones( - 4 - ) # Locations/thickness of internal bulkheads at section interfaces [m] - prob["main.bulkhead_locations"] = np.array( - [0.0, 0.37692308, 0.89230769, 1.0] - ) # Locations/thickness of internal bulkheads at section interfaces [m] - - prob["main.buoyancy_tank_diameter"] = 0.0 - prob["main.buoyancy_tank_height"] = 0.0 - - # Column ring stiffener parameters - prob["main.stiffener_web_height"] = 0.10 * np.ones(nsection) # (by section) [m] - prob["main.stiffener_web_thickness"] = 0.04 * np.ones(nsection) # (by section) [m] - prob["main.stiffener_flange_width"] = 0.10 * np.ones(nsection) # (by section) [m] - prob["main.stiffener_flange_thickness"] = 0.02 * np.ones(nsection) # (by section) [m] - prob["main.stiffener_spacing"] = np.array([1.5, 2.8, 3.0, 5.0]) # (by section) [m] + h = np.array([49.0, 59.0, 8.0, 14.0]) # Length of each section [m] + prob["member0.grid_axial_joints"] = [0.384615] # Fairlead at 70m + # prob["member0.ballast_grid"] = np.empy((0,2)) + # prob["member0.ballast_volume"] = np.empty(0) + prob["member0.s"] = np.cumsum(np.r_[0, h]) / h.sum() + prob["member0.outer_diameter_in"] = np.array([9.4, 9.4, 9.4, 6.5, 6.5]) + prob["member0.layer_thickness"] = 0.05 * np.ones((1, npts)) + prob["member0.layer_materials"] = ["steel"] + prob["member0.ballast_materials"] = ["slurry", "seawater"] + prob["member0.joint1"] = np.array([0.0, 0.0, 10.0 - h.sum()]) + prob["member0.joint2"] = np.array([0.0, 0.0, 10.0]) # Freeboard=10 + prob["member0.s_ghost1"] = 0.0 + prob["member0.s_ghost2"] = 1.0 + prob["member0.transition_flag"] = [False, True] + prob["member0.bulkhead_thickness"] = 0.05 * np.ones(4) # Locations of internal bulkheads + prob["member0.bulkhead_grid"] = np.array([0.0, 0.37692308, 0.89230769, 1.0]) # Thickness of internal bulkheads + prob["member0.ring_stiffener_web_height"] = 0.10 + prob["member0.ring_stiffener_web_thickness"] = 0.04 + prob["member0.ring_stiffener_flange_width"] = 0.10 + prob["member0.ring_stiffener_flange_thickness"] = 0.02 + prob["member0.ring_stiffener_spacing"] = 2.15 # Mooring parameters - prob["number_of_mooring_connections"] = 3 # Evenly spaced around structure - prob["mooring_lines_per_connection"] = 1 # Evenly spaced around structure - prob["mooring_type"] = "chain" # Options are chain, nylon, polyester, fiber, or iwrc - prob["anchor_type"] = "suctionpile" # Options are SUCTIONPILE or DRAGEMBEDMENT - prob["mooring_diameter"] = 0.09 # Diameter of mooring line/chain [m] - prob["fairlead_location"] = 0.384615 # Want 70.0 # Distance below waterline for attachment [m] - prob["fairlead_offset_from_shell"] = 0.5 # Offset from shell surface for mooring attachment [m] - prob["mooring_line_length"] = 300 + 902.2 # Unstretched mooring line length + prob["line_diameter"] = 0.09 # Diameter of mooring line/chain [m] + prob["line_length"] = 300 + 902.2 # Unstretched mooring line length + prob["line_mass_density_coeff"] = 19.9e3 + prob["line_stiffness_coeff"] = 8.54e10 + prob["line_breaking_load_coeff"] = 176972.7 + prob["line_cost_rate_coeff"] = 3.415e4 + prob["fairlead_radius"] = 10.0 # Offset from shell surface for mooring attachment [m] + prob["fairlead"] = 70.0 prob["anchor_radius"] = 853.87 # Distance from centerline to sea floor landing [m] - prob["fairlead_support_outer_diameter"] = 3.2 # Diameter of all fairlead support elements [m] - prob["fairlead_support_wall_thickness"] = 0.0175 # Thickness of all fairlead support elements [m] + prob["anchor_cost"] = 1e5 # Mooring constraints - prob["max_offset"] = 0.1 * prob["water_depth"] # Max surge/sway offset [m] - prob["max_survival_heel"] = 10.0 # Max heel (pitching) angle [deg] + prob["max_surge_fraction"] = 0.1 # Max surge/sway offset [m] + prob["survival_heel"] = 10.0 # Max heel (pitching) angle [deg] prob["operational_heel"] = 5.0 # Max heel (pitching) angle [deg] - # Design constraints - prob["max_draft"] = 200.0 # For manufacturability of rolling steel - - # API 2U flag - prob["loading"] = "axial" #'hydrostatic' - - # Other variables to avoid divide by zeros, even though it won't matter - prob["radius_to_offset_column"] = 15.0 - prob["off.height"] = 1.0 - prob["off.s"] = np.linspace(0, 1, nsection + 1) - prob["off.outer_diameter_in"] = 5.0 * np.ones(nsection + 1) - prob["off.layer_thickness"] = 0.1 * np.ones((1, nsection)) - prob["off.permanent_ballast_height"] = 0.1 - prob["off.stiffener_web_height"] = 0.1 * np.ones(nsection) - prob["off.stiffener_web_thickness"] = 0.1 * np.ones(nsection) - prob["off.stiffener_flange_width"] = 0.1 * np.ones(nsection) - prob["off.stiffener_flange_thickness"] = 0.1 * np.ones(nsection) - prob["off.stiffener_spacing"] = 0.1 * np.ones(nsection) - prob["offset_freeboard"] = 0.1 - prob["pontoon_outer_diameter"] = 1.0 - prob["pontoon_wall_thickness"] = 0.1 - # Set environment to that used in OC3 testing campaign + # prob["rho_air"] = 1.226 # Density of air [kg/m^3] + # prob["mu_air"] = 1.78e-5 # Viscosity of air [kg/m/s] + prob["rho_water"] = 1025.0 # Density of water [kg/m^3] + # prob["mu_water"] = 1.08e-3 # Viscosity of water [kg/m/s] prob["water_depth"] = 320.0 # Distance to sea floor [m] - prob["Hsig_wave"] = 0.0 # Significant wave height [m] - prob["Tsig_wave"] = 1e3 # Wave period [s] - prob["shearExp"] = 0.11 # Shear exponent in wind power law - prob["cm"] = 2.0 # Added mass coefficient - prob["Uc"] = 0.0 # Mean current speed - prob["yaw"] = 0.0 # Turbine yaw angle - prob["beta_wind"] = prob["beta_wave"] = 0.0 - prob["cd_usr"] = -1.0 # Compute drag coefficient - prob["Uref"] = 10.0 - prob["zref"] = 100.0 + # prob["Hsig_wave"] = 0.0 # Significant wave height [m] + # prob["Tsig_wave"] = 1e3 # Wave period [s] + # prob["shearExp"] = 0.11 # Shear exponent in wind power law + # prob["cm"] = 2.0 # Added mass coefficient + # prob["Uc"] = 0.0 # Mean current speed + # prob["yaw"] = 0.0 # Turbine yaw angle + # prob["beta_wind"] = prob["beta_wave"] = 0.0 + # prob["cd_usr"] = -1.0 # Compute drag coefficient + # prob["Uref"] = 10.0 + # prob["zref"] = 100.0 # Porperties of turbine tower - nTower = prob.model.options["modeling_options"]["TowerSE"]["n_height_tower"] - 1 - prob["tower_height"] = prob["hub_height"] = 77.6 - prob["tower_s"] = np.linspace(0.0, 1.0, nTower + 1) - prob["tower_outer_diameter_in"] = np.linspace(6.5, 3.87, nTower + 1) - prob["tower_layer_thickness"] = np.linspace(0.027, 0.019, nTower + 1).reshape((1, nTower + 1)) - prob["tower_outfitting_factor"] = 1.07 - - # Materials - prob["material_names"] = ["steel"] - prob["main.layer_materials"] = prob["off.layer_materials"] = prob["tow.tower_layer_materials"] = ["steel"] + nTower = prob.model.options["modeling_options"]["floating"]["tower"]["n_height"][0] + prob["hub_height"] = 85.0 + prob["distance_tt_hub"] = 5.0 + prob["tower.s"] = np.linspace(0.0, 1.0, nTower) + prob["tower.outer_diameter_in"] = np.linspace(6.5, 3.87, nTower) + prob["tower.layer_thickness"] = np.linspace(0.027, 0.019, nTower).reshape((1, nTower)) + prob["tower.layer_materials"] = ["steel"] + prob["tower.outfitting_factor"] = 1.07 # Properties of rotor-nacelle-assembly (RNA) prob["rna_mass"] = 350e3 # Mass [kg] prob["rna_I"] = 1e5 * np.array([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]) prob["rna_cg"] = np.zeros(3) - prob["rna_force"] = np.zeros(3) - prob["rna_moment"] = np.zeros(3) + prob["rna_F"] = np.zeros(3) + prob["rna_M"] = np.zeros(3) prob.run_model() @@ -197,20 +144,20 @@ def testMassPropertiesSpar(self): ansys_m_shell = 80150.0 + 32060.0 + 79701.0 + 1251800.0 ansys_m_stiff = 1390.9 * 52 + 1282.2 + 1121.2 + 951.44 * 3 ansys_m_spar = ansys_m_bulk + ansys_m_shell + ansys_m_stiff - ansys_cg = -58.926 - ansys_Ixx = 2178400000.0 + m_top * (0.25 * 3.2 ** 2.0 + (10 - ansys_cg) ** 2) - ansys_Iyy = 2178400000.0 + m_top * (0.25 * 3.2 ** 2.0 + (10 - ansys_cg) ** 2) + ansys_cg = np.array([0.0, 0.0, -58.926]) + ansys_Ixx = 2178400000.0 + m_top * (0.25 * 3.2 ** 2.0 + (10 - ansys_cg[-1]) ** 2) + ansys_Iyy = 2178400000.0 + m_top * (0.25 * 3.2 ** 2.0 + (10 - ansys_cg[-1]) ** 2) ansys_Izz = 32297000.0 + 0.5 * m_top * 3.2 ** 2.0 ansys_I = np.array([ansys_Ixx, ansys_Iyy, ansys_Izz, 0.0, 0.0, 0.0]) npt.assert_allclose( - ansys_m_bulk, prob["main.bulkhead_mass"].sum(), rtol=0.03 + ansys_m_bulk, prob["member0.bulkhead_mass"].sum(), rtol=0.03 ) # ANSYS uses R_od, we use R_id, top cover seems unaccounted for - npt.assert_allclose(ansys_m_shell, prob["main.cyl_mass.mass"].sum(), rtol=0.01) - npt.assert_allclose(ansys_m_stiff, prob["main.stiffener_mass"].sum(), rtol=0.01) - npt.assert_allclose(ansys_m_spar, prob["main.column_total_mass"].sum(), rtol=0.01) - npt.assert_allclose(ansys_cg, prob["main.z_center_of_mass"], rtol=0.01) - npt.assert_allclose(ansys_I, prob["main.I_column"], rtol=0.02) + npt.assert_allclose(ansys_m_shell, prob["member0.shell_mass"].sum(), rtol=0.01) + npt.assert_allclose(ansys_m_stiff, prob["member0.stiffener_mass"].sum(), rtol=0.01) + npt.assert_allclose(ansys_m_spar, prob["member0.total_mass"].sum(), rtol=0.01) + npt.assert_allclose(ansys_cg, prob["member0.center_of_mass"], rtol=0.02) + npt.assert_allclose(ansys_I, prob["member0.I_total"], rtol=0.02) def suite(): diff --git a/wisdem/test/test_floatingse/test_frame.py b/wisdem/test/test_floatingse/test_frame.py new file mode 100644 index 000000000..b1e93be71 --- /dev/null +++ b/wisdem/test/test_floatingse/test_frame.py @@ -0,0 +1,411 @@ +import unittest + +import numpy as np +import openmdao.api as om +import numpy.testing as npt +import wisdem.floatingse.floating_frame as frame +from wisdem.commonse import gravity as g +from wisdem.floatingse.member import NULL, MEMMAX + + +class TestPlatform(unittest.TestCase): + def setUp(self): + self.inputs = {} + self.outputs = {} + self.discrete_inputs = {} + self.discrete_outputs = {} + + self.opt = {} + self.opt["floating"] = {} + self.opt["WISDEM"] = {} + self.opt["WISDEM"]["FloatingSE"] = {} + self.opt["floating"]["members"] = {} + self.opt["floating"]["members"]["n_members"] = n_member = 6 + self.opt["WISDEM"]["FloatingSE"]["frame3dd"] = {} + self.opt["WISDEM"]["FloatingSE"]["frame3dd"]["shear"] = True + self.opt["WISDEM"]["FloatingSE"]["frame3dd"]["geom"] = True + self.opt["WISDEM"]["FloatingSE"]["frame3dd"]["tol"] = 1e-8 + self.opt["WISDEM"]["FloatingSE"]["frame3dd"]["modal"] = False + self.opt["mooring"] = {} + self.opt["mooring"]["n_attach"] = 3 + + self.inputs["tower_nodes"] = NULL * np.ones((MEMMAX, 3)) + self.inputs["tower_Rnode"] = NULL * np.ones(MEMMAX) + for k in range(n_member): + self.inputs["member" + str(k) + ":nodes_xyz"] = NULL * np.ones((MEMMAX, 3)) + self.inputs["member" + str(k) + ":nodes_r"] = NULL * np.ones(MEMMAX) + + for var in ["D", "t", "A", "Asx", "Asy", "Ixx", "Iyy", "Izz", "rho", "E", "G"]: + self.inputs["tower_elem_" + var] = NULL * np.ones(MEMMAX) + for k in range(n_member): + self.inputs["member" + str(k) + ":section_" + var] = NULL * np.ones(MEMMAX) + + self.inputs["member0:nodes_xyz"][:2, :] = np.array([[0, 0, 0], [1, 0, 0]]) + self.inputs["member1:nodes_xyz"][:2, :] = np.array([[1, 0, 0], [0.5, 1, 0]]) + self.inputs["member2:nodes_xyz"][:2, :] = np.array([[0.5, 1, 0], [0, 0, 0]]) + self.inputs["member3:nodes_xyz"][:2, :] = np.array([[0, 0, 0], [0, 0, 1]]) + self.inputs["member4:nodes_xyz"][:2, :] = np.array([[1, 0, 0], [0, 0, 1]]) + self.inputs["member5:nodes_xyz"][:2, :] = np.array([[0.5, 1, 0], [0, 0, 1]]) + for k in range(n_member): + L = np.sqrt(np.sum(np.diff(self.inputs["member" + str(k) + ":nodes_xyz"][:2, :], axis=0) ** 2)) + self.inputs["member" + str(k) + ":nodes_r"][:2] = 0.1 * k * np.ones(2) + self.inputs["member" + str(k) + ":section_D"][:1] = 2.0 + self.inputs["member" + str(k) + ":section_t"][:1] = 0.1 + self.inputs["member" + str(k) + ":section_A"][:1] = 0.5 * k * np.ones(1) + 1 + self.inputs["member" + str(k) + ":section_Asx"][:1] = 0.5 * k * np.ones(1) + 1 + self.inputs["member" + str(k) + ":section_Asy"][:1] = 0.5 * k * np.ones(1) + 1 + self.inputs["member" + str(k) + ":section_Ixx"][:1] = 2 * k * np.ones(1) + 1 + self.inputs["member" + str(k) + ":section_Iyy"][:1] = 2 * k * np.ones(1) + 1 + self.inputs["member" + str(k) + ":section_Izz"][:1] = 2 * k * np.ones(1) + 1 + self.inputs["member" + str(k) + ":section_rho"][:1] = 1e3 / (0.5 * k * np.ones(1) + 1) / L + self.inputs["member" + str(k) + ":section_E"][:1] = 3 * k * np.ones(1) + 1 + self.inputs["member" + str(k) + ":section_G"][:1] = 4 * k * np.ones(1) + 1 + self.inputs["member" + str(k) + ":idx_cb"] = 0 + self.inputs["member" + str(k) + ":buoyancy_force"] = 1e2 + self.inputs["member" + str(k) + ":displacement"] = 1e1 + self.inputs["member" + str(k) + ":center_of_buoyancy"] = self.inputs["member" + str(k) + ":nodes_xyz"][ + :2, : + ].mean(axis=0) + self.inputs["member" + str(k) + ":center_of_mass"] = self.inputs["member" + str(k) + ":nodes_xyz"][ + :2, : + ].mean(axis=0) + self.inputs["member" + str(k) + ":total_mass"] = 1e3 + self.inputs["member" + str(k) + ":total_cost"] = 2e3 + self.inputs["member" + str(k) + ":Awater"] = 5.0 + self.inputs["member" + str(k) + ":Iwater"] = 15.0 + self.inputs["member" + str(k) + ":added_mass"] = np.arange(6) + self.inputs["member" + str(k) + ":transition_node"] = NULL * np.ones(3) + + myones = np.ones(2) + self.inputs["tower_nodes"][:3, :] = np.array([[0.0, 0.0, 1.0], [0.0, 0.0, 51.0], [0.0, 0.0, 101.0]]) + # self.inputs["tower_Fnode"] = np.zeros + self.inputs["tower_Rnode"][:3] = np.zeros(3) + # self.inputs["tower_elem_n1"] = [0, 1] + # self.inputs["tower_elem_n2"] = [1, 2] + self.inputs["tower_elem_D"][:2] = 2.0 * myones + self.inputs["tower_elem_t"][:2] = 0.1 * myones + self.inputs["tower_elem_A"][:2] = 4.0 * myones + self.inputs["tower_elem_Asx"][:2] = 4.1 * myones + self.inputs["tower_elem_Asy"][:2] = 4.1 * myones + self.inputs["tower_elem_Ixx"][:2] = 5.0 * myones + self.inputs["tower_elem_Iyy"][:2] = 5.0 * myones + self.inputs["tower_elem_Izz"][:2] = 10.0 * myones + self.inputs["tower_elem_rho"][:2] = 5e3 / 4 / 100 * myones + self.inputs["tower_elem_E"][:2] = 30.0 * myones + self.inputs["tower_elem_G"][:2] = 40.0 * myones + self.inputs["tower_center_of_mass"] = self.inputs["tower_nodes"][:3, :].mean(axis=0) + self.inputs["tower_mass"] = 5e3 + + self.inputs["mooring_neutral_load"] = np.zeros((3, 3)) + self.inputs["mooring_neutral_load"][:, 0] = [200, -100.0, -100] + self.inputs["mooring_neutral_load"][:, 1] = [0.0, 50, -50] + self.inputs["mooring_neutral_load"][:, 2] = -1e3 + self.inputs["mooring_fairlead_joints"] = np.array([[0.0, 0.0, 0.0], [0.5, 1.0, 0.0], [1.0, 0.0, 0.0]]) + self.inputs["transition_node"] = self.inputs["tower_nodes"][0, :] + self.inputs["hub_node"] = self.inputs["tower_nodes"][2, :] + self.inputs["rna_mass"] = 1e4 + self.inputs["rna_cg"] = np.ones(3) + self.inputs["rna_I"] = 1e4 * np.arange(6) + self.inputs["rna_F"] = np.array([1e2, 1e1, 0.0]) + self.inputs["rna_M"] = np.array([2e1, 2e2, 0.0]) + self.inputs["transition_piece_mass"] = 1e3 + self.inputs["rho_water"] = 1e3 + + def testTetrahedron(self): + myobj = frame.PlatformFrame(options=self.opt) + myobj.node_mem2glob = {} + myobj.node_glob2mem = {} + myobj.compute(self.inputs, self.outputs) + + # Check NULLs and implied number of nodes / elements + npt.assert_equal(self.outputs["platform_nodes"][4:, :], NULL) + npt.assert_equal(self.outputs["platform_Fnode"][4:, :], NULL) + npt.assert_equal(self.outputs["platform_Rnode"][4:], NULL) + npt.assert_equal(self.outputs["platform_elem_n1"][6:], NULL) + npt.assert_equal(self.outputs["platform_elem_n2"][6:], NULL) + npt.assert_equal(self.outputs["platform_elem_D"][6:], NULL) + npt.assert_equal(self.outputs["platform_elem_t"][6:], NULL) + npt.assert_equal(self.outputs["platform_elem_A"][6:], NULL) + npt.assert_equal(self.outputs["platform_elem_Asx"][6:], NULL) + npt.assert_equal(self.outputs["platform_elem_Asy"][6:], NULL) + npt.assert_equal(self.outputs["platform_elem_Ixx"][6:], NULL) + npt.assert_equal(self.outputs["platform_elem_Iyy"][6:], NULL) + npt.assert_equal(self.outputs["platform_elem_Izz"][6:], NULL) + npt.assert_equal(self.outputs["platform_elem_rho"][6:], NULL) + npt.assert_equal(self.outputs["platform_elem_E"][6:], NULL) + npt.assert_equal(self.outputs["platform_elem_G"][6:], NULL) + + npt.assert_equal( + self.outputs["platform_nodes"][:4, :], + np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.5, 1.0, 0.0], [1.0, 0.0, 0.0]]), + ) + npt.assert_equal( + self.outputs["platform_Fnode"][:4, :], + 1e2 * np.array([[0.0, 0.0, 2], [0.0, 0.0, 0.0], [0.0, 0.0, 2], [0.0, 0.0, 2.0]]), + ) + npt.assert_equal(self.outputs["platform_Rnode"][:4], 0.1 * np.r_[3, 5, 5, 4]) + npt.assert_equal(self.outputs["platform_elem_n1"][:6], np.r_[0, 3, 2, 0, 3, 2]) + npt.assert_equal(self.outputs["platform_elem_n2"][:6], np.r_[3, 2, 0, 1, 1, 1]) + npt.assert_equal(self.outputs["platform_elem_D"][:6], 2.0) + npt.assert_equal(self.outputs["platform_elem_t"][:6], 0.1) + npt.assert_equal(self.outputs["platform_elem_A"][:6], 0.5 * np.arange(6) + 1) + npt.assert_equal(self.outputs["platform_elem_Asx"][:6], 0.5 * np.arange(6) + 1) + npt.assert_equal(self.outputs["platform_elem_Asy"][:6], 0.5 * np.arange(6) + 1) + npt.assert_equal(self.outputs["platform_elem_Ixx"][:6], 2 * np.arange(6) + 1) + npt.assert_equal(self.outputs["platform_elem_Iyy"][:6], 2 * np.arange(6) + 1) + npt.assert_equal(self.outputs["platform_elem_Izz"][:6], 2 * np.arange(6) + 1) + # npt.assert_equal(self.outputs["platform_elem_rho"][:6], 3 * np.arange(6)+1) + npt.assert_equal(self.outputs["platform_elem_E"][:6], 3 * np.arange(6) + 1) + npt.assert_equal(self.outputs["platform_elem_G"][:6], 4 * np.arange(6) + 1) + self.assertEqual(self.outputs["platform_displacement"], 6e1) + npt.assert_equal( + self.outputs["platform_center_of_buoyancy"], self.outputs["platform_nodes"][:4, :].mean(axis=0) + ) + npt.assert_equal(self.outputs["platform_center_of_mass"], self.outputs["platform_nodes"][:4, :].mean(axis=0)) + self.assertEqual(self.outputs["platform_mass"], 6e3) + self.assertEqual(self.outputs["platform_cost"], 6 * 2e3) + self.assertEqual(self.outputs["platform_Awater"], 30) + self.assertEqual(self.outputs["platform_Iwater"], 6 * 15) + npt.assert_equal(self.outputs["platform_added_mass"], 6 * np.arange(6)) + # Should find a transition mode even though one wasn't set + npt.assert_equal(self.outputs["transition_node"], [0.0, 0.0, 1.0]) + + # Test with set transition node + self.inputs["member1:transition_node"] = [0.5, 1.0, 0.0] + myobj.node_mem2glob = {} + myobj.node_glob2mem = {} + myobj.compute(self.inputs, self.outputs) + npt.assert_equal(self.outputs["transition_node"], [0.5, 1.0, 0.0]) + + def testPre(self): + inputs = {} + outputs = {} + inputs["transition_node"] = np.array([1, 1, 2]) + inputs["hub_height"] = 100.0 + inputs["distance_tt_hub"] = 5.0 + myobj = frame.TowerPreMember() + myobj.compute(inputs, outputs) + npt.assert_equal(outputs["hub_node"], np.array([1, 1, 95])) + + def testPlatformTower(self): + myobj = frame.PlatformFrame(options=self.opt) + myobj.node_mem2glob = {} + myobj.node_glob2mem = {} + myobj.compute(self.inputs, self.outputs) + for k in self.outputs: + self.inputs[k] = self.outputs[k] + for k in self.discrete_outputs: + self.discrete_inputs[k] = self.discrete_outputs[k] + myobj = frame.PlatformTowerFrame() + myobj.compute(self.inputs, self.outputs) + + npt.assert_equal( + self.outputs["system_nodes"][:6, :], + np.array( + [ + [0.0, 0.0, 0.0], + [0.0, 0.0, 1.0], + [0.5, 1.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 0.0, 51.0], + [0.0, 0.0, 101.0], + ] + ), + ) + npt.assert_equal( + self.outputs["system_Fnode"][:6, :], + 1e2 + * np.array( + [[0.0, 0.0, 2], [0.0, 0.0, 0.0], [0.0, 0.0, 2], [0.0, 0.0, 2.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] + ), + ) + npt.assert_equal(self.outputs["system_Rnode"][:6], 0.1 * np.r_[3, 5, 5, 4, 0, 0]) + npt.assert_equal(self.outputs["system_elem_n1"][:8], np.r_[0, 3, 2, 0, 3, 2, 1, 4]) + npt.assert_equal(self.outputs["system_elem_n2"][:8], np.r_[3, 2, 0, 1, 1, 1, 4, 5]) + npt.assert_equal(self.outputs["system_elem_D"][:8], 2.0) + npt.assert_equal(self.outputs["system_elem_t"][:8], 0.1) + npt.assert_equal(self.outputs["system_elem_A"][:8], np.r_[0.5 * np.arange(6) + 1, 4, 4]) + npt.assert_equal(self.outputs["system_elem_Asx"][:8], np.r_[0.5 * np.arange(6) + 1, 4.1, 4.1]) + npt.assert_equal(self.outputs["system_elem_Asy"][:8], np.r_[0.5 * np.arange(6) + 1, 4.1, 4.1]) + npt.assert_equal(self.outputs["system_elem_Ixx"][:8], np.r_[2 * np.arange(6) + 1, 5, 5]) + npt.assert_equal(self.outputs["system_elem_Iyy"][:8], np.r_[2 * np.arange(6) + 1, 5, 5]) + npt.assert_equal(self.outputs["system_elem_Izz"][:8], np.r_[2 * np.arange(6) + 1, 10, 10]) + # npt.assert_equal(self.outputs["system_elem_rho"][:8], np.r_[3 * np.arange(6)+1, 20, 20]) + npt.assert_equal(self.outputs["system_elem_E"][:8], np.r_[3 * np.arange(6) + 1, 30, 30]) + npt.assert_equal(self.outputs["system_elem_G"][:8], np.r_[4 * np.arange(6) + 1, 40, 40]) + self.assertEqual(self.outputs["system_mass"], 6e3 + 5e3 + 1e4 + 1e3) + self.assertEqual(self.outputs["variable_ballast_mass"], 6e4 - self.outputs["system_mass"]) + npt.assert_equal( + self.outputs["system_center_of_mass"], + ( + 6e3 * np.array([0.375, 0.25, 0.25]) + + 5e3 * np.array([0.0, 0.0, 51.0]) + + 1e3 * np.array([0.0, 0.0, 1.0]) + + 1e4 * np.array([1.0, 1.0, 102.0]) + ) + / 2.2e4, + ) + npt.assert_equal(self.outputs["transition_piece_I"], 1e3 * 0.25 * np.r_[0.5, 0.5, 1.0, np.zeros(3)]) + + def testRunFrame(self): + myobj = frame.PlatformFrame(options=self.opt) + myobj.node_mem2glob = {} + myobj.node_glob2mem = {} + myobj.compute(self.inputs, self.outputs) + for k in self.outputs: + self.inputs[k] = self.outputs[k] + for k in self.discrete_outputs: + self.discrete_inputs[k] = self.discrete_outputs[k] + myobj = frame.PlatformTowerFrame() + myobj.compute(self.inputs, self.outputs) + for k in self.outputs: + self.inputs[k] = self.outputs[k] + for k in self.discrete_outputs: + self.discrete_inputs[k] = self.discrete_outputs[k] + myobj = frame.FrameAnalysis(options=self.opt) + myobj.compute(self.inputs, self.outputs) + self.assertTrue(True) + + +class TestGroup(unittest.TestCase): + def testRunAll(self): + + opt = {} + opt["floating"] = {} + opt["WISDEM"] = {} + opt["WISDEM"]["FloatingSE"] = {} + opt["floating"]["members"] = {} + opt["floating"]["members"]["n_members"] = n_member = 6 + opt["floating"]["members"]["n_height"] = [2] + opt["floating"]["members"]["n_bulkheads"] = [4] + opt["floating"]["members"]["n_layers"] = [1] + opt["floating"]["members"]["n_ballasts"] = [0] + opt["floating"]["members"]["n_axial_joints"] = [1] + opt["floating"]["tower"] = {} + opt["floating"]["tower"]["n_height"] = [3] + opt["floating"]["tower"]["n_bulkheads"] = [0] + opt["floating"]["tower"]["n_layers"] = [1] + opt["floating"]["tower"]["n_ballasts"] = [0] + opt["floating"]["tower"]["n_axial_joints"] = [0] + opt["WISDEM"]["FloatingSE"]["frame3dd"] = {} + opt["WISDEM"]["FloatingSE"]["frame3dd"]["shear"] = True + opt["WISDEM"]["FloatingSE"]["frame3dd"]["geom"] = True + opt["WISDEM"]["FloatingSE"]["frame3dd"]["tol"] = 1e-7 + opt["WISDEM"]["FloatingSE"]["frame3dd"]["modal"] = False # True + opt["WISDEM"]["FloatingSE"]["gamma_f"] = 1.35 # Safety factor on loads + opt["WISDEM"]["FloatingSE"]["gamma_m"] = 1.3 # Safety factor on materials + opt["WISDEM"]["FloatingSE"]["gamma_n"] = 1.0 # Safety factor on consequence of failure + opt["WISDEM"]["FloatingSE"]["gamma_b"] = 1.1 # Safety factor on buckling + opt["WISDEM"]["FloatingSE"]["gamma_fatigue"] = 1.755 # Not used + opt["mooring"] = {} + opt["mooring"]["n_attach"] = 3 + opt["mooring"]["n_anchors"] = 3 + + opt["materials"] = {} + opt["materials"]["n_mat"] = 2 + + prob = om.Problem() + prob.model = frame.FloatingFrame(modeling_options=opt) + prob.setup() + + # Material properties + prob["rho_mat"] = np.array([7850.0, 5000.0]) # Steel, ballast slurry [kg/m^3] + prob["E_mat"] = 200e9 * np.ones((2, 3)) # Young's modulus [N/m^2] + prob["G_mat"] = 79.3e9 * np.ones((2, 3)) # Shear modulus [N/m^2] + prob["sigma_y_mat"] = 3.45e8 * np.ones(2) # Elastic yield stress [N/m^2] + prob["unit_cost_mat"] = np.array([2.0, 1.0]) + prob["material_names"] = ["steel", "slurry"] + + # Mass and cost scaling factors + prob["labor_cost_rate"] = 1.0 # Cost factor for labor time [$/min] + prob["painting_cost_rate"] = 14.4 # Cost factor for column surface finishing [$/m^2] + + prob["member0:nodes_xyz"][:2, :] = np.array([[0, 0, 0], [1, 0, 0]]) + prob["member1:nodes_xyz"][:2, :] = np.array([[1, 0, 0], [0.5, 1, 0]]) + prob["member2:nodes_xyz"][:2, :] = np.array([[0.5, 1, 0], [0, 0, 0]]) + prob["member3:nodes_xyz"][:2, :] = np.array([[0, 0, 0], [0, 0, 1]]) + prob["member4:nodes_xyz"][:2, :] = np.array([[1, 0, 0], [0, 0, 1]]) + prob["member5:nodes_xyz"][:2, :] = np.array([[0.5, 1, 0], [0, 0, 1]]) + for k in range(n_member): + L = np.sqrt(np.sum(np.diff(prob["member" + str(k) + ":nodes_xyz"][:, 2], axis=0) ** 2)) + prob["member" + str(k) + ":nodes_r"][:2] = 0.1 * k * np.ones(2) + prob["member" + str(k) + ":section_D"][:1] = 2.0 + prob["member" + str(k) + ":section_t"][:1] = 0.1 + prob["member" + str(k) + ":section_A"][:1] = 0.5 * k * np.ones(1) + 1 + prob["member" + str(k) + ":section_Asx"][:1] = 0.5 * k * np.ones(1) + 1 + prob["member" + str(k) + ":section_Asy"][:1] = 0.5 * k * np.ones(1) + 1 + prob["member" + str(k) + ":section_Ixx"][:1] = 2 * k * np.ones(1) + 1 + prob["member" + str(k) + ":section_Iyy"][:1] = 2 * k * np.ones(1) + 1 + prob["member" + str(k) + ":section_Izz"][:1] = 2 * k * np.ones(1) + 1 + prob["member" + str(k) + ":section_rho"][:1] = 1e3 / (0.5 * k * np.ones(1) + 1) / L + prob["member" + str(k) + ":section_E"][:1] = 3 * k * np.ones(1) + 1 + prob["member" + str(k) + ":section_G"][:1] = 4 * k * np.ones(1) + 1 + prob["member" + str(k) + ":idx_cb"] = 0 + prob["member" + str(k) + ":buoyancy_force"] = 1e2 + prob["member" + str(k) + ":displacement"] = 1e1 + prob["member" + str(k) + ":center_of_buoyancy"] = prob["member" + str(k) + ":nodes_xyz"][:2, :].mean(axis=0) + prob["member" + str(k) + ":center_of_mass"] = prob["member" + str(k) + ":nodes_xyz"][:2, :].mean(axis=0) + prob["member" + str(k) + ":total_mass"] = 1e3 + prob["member" + str(k) + ":total_cost"] = 2e3 + prob["member" + str(k) + ":Awater"] = 5.0 + prob["member" + str(k) + ":Iwater"] = 15.0 + prob["member" + str(k) + ":added_mass"] = np.arange(6) + + # Set environment to that used in OC3 testing campaign + # prob["rho_air"] = 1.226 # Density of air [kg/m^3] + # prob["mu_air"] = 1.78e-5 # Viscosity of air [kg/m/s] + prob["rho_water"] = 1025.0 # Density of water [kg/m^3] + # prob["mu_water"] = 1.08e-3 # Viscosity of water [kg/m/s] + # prob["water_depth"] = 320.0 # Distance to sea floor [m] + # prob["Hsig_wave"] = 0.0 # Significant wave height [m] + # prob["Tsig_wave"] = 1e3 # Wave period [s] + # prob["shearExp"] = 0.11 # Shear exponent in wind power law + # prob["cm"] = 2.0 # Added mass coefficient + # prob["Uc"] = 0.0 # Mean current speed + # prob["yaw"] = 0.0 # Turbine yaw angle + # prob["beta_wind"] = prob["beta_wave"] = 0.0 + # prob["cd_usr"] = -1.0 # Compute drag coefficient + # prob["Uref"] = 10.0 + # prob["zref"] = 100.0 + + # Porperties of turbine tower + nTower = prob.model.options["modeling_options"]["floating"]["tower"]["n_height"][0] + prob["hub_height"] = 85.0 + prob["distance_tt_hub"] = 5.0 + prob["tower.s"] = np.linspace(0.0, 1.0, nTower) + prob["tower.outer_diameter_in"] = np.linspace(6.5, 3.87, nTower) + prob["tower.layer_thickness"] = np.linspace(0.027, 0.019, nTower).reshape((1, nTower)) + prob["tower.layer_materials"] = ["steel"] + prob["tower.outfitting_factor"] = 1.07 + + prob["mooring_neutral_load"] = np.zeros((3, 3)) + prob["mooring_neutral_load"][:, 0] = [200, -100.0, -100] + prob["mooring_neutral_load"][:, 1] = [0.0, 50, -50] + prob["mooring_neutral_load"][:, 2] = -1e3 + prob["mooring_fairlead_joints"] = np.array([[0.0, 0.0, 0.0], [0.5, 1.0, 0.0], [1.0, 0.0, 0.0]]) + prob["rna_mass"] = 1e4 + prob["rna_cg"] = np.ones(3) + prob["rna_I"] = 1e4 * np.arange(6) + prob["rna_F"] = np.array([1e2, 1e1, 0.0]) + prob["rna_M"] = np.array([2e1, 2e2, 0.0]) + prob["transition_piece_mass"] = 1e3 + prob["transition_node"] = prob["member4:nodes_xyz"][0, :] + + prob.run_model() + self.assertTrue(True) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestPlatform)) + suite.addTest(unittest.makeSuite(TestGroup)) + return suite + + +if __name__ == "__main__": + result = unittest.TextTestRunner().run(suite()) + + if result.wasSuccessful(): + exit(0) + else: + exit(1) diff --git a/wisdem/test/test_floatingse/test_loading.py b/wisdem/test/test_floatingse/test_loading.py deleted file mode 100644 index d30322c64..000000000 --- a/wisdem/test/test_floatingse/test_loading.py +++ /dev/null @@ -1,467 +0,0 @@ -from __future__ import print_function - -import copy -import time -import unittest - -import numpy as np -import numpy.testing as npt -import matplotlib.pyplot as plt -import wisdem.floatingse.loading as sP -from wisdem.commonse import gravity as g -from mpl_toolkits.mplot3d import Axes3D -from wisdem.commonse.vertical_cylinder import get_nfull - -NSECTIONS = 5 -NHEIGHT = NSECTIONS + 1 -NPTS = get_nfull(NHEIGHT) - - -opt = {} -opt["gamma_f"] = 1.35 -opt["gamma_m"] = 1.1 -opt["gamma_n"] = 1.0 -opt["gamma_b"] = 1.1 -opt["gamma_fatigue"] = 1.755 - -opt["frame3dd"] = {} -opt["frame3dd"]["shear"] = True -opt["frame3dd"]["geom"] = False -opt["frame3dd"]["dx"] = -1 -opt["frame3dd"]["nM"] = 6 -opt["frame3dd"]["Mmethod"] = 1 -opt["frame3dd"]["lump"] = 0 -opt["frame3dd"]["tol"] = 1e-7 - -opt["run_modal"] = False - -opt["tower"] = {} -opt["tower"]["buckling_length"] = 30.0 - -opt["columns"] = {} -opt["columns"]["main"] = {} -opt["columns"]["offset"] = {} -opt["columns"]["main"]["buckling_length"] = opt["columns"]["offset"]["buckling_length"] = 30.0 - - -def DrawTruss(mytruss): - mynodes = {} - for k in range(len(mytruss.myframe.nx)): - mynodes[mytruss.myframe.nnode[k]] = np.r_[mytruss.myframe.nx[k], mytruss.myframe.ny[k], mytruss.myframe.nz[k]] - myelem = [] - for k in range(len(mytruss.myframe.eN1)): - myelem.append((mytruss.myframe.eN1[k], mytruss.myframe.eN2[k])) - - fig = plt.figure() - ax = fig.add_subplot(111, projection="3d") - for e in myelem: - xs = np.array([mynodes[e[0]][0], mynodes[e[1]][0]]) - ys = np.array([mynodes[e[0]][1], mynodes[e[1]][1]]) - zs = np.array([mynodes[e[0]][2], mynodes[e[1]][2]]) - ax.plot(xs, ys, zs) - # ax.auto_scale_xyz([-10, 10], [-10, 10], [-30, 50]) - plt.show() - - -def getInputs(): - inputs = {} - discrete_inputs = {} - - inputs["main_E_full"] = inputs["offset_E_full"] = inputs["tower_E_full"] = 200e9 * np.ones(NPTS - 1) - inputs["main_G_full"] = inputs["offset_G_full"] = inputs["tower_G_full"] = 79.3e9 * np.ones(NPTS - 1) - inputs["main_rho_full"] = inputs["offset_rho_full"] = inputs["tower_rho_full"] = 7850.0 * np.ones(NPTS - 1) - inputs["main_sigma_y_full"] = inputs["offset_sigma_y_full"] = inputs["tower_sigma_y_full"] = 345e6 * np.ones( - NPTS - 1 - ) - - inputs["hsig_wave"] = 10.0 - - inputs["rho_water"] = 1025.0 - - discrete_inputs["cross_attachment_pontoons"] = True - discrete_inputs["lower_attachment_pontoons"] = True - discrete_inputs["upper_attachment_pontoons"] = True - discrete_inputs["lower_ring_pontoons"] = True - discrete_inputs["upper_ring_pontoons"] = True - discrete_inputs["outer_cross_pontoons"] = True - - inputs["number_of_offset_columns"] = 3 - inputs["connection_ratio_max"] = 0.25 - - inputs["number_of_mooring_connections"] = 3 - inputs["mooring_lines_per_connection"] = 1 - inputs["mooring_neutral_load"] = 1e2 * np.ones((15, 3)) - inputs["mooring_stiffness"] = 1e10 * np.eye(6) - inputs["mooring_moments_of_inertia"] = np.ones(6) - - inputs["fairlead"] = 7.5 - inputs["fairlead_radius"] = 1.0 - inputs["fairlead_support_outer_diameter"] = 2.0 - inputs["fairlead_support_wall_thickness"] = 1.0 - - inputs["main_pontoon_attach_upper"] = 1.0 - inputs["main_pontoon_attach_lower"] = 0.0 - - inputs["radius_to_offset_column"] = 15.0 - inputs["pontoon_outer_diameter"] = 2.0 - inputs["pontoon_wall_thickness"] = 1.0 - - colz = np.r_[np.linspace(-15, -10, 7)[:-1], np.linspace(-10, 0, 4)[:-1], np.linspace(0, 10, 7)] - inputs["main_z_full"] = colz.copy() - inputs["main_d_full"] = 2 * 10.0 * np.ones(NPTS) - inputs["main_t_full"] = 0.1 * np.ones(NPTS - 1) - inputs["main_mass"] = 1e2 * np.ones(NPTS - 1) - inputs["main_buckling_length"] = 2.0 * np.ones(NPTS - 1) - inputs["main_displaced_volume"] = 1e2 * np.ones(NPTS - 1) - inputs["main_hydrostatic_force"] = np.zeros(NPTS - 1) - inputs["main_hydrostatic_force"][0] = inputs["main_displaced_volume"].sum() * g * inputs["rho_water"] - inputs["main_center_of_buoyancy"] = -10.0 - inputs["main_center_of_mass"] = -6.0 - inputs["main_Px"] = 50.0 * np.ones(NPTS) - inputs["main_Py"] = np.zeros(NPTS) - inputs["main_Pz"] = np.zeros(NPTS) - inputs["main_qdyn"] = 70.0 * np.ones(NPTS) - - colz = np.r_[np.linspace(-15, 0, 10)[:-1], np.linspace(0, 2.5, 4)[:-1], np.linspace(2.5, 10, 4)] - inputs["offset_z_full"] = colz.copy() - inputs["offset_d_full"] = 2 * 2.0 * np.ones(NPTS) - inputs["offset_t_full"] = 0.05 * np.ones(NPTS - 1) - inputs["offset_mass"] = 1e1 * np.ones(NPTS - 1) - inputs["offset_buckling_length"] = 2.0 * np.ones(NPTS - 1) - inputs["offset_displaced_volume"] = 1e1 * np.ones(NPTS - 1) - inputs["offset_hydrostatic_force"] = np.zeros(NPTS - 1) - inputs["offset_hydrostatic_force"][0] = inputs["main_displaced_volume"].sum() * g * inputs["rho_water"] - inputs["offset_center_of_buoyancy"] = -5.0 - inputs["offset_center_of_mass"] = -3.0 - inputs["offset_Px"] = 50.0 * np.ones(NPTS) - inputs["offset_Py"] = np.zeros(NPTS) - inputs["offset_Pz"] = np.zeros(NPTS) - inputs["offset_qdyn"] = 70.0 * np.ones(NPTS) - - inputs["tower_z_full"] = np.linspace(0, 90, NPTS) - inputs["tower_d_full"] = 2 * 7.0 * np.ones(NPTS) - inputs["tower_t_full"] = 0.5 * np.ones(NPTS - 1) - inputs["tower_mass_section"] = 2e2 * np.ones(NPTS - 1) - inputs["tower_buckling_length"] = 25.0 - inputs["tower_center_of_mass"] = 50.0 - inputs["tower_Px"] = 50.0 * np.ones(NPTS) - inputs["tower_Py"] = np.zeros(NPTS) - inputs["tower_Pz"] = np.zeros(NPTS) - inputs["tower_qdyn"] = 70.0 * np.ones(NPTS) - - inputs["rna_force"] = 6e1 * np.ones(3) - inputs["rna_moment"] = 7e2 * np.ones(3) - inputs["rna_mass"] = 6e1 - inputs["rna_cg"] = np.array([3.05, 2.96, 2.13]) - inputs["rna_I"] = np.array([3.05284574e9, 2.96031642e9, 2.13639924e7, 0.0, 2.89884849e7, 0.0]) - - inputs["material_cost_rate"] = 1.0 - inputs["painting_cost_rate"] = 10.0 - inputs["labor_cost_rate"] = 2.0 - return inputs, discrete_inputs - - -class TestFrame(unittest.TestCase): - def setUp(self): - self.inputs, self.discrete_inputs = getInputs() - self.outputs = {} - self.discrete_outputs = {} - - self.outputs["pontoon_stress"] = np.zeros(70) - - self.mytruss = sP.FloatingFrame( - n_height_main=NHEIGHT, n_height_off=NHEIGHT, n_height_tow=NHEIGHT, modeling_options=opt - ) - - def tearDown(self): - self.mytruss = None - - def testStandard(self): - self.inputs["radius_to_offset_column"] = 20.0 - self.inputs["fairlead_radius"] = 30.0 - # self.inputs['offset_z_full'] = np.array([-15.0, -10.0, -5.0, 0.0, 2.5, 3.0]) - self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - - npt.assert_equal(self.outputs["main_connection_ratio"], 0.25 - 0.1) - npt.assert_equal(self.outputs["offset_connection_ratio"], 0.25 - 0.5) - # DrawTruss(self.mytruss) - - def testSpar(self): - self.inputs["cross_attachment_pontoons"] = False - self.inputs["lower_attachment_pontoons"] = False - self.inputs["upper_attachment_pontoons"] = False - self.inputs["lower_ring_pontoons"] = False - self.inputs["upper_ring_pontoons"] = False - self.inputs["outer_cross_pontoons"] = False - self.inputs["number_of_offset_columns"] = 0 - - # self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - - # DrawTruss(self.mytruss) - - def testOutputsIncremental(self): - ncyl = self.inputs["number_of_offset_columns"] - R_semi = self.inputs["radius_to_offset_column"] - Rmain = 0.5 * self.inputs["main_d_full"][0] - Roff = 0.5 * self.inputs["offset_d_full"][0] - Ro = 0.5 * self.inputs["pontoon_outer_diameter"] - Ri = Ro - self.inputs["pontoon_wall_thickness"] - rho = self.inputs["main_rho_full"][0] - rhoW = self.inputs["rho_water"] - - self.inputs["fairlead_radius"] = 0.0 - self.inputs["fairlead_outer_diameter"] = 0.0 - self.inputs["fairlead_wall_thickness"] = 0.0 - self.inputs["mooring_lines_per_column"] = 0.0 - self.inputs["number_of_mooring_connections"] = 0.0 - self.discrete_inputs["cross_attachment_pontoons"] = False - self.discrete_inputs["lower_attachment_pontoons"] = True - self.discrete_inputs["upper_attachment_pontoons"] = False - self.discrete_inputs["lower_ring_pontoons"] = False - self.discrete_inputs["upper_ring_pontoons"] = False - self.discrete_inputs["outer_cross_pontoons"] = False - self.inputs["pontoon_main_attach_upper"] = 1.0 - self.inputs["pontoon_main_attach_lower"] = 0.0 - self.inputs["material_cost_rate"] = 6.25 - self.inputs["painting_cost_rate"] = 0.0 - self.inputs["labor_cost_rate"] = 0.0 - - self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - V = np.pi * Ro * Ro * (R_semi - Rmain - Roff) * ncyl - m = np.pi * (Ro * Ro - Ri * Ri) * (R_semi - Rmain - Roff) * ncyl * rho - self.assertAlmostEqual(self.outputs["pontoon_displacement"], V) - self.assertAlmostEqual(self.outputs["pontoon_center_of_buoyancy"], -15.0) - self.assertAlmostEqual(self.outputs["pontoon_center_of_mass"], -15.0) - self.assertAlmostEqual(self.outputs["pontoon_mass"], m) - self.assertAlmostEqual(self.outputs["pontoon_cost"], m * 6.25, 2) - - self.discrete_inputs["lower_ring_pontoons"] = True - self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - - V = np.pi * Ro * Ro * ncyl * (R_semi * (1 + np.sqrt(3)) - Rmain - 3 * Roff) - m = np.pi * (Ro * Ro - Ri * Ri) * ncyl * rho * (R_semi * (1 + np.sqrt(3)) - Rmain - 3 * Roff) - self.assertAlmostEqual(self.outputs["pontoon_displacement"], V) - self.assertAlmostEqual(self.outputs["pontoon_center_of_buoyancy"], -15.0) - self.assertAlmostEqual(self.outputs["pontoon_center_of_mass"], -15.0) - self.assertAlmostEqual(self.outputs["pontoon_mass"], m) - self.assertAlmostEqual(self.outputs["pontoon_cost"], m * 6.25, 2) - - self.discrete_inputs["upper_attachment_pontoons"] = True # above waterline - self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - - V = np.pi * Ro * Ro * ncyl * (R_semi * (1 + np.sqrt(3)) - Rmain - 3 * Roff) - m = np.pi * (Ro * Ro - Ri * Ri) * ncyl * rho * (R_semi * (2 + np.sqrt(3)) - 2 * Rmain - 4 * Roff) - # cg = ((-15)*(1 + np.sqrt(3)) + 10) / (2+np.sqrt(3)) - self.assertAlmostEqual(self.outputs["pontoon_displacement"], V) - self.assertAlmostEqual(self.outputs["pontoon_center_of_buoyancy"], -15.0) - # self.assertAlmostEqual(self.outputs['pontoon_center_of_mass'], cg) - self.assertAlmostEqual(self.outputs["pontoon_mass"], m) - self.assertAlmostEqual(self.outputs["pontoon_cost"], m * 6.25, 2) - - self.discrete_inputs["upper_ring_pontoons"] = True - self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - - V = np.pi * Ro * Ro * ncyl * (R_semi * (1 + np.sqrt(3)) - Rmain - 3 * Roff) - m = np.pi * (Ro * Ro - Ri * Ri) * ncyl * rho * 2 * (R_semi * (1 + np.sqrt(3)) - Rmain - 3 * Roff) - self.assertAlmostEqual(self.outputs["pontoon_displacement"], V) - self.assertAlmostEqual(self.outputs["pontoon_center_of_buoyancy"], -15.0) - self.assertAlmostEqual(self.outputs["pontoon_center_of_mass"], -2.5) - self.assertAlmostEqual(self.outputs["pontoon_mass"], m) - self.assertAlmostEqual(self.outputs["pontoon_cost"], m * 6.25, 2) - - # self.inputs['cross_attachment_pontoons'] = True - # self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - - # L = np.sqrt(R_semi*R_semi + 25*25) - # k = 15. / 25. - # V = np.pi * Ro*Ro * ncyl * (k*L + R_semi * (1 + np.sqrt(3))) - # m = np.pi * (Ro*Ro-Ri*Ri) * ncyl * rho * (L + R_semi * 2 * (1 + np.sqrt(3))) - # self.assertAlmostEqual(self.outputs['pontoon_displacement'], V) - # self.assertAlmostEqual(self.outputs['pontoon_mass'], m) - # self.assertAlmostEqual(self.outputs['pontoon_cost'], m*6.25, 2) - - # self.inputs['outer_cross_pontoons'] = True - # self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - - def testForces(self): - self.inputs["offset_z_full"] = np.linspace(-1e-2, 0.0, NPTS) - self.inputs["main_z_full"] = np.linspace(-1e-2, 0.0, NPTS) - self.inputs["tower_z_full"] = np.linspace(0, 1e-2, NPTS) - self.inputs["fairlead"] = 0.0 - self.inputs["number_of_offset_columns"] = 0 - self.inputs["main_mass"] = 1.0 * np.ones(NPTS - 1) - self.inputs["main_center_of_mass"] = 0.0 - self.inputs["main_hydrostatic_force"] = 1e-12 * np.ones(NPTS - 1) - self.inputs["offset_mass"] = 0.0 * np.ones(NPTS - 1) - self.inputs["offset_center_of_mass"] = 0.0 - self.inputs["offset_hydrostatic_force"] = 1e-12 * np.ones(NPTS - 1) - self.inputs["tower_mass_section"] = 1.0 * np.ones(NPTS - 1) - self.inputs["tower_center_of_mass"] = 0.0 - self.inputs["rna_mass"] = 1.0 - self.inputs["rna_force"] = 10.0 * np.ones(3) - self.inputs["rna_moment"] = 20.0 * np.ones(3) - self.inputs["rna_cg"] = np.array([0.0, 0.0, 0.0]) - self.inputs["rna_I"] = np.zeros(6) - self.inputs["main_Px"] = 0.0 * np.ones(NPTS - 1) - self.inputs["offset_Px"] = 0.0 * np.ones(NPTS - 1) - self.inputs["tower_Px"] = 0.0 * np.ones(NPTS - 1) - self.inputs["main_qdyn"] = 0.0 * np.ones(NPTS) - self.inputs["offset_qdyn"] = 0.0 * np.ones(NPTS) - self.inputs["tower_qdyn"] = 0.0 * np.ones(NPTS) - self.discrete_inputs["cross_attachment_pontoons"] = False - self.discrete_inputs["lower_attachment_pontoons"] = False - self.discrete_inputs["upper_attachment_pontoons"] = False - self.discrete_inputs["lower_ring_pontoons"] = False - self.discrete_inputs["upper_ring_pontoons"] = False - self.discrete_inputs["outer_cross_pontoons"] = False - self.inputs["main_pontoon_attach_upper"] = 1.0 - self.inputs["main_pontoon_attach_lower"] = 0.0 - self.inputs["rho_water"] = 1e-12 - self.inputs["number_of_mooring_connections"] = 3 - self.inputs["mooring_lines_per_connection"] = 1 - self.inputs["mooring_neutral_load"] = 10.0 * np.ones((15, 3)) - self.inputs["mooring_moments_of_inertia"] = np.ones(6) - self.inputs["fairlead_radius"] = 10.1 - self.inputs["fairlead_support_outer_diameter"] = 2 * np.sqrt(2.0 / np.pi) - self.inputs["fairlead_support_wall_thickness"] = np.sqrt(2.0 / np.pi) - np.sqrt(1.0 / np.pi) - self.inputs["main_rho_full"] = self.inputs["offset_rho_full"] = self.inputs["tower_rho_full"] = 20.0 * np.ones( - NPTS - 1 - ) - self.inputs["radius_to_offset_column"] = 1.0 - - goodRun = False - kiter = 0 - while goodRun == False: - kiter += 1 - self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - if self.outputs["substructure_mass"] < 1e3: - goodRun = True - if kiter > 3: - self.assertTrue(goodRun) - - msub = (NPTS - 1) + 3 * 20.0 * 1.0 * 0.1 - mtowrna = (NPTS - 1) + 1 - mtot = msub + mtowrna - - self.assertAlmostEqual(self.outputs["substructure_mass"], msub, 5) - self.assertAlmostEqual(self.outputs["structural_mass"], mtot, 4) - npt.assert_almost_equal(self.outputs["total_force"], 10 * 3 + np.array([10.0, 10.0, 10 - mtot * g]), decimal=1) - npt.assert_almost_equal(self.outputs["total_moment"], np.array([20.0, 20.0, 20.0]), decimal=0) - - self.inputs["rna_cg"] = np.array([5.0, 5.0, 5.0]) - goodRun = False - kiter = 0 - while goodRun == False: - kiter += 1 - self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - if self.outputs["substructure_mass"] < 1e3: - goodRun = True - if kiter > 10: - self.assertTrue(goodRun) - npt.assert_almost_equal(self.outputs["total_force"], 3 * 10 + np.array([10.0, 10.0, 10 - mtot * g]), decimal=1) - # self.assertAlmostEqual(self.outputs['total_moment'][-1], 20.0) - - -class TestModal(unittest.TestCase): - def testRunModal(self): - inputs, discrete_inputs = getInputs() - outputs = {} - discrete_outputs = {} - outputs["pontoon_stress"] = np.zeros(70) - - newopt = copy.copy(opt) - newopt["run_modal"] = True - mytruss = sP.FloatingFrame( - n_height_main=NHEIGHT, n_height_off=NHEIGHT, n_height_tow=NHEIGHT, modeling_options=newopt - ) - - mytruss.compute(inputs, outputs, discrete_inputs, discrete_outputs) - self.assertTrue(True) - - -class TestSandbox(unittest.TestCase): - def setUp(self): - self.inputs, self.discrete_inputs = getInputs() - self.outputs = {} - self.discrete_outputs = {} - self.outputs["pontoon_stress"] = np.zeros(70) - - self.mytruss = sP.FloatingFrame( - n_height_main=NHEIGHT, n_height_off=NHEIGHT, n_height_tow=NHEIGHT, modeling_options=opt - ) - - def tearDown(self): - self.mytruss = None - - def testBadInput(self): - self.inputs["number_of_offset_columns"] = 1 - self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - self.assertEqual(self.outputs["substructure_mass"], 1e30) - - self.inputs["number_of_offset_columns"] = 2 - self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - self.assertEqual(self.outputs["substructure_mass"], 1e30) - - self.inputs["number_of_offset_columns"] = 8 - self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - self.assertEqual(self.outputs["substructure_mass"], 1e30) - - self.inputs["number_of_offset_columns"] = 3 - self.inputs["main_z_full"][-2] = self.inputs["main_z_full"][-3] + 1e-12 - self.mytruss.compute(self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs) - self.assertEqual(self.outputs["substructure_mass"], 1e30) - - def testCombinations(self): - self.inputs["radius_to_offset_column"] = 30.0 - - for nc in [0, 3, 4, 5, 6, 7]: - - for cap in [True, False]: - - for lap in [True, False]: - - for uap in [True, False]: - - for lrp in [True, False]: - - for urp in [True, False]: - - for ocp in [True, False]: - self.inputs["number_of_offset_columns"] = nc - self.inputs["number_of_mooring_connections"] = np.maximum(nc, 3) - self.inputs["mooring_lines_per_connection"] = 1 - self.discrete_inputs["cross_attachment_pontoons"] = cap - self.discrete_inputs["lower_attachment_pontoons"] = lap - self.discrete_inputs["upper_attachment_pontoons"] = uap - self.discrete_inputs["lower_ring_pontoons"] = lrp - self.discrete_inputs["upper_ring_pontoons"] = urp - self.discrete_inputs["outer_cross_pontoons"] = ocp - if (nc > 0) and (not cap) and (not lap) and (not uap): - continue - if (nc > 0) and (ocp) and (not lrp): - continue - - self.mytruss.compute( - self.inputs, self.outputs, self.discrete_inputs, self.discrete_outputs - ) - if self.outputs["substructure_mass"] == 1e30: - print(nc, cap, lap, uap, lrp, urp, ocp) - self.assertNotEqual(self.outputs["substructure_mass"], 1e30) - time.sleep(1e-4) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestFrame)) - suite.addTest(unittest.makeSuite(TestModal)) - suite.addTest(unittest.makeSuite(TestSandbox)) - return suite - - -if __name__ == "__main__": - result = unittest.TextTestRunner().run(suite()) - - if result.wasSuccessful(): - exit(0) - else: - exit(1) diff --git a/wisdem/test/test_floatingse/test_map_mooring.py b/wisdem/test/test_floatingse/test_map_mooring.py index d01c271e6..1494bf80c 100644 --- a/wisdem/test/test_floatingse/test_map_mooring.py +++ b/wisdem/test/test_floatingse/test_map_mooring.py @@ -20,16 +20,18 @@ def myisnumber(instr): "---------------------- LINE DICTIONARY ---------------------------------------", "LineType Diam MassDenInAir EA CB CIntDamp Ca Cdn Cdt", "(-) (m) (kg/m) (N) (-) (Pa-s) (-) (-) (-)", - "chain 0.05 49.75 213500000.0 0.65 1.0E8 0.6 -1.0 0.05", + "myline 0.1 0.1 20000 0.65 1.0E8 0.6 -1.0 0.05", "---------------------- NODE PROPERTIES ---------------------------------------", "Node Type X Y Z M V FX FY FZ", "(-) (-) (m) (m) (m) (kg) (m^3) (kN) (kN) (kN)", "1 VESSEL 11.0 0.0 -10.0 0.0 0.0 # # #", - "2 FIX 175.0 0.0 depth 0.0 0.0 # # #", + "2 FIX 216.50635094610965 -125.0 depth 0.0 0.0 # # #", + "3 FIX 216.50635094610965 125.0 depth 0.0 0.0 # # #", "---------------------- LINE PROPERTIES ---------------------------------------", "Line LineType UnstrLen NodeAnch NodeFair Flags", "(-) (-) (m) (-) (-) (-)", - "1 chain 235.8 2 1", + "1 myline 270.0 2 1", + "2 myline 270.0 3 1", "---------------------- SOLVER OPTIONS-----------------------------------------", "Option", "(-)", @@ -60,59 +62,79 @@ def setUp(self): self.outputs = {} self.discrete_inputs = {} - self.inputs["fairlead"] = 10.0 - self.inputs["fairlead_radius"] = 11.0 - self.inputs["anchor_radius"] = 175.0 - - self.inputs["rho_water"] = 1025.0 # 1e3 - self.inputs["water_depth"] = 218.0 # 100.0 - - self.inputs["number_of_mooring_connections"] = 3 - self.inputs["mooring_lines_per_connection"] = 1 - self.inputs["mooring_line_length"] = 0.6 * (self.inputs["water_depth"] + self.inputs["anchor_radius"]) - self.inputs["mooring_diameter"] = 0.05 - self.discrete_inputs["mooring_type"] = "chain" - self.discrete_inputs["anchor_type"] = "suctionpile" - self.inputs["drag_embedment_extra_length"] = 300.0 - self.inputs["max_offset"] = 10.0 - self.inputs["max_survival_heel"] = 10.0 - self.inputs["operational_heel"] = 10.0 - self.inputs["mooring_cost_factor"] = 1.1 + myones = np.ones(1) + self.inputs["fairlead"] = 10.0 * myones + self.inputs["fairlead_radius"] = 11.0 * myones + self.inputs["anchor_radius"] = 250.0 * myones + self.inputs["anchor_cost"] = 10.0 + + self.inputs["rho_water"] = 1e3 + self.inputs["water_depth"] = 200.0 # 100.0 + + self.inputs["line_length"] = 0.6 * (self.inputs["water_depth"] + self.inputs["anchor_radius"]) * myones + self.inputs["line_diameter"] = 0.1 * myones + self.inputs["line_mass_density_coeff"] = 10.0 * myones + self.inputs["line_stiffness_coeff"] = 2e6 * myones + self.inputs["line_breaking_load_coeff"] = 30.0 * myones + self.inputs["line_cost_rate_coeff"] = 40.0 * myones + self.inputs["max_surge_fraction"] = 0.2 + self.inputs["survival_heel"] = 10.0 + self.inputs["operational_heel"] = 5.0 opt = {} - opt["gamma_f"] = 1.35 + opt["n_attach"] = 3 + opt["n_anchors"] = 6 - self.mymap = mapMooring.MapMooring(modeling_options=opt) - self.mymap.set_properties(self.inputs, self.discrete_inputs) + self.mymap = mapMooring.MapMooring(options=opt, gamma=1.35) + + def testGeometry(self): + self.mymap.set_geometry(self.inputs, self.outputs) + self.assertFalse(self.mymap.tlpFlag) + npt.assert_equal(self.outputs["constr_mooring_length"], 0.6 * 450 / (0.95 * 440)) + + self.inputs["line_length"] = 150.0 self.mymap.set_geometry(self.inputs, self.outputs) + self.assertTrue(self.mymap.tlpFlag) + npt.assert_equal(self.outputs["constr_mooring_length"], 150 / (200 - 10 - 1.35 * 11 * np.sin(np.deg2rad(10)))) def testWriteInputAll(self): - self.mymap.write_input_file(self.inputs, self.discrete_inputs) + self.mymap.set_geometry(self.inputs, self.outputs) + self.mymap.write_input_file(self.inputs) actual = self.mymap.finput[:] expect = truth[:] - self.assertEqual(len(expect), len(actual)) + # for k in actual: print(k) + self.assertEqual(len(actual), len(expect)) for n in range(len(actual)): actualTok = actual[n].split() expectTok = expect[n].split() - self.assertEqual(len(expectTok), len(actualTok)) + self.assertEqual(len(actualTok), len(expectTok)) for k in range(len(actualTok)): if myisnumber(actualTok[k]): - self.assertEqual(float(actualTok[k]), float(expectTok[k])) + self.assertAlmostEqual(float(actualTok[k]), float(expectTok[k]), 6) else: self.assertEqual(actualTok[k], expectTok[k]) def testRunMap(self): - self.mymap.runMAP(self.inputs, self.discrete_inputs, self.outputs) - - self.assertEqual(np.count_nonzero(self.outputs["mooring_neutral_load"]), 9) - self.assertEqual(np.count_nonzero(self.outputs["mooring_stiffness"]), 36) - self.assertEqual(np.count_nonzero(self.outputs["operational_heel_restoring_force"]), 9) - self.assertGreater(np.count_nonzero(self.outputs["mooring_plot_matrix"]), 9 * 20 - 3) - - def testCost(self): - self.mymap.compute_cost(self.inputs, self.discrete_inputs, self.outputs) + self.mymap.compute(self.inputs, self.outputs) + npt.assert_almost_equal(self.outputs["mooring_neutral_load"].sum(axis=0)[:2], 0.0, decimal=2) + self.assertEqual(self.outputs["mooring_neutral_load"].shape[0], 3) + self.assertGreater(self.outputs["constr_axial_load"], 1.0) + self.assertGreater(self.outputs["max_surge_restoring_force"], 1e3) + + self.assertEqual(self.outputs["operational_heel_restoring_force"].shape[0], 3) + self.assertGreater(0.0, self.outputs["operational_heel_restoring_force"].sum(axis=0)[0]) + self.assertAlmostEqual(self.outputs["operational_heel_restoring_force"].sum(axis=0)[1], 0.0, 2) + self.assertGreater(0.0, self.outputs["operational_heel_restoring_force"].sum(axis=0)[2]) + + self.assertEqual(self.outputs["survival_heel_restoring_force"].shape[0], 3) + self.assertGreater(0.0, self.outputs["survival_heel_restoring_force"].sum(axis=0)[0]) + self.assertAlmostEqual(self.outputs["survival_heel_restoring_force"].sum(axis=0)[1], 0.0, 2) + self.assertGreater(0.0, self.outputs["survival_heel_restoring_force"].sum(axis=0)[2]) + + self.assertAlmostEqual(self.outputs["mooring_mass"], 6 * 270 * 0.1) + self.assertAlmostEqual(self.outputs["mooring_cost"], 6 * 270 * 0.4 + 6 * 10) def testListEntry(self): # Initiate MAP++ for this design diff --git a/wisdem/test/test_floatingse/test_member.py b/wisdem/test/test_floatingse/test_member.py new file mode 100644 index 000000000..0256fe4c1 --- /dev/null +++ b/wisdem/test/test_floatingse/test_member.py @@ -0,0 +1,886 @@ +import unittest + +import numpy as np +import openmdao.api as om +import numpy.testing as npt +import wisdem.floatingse.member as member +import wisdem.commonse.utilities as util +from wisdem.commonse import gravity as g + +NULL = member.NULL +NHEIGHT = 6 +NPTS = member.get_nfull(NHEIGHT) +myones = np.ones((NPTS,)) +secones = np.ones((NPTS - 1,)) + + +class TestInputs(unittest.TestCase): + def testDiscYAML_1Material(self): + inputs = {} + outputs = {} + discrete_inputs = {} + discrete_outputs = {} + + # Test land based, 1 material + inputs["s"] = np.linspace(0, 1, 5) + inputs["layer_thickness"] = 0.25 * np.ones((1, 5)) + inputs["joint1"] = np.zeros(3) + inputs["joint2"] = np.r_[np.zeros(2), 1e2] + discrete_inputs["transition_flag"] = [False, False] + inputs["outer_diameter_in"] = 8 * np.ones(5) + discrete_inputs["layer_materials"] = ["steel"] + discrete_inputs["ballast_materials"] = ["slurry", "slurry", "seawater"] + inputs["E_mat"] = 1e9 * np.ones((2, 3)) + inputs["G_mat"] = 1e8 * np.ones((2, 3)) + inputs["sigma_y_mat"] = np.array([1e7, 1e7]) + inputs["rho_mat"] = np.array([1e4, 1e5]) + inputs["rho_water"] = 1e3 + inputs["unit_cost_mat"] = np.array([1e1, 2e1]) + inputs["outfitting_factor_in"] = 1.05 + discrete_inputs["material_names"] = ["steel", "slurry"] + opt = {} + opt["n_height"] = [5] + opt["n_layers"] = [1] + opt["n_ballasts"] = [3] + myobj = member.DiscretizationYAML(options=opt, idx=0, n_mat=2) + myobj.compute(inputs, outputs, discrete_inputs, discrete_outputs) + + myones = np.ones(4) + self.assertEqual(outputs["height"], 100.0) + npt.assert_equal(outputs["section_height"], 25.0 * myones) + npt.assert_equal(outputs["outer_diameter"], inputs["outer_diameter_in"]) + npt.assert_equal(outputs["wall_thickness"], 0.25 * myones) + npt.assert_equal(outputs["E"], 1e9 * myones) + npt.assert_equal(outputs["G"], 1e8 * myones) + npt.assert_equal(outputs["sigma_y"], 1e7 * myones) + npt.assert_equal(outputs["rho"], 1e4 * myones) + npt.assert_equal(outputs["unit_cost"], 1e1 * myones) + npt.assert_equal(outputs["outfitting_factor"], 1.05 * myones) + npt.assert_equal(outputs["ballast_density"], np.array([1e5, 1e5, 1e3])) + npt.assert_equal(outputs["ballast_unit_cost"], np.array([2e1, 2e1, 0.0])) + npt.assert_equal(outputs["transition_node"], NULL * np.ones(3)) + A = np.pi * (16 - 3.75 ** 2) + I = (256.0 - 3.75 ** 4) * np.pi / 4.0 + npt.assert_equal(outputs["z_param"], 100 * np.linspace(0, 1, 5)) + npt.assert_equal(outputs["sec_loc"], np.linspace(0, 1, 4)) + # npt.assert_equal(outputs["str_tw"], np.zeros(nout)) + # npt.assert_equal(outputs["tw_iner"], np.zeros(nout)) + npt.assert_equal(outputs["mass_den"], 1e4 * A * myones) + npt.assert_equal(outputs["foreaft_iner"], 1e4 * I * myones) + npt.assert_equal(outputs["sideside_iner"], 1e4 * I * myones) + npt.assert_equal(outputs["foreaft_stff"], 1e9 * I * myones) + npt.assert_equal(outputs["sideside_stff"], 1e9 * I * myones) + npt.assert_equal(outputs["tor_stff"], 1e8 * 2 * I * myones) + npt.assert_equal(outputs["axial_stff"], 1e9 * A * myones) + # npt.assert_equal(outputs["cg_offst"], np.zeros(nout)) + # npt.assert_equal(outputs["sc_offst"], np.zeros(nout)) + # npt.assert_equal(outputs["tc_offst"], np.zeros(nout)) + + discrete_inputs["transition_flag"] = [False, True] + myobj.compute(inputs, outputs, discrete_inputs, discrete_outputs) + npt.assert_equal(outputs["transition_node"], np.r_[np.zeros(2), 1e2]) + + def testDiscYAML_2Materials(self): + inputs = {} + outputs = {} + discrete_inputs = {} + discrete_outputs = {} + + # Test land based, 2 materials + inputs["s"] = np.linspace(0, 1, 5) + inputs["layer_thickness"] = np.array([[0.2, 0.2, 0.2, 0.0, 0.0], [0.0, 0.0, 0.0, 0.1, 0.1]]) + inputs["joint1"] = np.zeros(3) + inputs["joint2"] = np.r_[np.zeros(2), 1e2] + discrete_inputs["transition_flag"] = [False, False] + inputs["outer_diameter_in"] = 8 * np.ones(5) + discrete_inputs["layer_materials"] = ["steel", "other"] + discrete_inputs["ballast_materials"] = ["slurry", "slurry", "seawater"] + inputs["E_mat"] = 1e9 * np.vstack((np.ones((2, 3)), 2 * np.ones((1, 3)))) + inputs["G_mat"] = 1e8 * np.vstack((np.ones((2, 3)), 2 * np.ones((1, 3)))) + inputs["sigma_y_mat"] = np.array([1e7, 1e7, 2e7]) + inputs["rho_mat"] = np.array([1e4, 1e5, 2e4]) + inputs["rho_water"] = 1e3 + inputs["unit_cost_mat"] = np.array([1e1, 2e1, 2e1]) + inputs["outfitting_factor_in"] = 1.05 + discrete_inputs["material_names"] = ["steel", "slurry", "other"] + opt = {} + opt["n_height"] = [5] + opt["n_layers"] = [2] + opt["n_ballasts"] = [3] + myobj = member.DiscretizationYAML(options=opt, idx=0, n_mat=3) + myobj.compute(inputs, outputs, discrete_inputs, discrete_outputs) + + # Define mixtures + v = np.r_[np.mean([0.2, 0]), np.mean([0.1, 0.0])] + vv = v / v.sum() + x = np.r_[1, 2] + xx1 = np.sum(x * vv) # Mass weighted + xx2 = 0.5 * np.sum(vv * x) + 0.5 / np.sum(vv / x) # Volumetric method + xx3 = np.sum(x * x * vv) / xx1 # Mass-cost weighted + self.assertEqual(outputs["height"], 100.0) + npt.assert_equal(outputs["section_height"], 25.0 * np.ones(4)) + npt.assert_equal(outputs["outer_diameter"], inputs["outer_diameter_in"]) + npt.assert_almost_equal(outputs["wall_thickness"], np.array([0.2, 0.2, v.sum(), 0.1])) + npt.assert_almost_equal(outputs["E"], 1e9 * np.array([1, 1, xx2, 2])) + npt.assert_almost_equal(outputs["G"], 1e8 * np.array([1, 1, xx2, 2])) + npt.assert_almost_equal(outputs["sigma_y"], 1e7 * np.array([1, 1, xx2, 2])) + npt.assert_almost_equal(outputs["rho"], 1e4 * np.array([1, 1, xx1, 2])) + npt.assert_almost_equal(outputs["unit_cost"], 1e1 * np.array([1, 1, xx3, 2])) + npt.assert_equal(outputs["outfitting_factor"], 1.05 * np.ones(4)) + npt.assert_equal(outputs["ballast_density"], np.array([1e5, 1e5, 1e3])) + npt.assert_equal(outputs["ballast_unit_cost"], np.array([2e1, 2e1, 0.0])) + npt.assert_equal(outputs["transition_node"], NULL * np.ones(3)) + + +class TestFullDiscretization(unittest.TestCase): + def setUp(self): + self.inputs = {} + self.outputs = {} + + self.inputs["s"] = np.array([0.0, 0.1, 0.3, 0.6, 1.0]) + self.inputs["height"] = 1e1 + self.inputs["outer_diameter"] = 5.0 * np.ones(5) + self.inputs["wall_thickness"] = 0.05 * np.ones(4) + self.inputs["unit_cost"] = 1.0 * np.ones(4) + self.inputs["E"] = 2e9 * np.ones(4) + self.inputs["G"] = 2e7 * np.ones(4) + self.inputs["sigma_y"] = 3e9 * np.ones(4) + self.inputs["rho"] = 7850 * np.ones(4) + self.inputs["outfitting_factor"] = 1.05 * np.ones(4) + self.inputs["unit_cost"] = 7.0 * np.ones(4) + + self.mydis = member.MemberDiscretization(n_height=5, n_refine=2) + + def testRefine2(self): + self.mydis.compute(self.inputs, self.outputs) + npt.assert_array_equal(self.outputs["z_full"], np.array([0.0, 0.5, 1.0, 2.0, 3.0, 4.5, 6.0, 8.0, 10.0])) + npt.assert_array_equal(self.outputs["d_full"], 5.0 * np.ones(9)) + npt.assert_array_equal(self.outputs["t_full"], 0.05 * np.ones(8)) + npt.assert_array_equal(self.outputs["E_full"], 2e9 * np.ones(8)) + npt.assert_array_equal(self.outputs["G_full"], 2e7 * np.ones(8)) + npt.assert_array_equal(self.outputs["nu_full"], 49 * np.ones(8)) + npt.assert_array_equal(self.outputs["sigma_y_full"], 3e9 * np.ones(8)) + npt.assert_array_equal(self.outputs["rho_full"], 7850 * np.ones(8)) + npt.assert_array_equal(self.outputs["unit_cost_full"], 7 * np.ones(8)) + npt.assert_array_equal(self.outputs["outfitting_full"], 1.05 * np.ones(8)) + + npt.assert_almost_equal(self.outputs["s_full"], np.array([0.0, 0.05, 0.1, 0.2, 0.3, 0.45, 0.6, 0.8, 1.0])) + for k in self.inputs["s"]: + self.assertIn(k, self.outputs["s_full"]) + + def testRefineInterp(self): + self.inputs["outer_diameter"] = np.array([5.0, 5.0, 6.0, 7.0, 7.0]) + self.inputs["wall_thickness"] = 1e-2 * np.array([5.0, 5.0, 6.0, 7.0]) + self.mydis.compute(self.inputs, self.outputs) + npt.assert_almost_equal(self.outputs["s_full"], np.array([0.0, 0.05, 0.1, 0.2, 0.3, 0.45, 0.6, 0.8, 1.0])) + npt.assert_array_equal(self.outputs["z_full"], np.array([0.0, 0.5, 1.0, 2.0, 3.0, 4.5, 6.0, 8.0, 10.0])) + npt.assert_array_equal(self.outputs["d_full"], np.array([5.0, 5.0, 5.0, 5.5, 6.0, 6.5, 7.0, 7.0, 7.0])) + npt.assert_array_equal(self.outputs["t_full"], 1e-2 * np.array([5.0, 5.0, 5.0, 5.0, 6.0, 6.0, 7.0, 7.0])) + npt.assert_array_equal(self.outputs["E_full"], 2e9 * np.ones(8)) + npt.assert_array_equal(self.outputs["G_full"], 2e7 * np.ones(8)) + npt.assert_array_equal(self.outputs["nu_full"], 49 * np.ones(8)) + npt.assert_array_equal(self.outputs["sigma_y_full"], 3e9 * np.ones(8)) + npt.assert_array_equal(self.outputs["rho_full"], 7850 * np.ones(8)) + npt.assert_array_equal(self.outputs["unit_cost_full"], 7 * np.ones(8)) + npt.assert_array_equal(self.outputs["outfitting_full"], 1.05 * np.ones(8)) + + +class TestMemberComponent(unittest.TestCase): + def setUp(self): + self.inputs = {} + self.outputs = {} + + self.inputs["s_full"] = np.linspace(0, 1, NPTS) + self.inputs["z_full"] = 100 * np.linspace(0, 1, NPTS) + self.inputs["height"] = 100 + self.inputs["d_full"] = 10.0 * myones + self.inputs["t_full"] = 0.05 * secones + self.inputs["rho_full"] = 1e3 * secones + self.inputs["E_full"] = 1e6 * secones + self.inputs["G_full"] = 1e5 * secones + self.inputs["outfitting_full"] = 1.1 * secones + self.inputs["unit_cost_full"] = 1.0 * secones + self.inputs["painting_cost_rate"] = 10.0 + self.inputs["labor_cost_rate"] = 2.0 + + self.inputs["bulkhead_grid"] = np.array([0.0, 0.08, 0.16, 0.48, 0.88, 1.0]) + nbulk = len(self.inputs["bulkhead_grid"]) + self.inputs["bulkhead_thickness"] = 1.0 * np.ones(nbulk) + + self.inputs["ring_stiffener_web_thickness"] = 0.2 + self.inputs["ring_stiffener_flange_thickness"] = 0.3 + self.inputs["ring_stiffener_web_height"] = 0.5 + self.inputs["ring_stiffener_flange_width"] = 1.0 + self.inputs["ring_stiffener_spacing"] = 20.0 + + self.inputs["ballast_grid"] = np.array([[0.0, 0.08], [0.08, 0.16], [0.16, 0.48]]) + self.inputs["ballast_density"] = np.array([2e3, 4e3, 1e2]) + self.inputs["ballast_volume"] = np.pi * np.array([10.0, 10.0, 0.0]) + self.inputs["ballast_unit_cost"] = np.array([2.0, 4.0, 0.0]) + + self.inputs["grid_axial_joints"] = np.array([0.44, 0.55, 0.66]) + self.inputs["joint1"] = np.array([20.0, 10.0, -30.0]) + self.inputs["joint2"] = np.array([25.0, 10.0, 15.0]) + self.inputs["s_ghost1"] = 0.0 + self.inputs["s_ghost2"] = 1.0 + + opt = {} + opt["n_height"] = [NHEIGHT] + opt["n_ballasts"] = [3] + opt["n_bulkheads"] = [nbulk] + opt["n_axial_joints"] = [3] + self.mem = member.MemberComponent(options=opt, idx=0) + self.mem.sections = member.SortedDict() + + def testSortedDict(self): + # Test create list + self.mem.add_section(0.0, 0.5, "sec0") + self.mem.add_section(0.5, 1.0, "sec1") + self.assertEqual(list(self.mem.sections.keys()), [0.0, 0.5, 1.0]) + self.assertEqual(list(self.mem.sections.values()), ["sec0", "sec1", None]) + + # Test adding a node + self.mem.add_node(0.25) + self.assertEqual(list(self.mem.sections.keys()), [0.0, 0.25, 0.5, 1.0]) + self.assertEqual(list(self.mem.sections.values()), ["sec0", "sec0", "sec1", None]) + self.mem.sections[0.25] = "sec2" + self.assertEqual(list(self.mem.sections.keys()), [0.0, 0.25, 0.5, 1.0]) + self.assertEqual(list(self.mem.sections.values()), ["sec0", "sec2", "sec1", None]) + self.mem.add_node(0.25) + self.assertEqual(list(self.mem.sections.keys()), [0.0, 0.25, 0.5, 1.0]) + self.assertEqual(list(self.mem.sections.values()), ["sec0", "sec2", "sec1", None]) + + # Test inserting a section + self.mem.insert_section(0.75, 0.8, "sec3") + self.assertEqual(list(self.mem.sections.keys()), [0.0, 0.25, 0.5, 0.75, 0.8, 1.0]) + self.assertEqual(list(self.mem.sections.values()), ["sec0", "sec2", "sec1", "sec3", "sec1", None]) + self.mem.insert_section(0.45, 0.55, "sec4") + self.assertEqual(list(self.mem.sections.keys()), [0.0, 0.25, 0.45, 0.5, 0.55, 0.75, 0.8, 1.0]) + self.assertEqual( + list(self.mem.sections.values()), ["sec0", "sec2", "sec4", "sec4", "sec1", "sec3", "sec1", None] + ) + + def testMainSections(self): + self.mem.add_main_sections(self.inputs, self.outputs) + + m = np.pi * 0.25 * (10.0 ** 2 - 9.9 ** 2) * 1e3 * 1.1 * 100.0 + Iax = 0.5 * m * 0.25 * (10.0 ** 2 + 9.9 ** 2) + Ix = (1 / 12.0) * m * (3 * 0.25 * (10.0 ** 2 + 9.9 ** 2) + 100 ** 2) + m * 50 * 50 # parallel axis on last term + self.assertAlmostEqual(self.outputs["shell_mass"], m) + self.assertAlmostEqual(self.outputs["shell_z_cg"], 50.0) + npt.assert_almost_equal(self.outputs["shell_I_base"], [Ix, Ix, Iax, 0.0, 0.0, 0.0], decimal=5) + self.assertGreater(self.outputs["shell_cost"], 1e3) + + key = list(self.mem.sections.keys()) + self.assertEqual(key, self.inputs["s_full"].tolist()) + for k in key: + if k == 1.0: + self.assertEqual(self.mem.sections[k], None) + else: + self.assertAlmostEqual(self.mem.sections[k].D, 10.0) + self.assertAlmostEqual(self.mem.sections[k].t, 1.1 * 0.05) + self.assertAlmostEqual(self.mem.sections[k].A, 1.1 * np.pi * 0.25 * (10.0 ** 2 - 9.9 ** 2)) + self.assertAlmostEqual(self.mem.sections[k].Ixx, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Iyy, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Izz, 2 * 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].rho, 1e3) + self.assertAlmostEqual(self.mem.sections[k].E, 1e6) + self.assertAlmostEqual(self.mem.sections[k].G, 1e5) + + def testMainSectionsWithGhost(self): + self.inputs["s_ghost1"] = 0.0 + self.inputs["s_ghost2"] = 0.9 + self.mem.add_main_sections(self.inputs, self.outputs) + + m = np.pi * 0.25 * (10.0 ** 2 - 9.9 ** 2) * 1e3 * 1.1 * 90.0 + Iax = 0.5 * m * 0.25 * (10.0 ** 2 + 9.9 ** 2) + Ix = (1 / 12.0) * m * (3 * 0.25 * (10.0 ** 2 + 9.9 ** 2) + 90 ** 2) + m * 45 * 45 + self.assertAlmostEqual(self.outputs["shell_mass"], m) + self.assertAlmostEqual(self.outputs["shell_z_cg"], 45.0) + npt.assert_almost_equal(self.outputs["shell_I_base"], [Ix, Ix, Iax, 0.0, 0.0, 0.0], decimal=5) + self.assertGreater(self.outputs["shell_cost"], 0.9 * 1e3) + key = list(self.mem.sections.keys()) + npt.assert_equal(key, np.unique(np.r_[0.9, self.inputs["s_full"].tolist()])) + for k in key: + if k == 1.0: + self.assertEqual(self.mem.sections[k], None) + else: + self.assertAlmostEqual(self.mem.sections[k].D, 10.0) + self.assertAlmostEqual(self.mem.sections[k].t, 1.1 * 0.05) + self.assertAlmostEqual(self.mem.sections[k].A, 1.1 * np.pi * 0.25 * (10.0 ** 2 - 9.9 ** 2)) + self.assertAlmostEqual(self.mem.sections[k].Izz, 2 * 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Ixx, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Iyy, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + if k < 0.9: + self.assertAlmostEqual(self.mem.sections[k].rho, 1e3) + self.assertAlmostEqual(self.mem.sections[k].E, 1e6) + self.assertAlmostEqual(self.mem.sections[k].G, 1e5) + else: + self.assertAlmostEqual(self.mem.sections[k].rho, 0.01) + self.assertAlmostEqual(self.mem.sections[k].E, 1e8) + self.assertAlmostEqual(self.mem.sections[k].G, 1e7) + + def testBulk(self): + self.mem.add_main_sections(self.inputs, self.outputs) + self.mem.add_bulkhead_sections(self.inputs, self.outputs) + bgrid = self.inputs["bulkhead_grid"] + + s_full = self.inputs["s_full"] + key = list(self.mem.sections.keys()) + bulks = np.vstack(([0.0, 0.01], np.c_[bgrid[1:-1] - 0.005, bgrid[1:-1] + 0.005], [0.99, 1.0])) + expect = np.unique(np.r_[s_full, bulks.flatten()]) + npt.assert_almost_equal(key, expect) + for k in key: + inbulk = np.any(np.logical_and(k >= bulks[:, 0], k < bulks[:, 1])) + if inbulk: + self.assertAlmostEqual(self.mem.sections[k].t, 5.0) + self.assertAlmostEqual(self.mem.sections[k].A, np.pi * 0.25 * (10.0 ** 2 - 0 ** 2)) + self.assertAlmostEqual(self.mem.sections[k].Ixx, np.pi * (10.0 ** 4 - 0 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Iyy, np.pi * (10.0 ** 4 - 0 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Izz, 2 * np.pi * (10.0 ** 4 - 0 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].rho, 1.1 * 1e3) + elif k == 1.0: + self.assertEqual(self.mem.sections[k], None) + continue + else: + self.assertAlmostEqual(self.mem.sections[k].A, 1.1 * np.pi * 0.25 * (10.0 ** 2 - 9.9 ** 2)) + self.assertAlmostEqual(self.mem.sections[k].Ixx, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Iyy, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Izz, 2 * 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].t, 1.1 * 0.05) + self.assertAlmostEqual(self.mem.sections[k].rho, 1e3) + + self.assertAlmostEqual(self.mem.sections[k].D, 10.0) + self.assertAlmostEqual(self.mem.sections[k].E, 1e6) + self.assertAlmostEqual(self.mem.sections[k].G, 1e5) + + nbulk = len(bgrid) + R_i = 0.5 * 10 - 0.05 + m_bulk = 1.1 * 1e3 * np.pi * R_i ** 2 * 1.0 + npt.assert_almost_equal(self.outputs["bulkhead_mass"], m_bulk * nbulk) + npt.assert_almost_equal(self.outputs["bulkhead_z_cg"], 100 * bgrid.mean()) + + J0 = 0.50 * m_bulk * R_i ** 2 + I0 = 0.25 * m_bulk * R_i ** 2 + + I = np.zeros(6) + I[2] = nbulk * J0 + for k in bgrid: + I[0] += I0 + m_bulk * (100 * k) ** 2 + I[1] = I[0] + npt.assert_almost_equal(self.outputs["bulkhead_I_base"], I) + + self.assertGreater(self.outputs["bulkhead_cost"], 2e3) + + def testBulkWithGhost(self): + self.inputs["s_ghost1"] = 0.0 + self.inputs["s_ghost2"] = 0.9 + self.mem.add_main_sections(self.inputs, self.outputs) + self.mem.add_bulkhead_sections(self.inputs, self.outputs) + bgrid = np.minimum(0.9, self.inputs["bulkhead_grid"]) + + s_full = self.inputs["s_full"] + key = list(self.mem.sections.keys()) + bulks = np.vstack(([0.0, 0.01], np.c_[bgrid[1:-1] - 0.005, bgrid[1:-1] + 0.005], [0.89, 0.9])) + expect = np.unique(np.r_[s_full, 0.9, bulks.flatten()]) + npt.assert_almost_equal(key, expect) + for k in key: + inbulk = np.any(np.logical_and(k >= bulks[:, 0], k < bulks[:, 1])) + if inbulk: + self.assertAlmostEqual(self.mem.sections[k].t, 5.0) + self.assertAlmostEqual(self.mem.sections[k].A, np.pi * 0.25 * (10.0 ** 2 - 0 ** 2)) + self.assertAlmostEqual(self.mem.sections[k].Ixx, np.pi * (10.0 ** 4 - 0 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Iyy, np.pi * (10.0 ** 4 - 0 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Izz, 2 * np.pi * (10.0 ** 4 - 0 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].rho, 1.1 * 1e3) + elif k == 1.0: + self.assertEqual(self.mem.sections[k], None) + continue + else: + self.assertAlmostEqual(self.mem.sections[k].A, 1.1 * np.pi * 0.25 * (10.0 ** 2 - 9.9 ** 2)) + self.assertAlmostEqual(self.mem.sections[k].Ixx, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Iyy, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Izz, 2 * 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].t, 1.1 * 0.05) + if k < 0.9: + self.assertAlmostEqual(self.mem.sections[k].rho, 1e3) + self.assertAlmostEqual(self.mem.sections[k].E, 1e6) + self.assertAlmostEqual(self.mem.sections[k].G, 1e5) + else: + self.assertAlmostEqual(self.mem.sections[k].rho, 0.01) + self.assertAlmostEqual(self.mem.sections[k].E, 1e8) + self.assertAlmostEqual(self.mem.sections[k].G, 1e7) + + self.assertAlmostEqual(self.mem.sections[k].D, 10.0) + + nbulk = len(bgrid) + R_i = 0.5 * 10 - 0.05 + m_bulk = 1.1 * 1e3 * np.pi * R_i ** 2 * 1.0 + npt.assert_almost_equal(self.outputs["bulkhead_mass"], m_bulk * nbulk) + npt.assert_almost_equal(self.outputs["bulkhead_z_cg"], 100 * bgrid.mean()) + + J0 = 0.50 * m_bulk * R_i ** 2 + I0 = 0.25 * m_bulk * R_i ** 2 + + I = np.zeros(6) + I[2] = nbulk * J0 + for k in bgrid: + I[0] += I0 + m_bulk * (100 * k) ** 2 + I[1] = I[0] + npt.assert_almost_equal(self.outputs["bulkhead_I_base"], I) + + self.assertGreater(self.outputs["bulkhead_cost"], 2e3) + + def testStiff(self): + self.mem.add_main_sections(self.inputs, self.outputs) + self.mem.add_ring_stiffener_sections(self.inputs, self.outputs) + + s_stiff = np.array([0.1, 0.3, 0.5, 0.7, 0.9]) + z_stiff = 100 * s_stiff + + Rwo = 0.5 * (10 - 2 * 0.05) + Rwi = Rwo - 0.5 + Rfi = Rwi - 0.3 + self.assertEqual(self.outputs["flange_spacing_ratio"], 0.1) + nout = np.where(self.outputs["stiffener_radius_ratio"] == NULL)[0][0] + self.assertEqual(nout, 5) + npt.assert_almost_equal(self.outputs["stiffener_radius_ratio"][nout:], NULL) + npt.assert_almost_equal(self.outputs["stiffener_radius_ratio"][:nout], 1 - Rfi / 5) + + # Test Mass + A1 = np.pi * (Rwo ** 2 - Rwi ** 2) + A2 = np.pi * (Rwi ** 2 - Rfi ** 2) + V1 = A1 * 0.2 + V2 = A2 * 1.0 + m1 = V1 * 1e3 + m2 = V2 * 1e3 + m = m1 + m2 + f = 0.2 + self.assertAlmostEqual(self.outputs["stiffener_mass"], m * 5) + self.assertAlmostEqual(self.outputs["stiffener_z_cg"], 50.0) + self.assertGreater(self.outputs["stiffener_cost"], 1e3) + + # Test moment + I_web = member.I_cyl(Rwi, Rwo, 0.2, m1) + I_fl = member.I_cyl(Rfi, Rwi, 1.0, m2) + I_sec = (I_web + I_fl).flatten() + + I = np.zeros(6) + I[0] = np.sum(I_sec[0] + m * z_stiff ** 2.0) + I[1] = I[0] + I[2] = 5 * I_sec[2] + npt.assert_almost_equal(self.outputs["stiffener_I_base"], I) + + s_full = self.inputs["s_full"] + key = list(self.mem.sections.keys()) + stiffs = np.array([[0.095, 0.105], [0.295, 0.305], [0.495, 0.505], [0.695, 0.705], [0.895, 0.905]]) + expect = np.unique(np.r_[s_full, stiffs.flatten()]) + npt.assert_almost_equal(key, expect) + for k in key: + instiff = np.any(np.logical_and(k >= stiffs[:, 0], k < stiffs[:, 1])) + if instiff: + a = f * A1 + A2 + 1.1 * np.pi * 0.25 * (10.0 ** 2 - 9.9 ** 2) + self.assertAlmostEqual(self.mem.sections[k].A, a) + self.assertGreater(self.mem.sections[k].Ixx, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertGreater(self.mem.sections[k].Iyy, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertGreater(self.mem.sections[k].Izz, 2 * 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].t, 5 - np.sqrt(25 - a / np.pi)) + elif k == 1.0: + self.assertEqual(self.mem.sections[k], None) + continue + else: + self.assertAlmostEqual(self.mem.sections[k].A, 1.1 * np.pi * 0.25 * (10.0 ** 2 - 9.9 ** 2)) + self.assertAlmostEqual(self.mem.sections[k].Ixx, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Iyy, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Izz, 2 * 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].t, 1.1 * 0.05) + + self.assertAlmostEqual(self.mem.sections[k].D, 10.0) + self.assertAlmostEqual(self.mem.sections[k].rho, 1e3) + self.assertAlmostEqual(self.mem.sections[k].E, 1e6) + self.assertAlmostEqual(self.mem.sections[k].G, 1e5) + + def testStiffWithGhost(self): + self.inputs["s_ghost1"] = 0.0 + self.inputs["s_ghost2"] = 0.9 + self.mem.add_main_sections(self.inputs, self.outputs) + self.mem.add_ring_stiffener_sections(self.inputs, self.outputs) + + s_stiff = np.array([0.1, 0.3, 0.5, 0.7]) + z_stiff = 100 * s_stiff + + Rwo = 0.5 * (10 - 2 * 0.05) + Rwi = Rwo - 0.5 + Rfi = Rwi - 0.3 + self.assertEqual(self.outputs["flange_spacing_ratio"], 0.1) + nout = np.where(self.outputs["stiffener_radius_ratio"] == NULL)[0][0] + self.assertEqual(nout, 4) + npt.assert_almost_equal(self.outputs["stiffener_radius_ratio"][nout:], NULL) + npt.assert_almost_equal(self.outputs["stiffener_radius_ratio"][:nout], 1 - Rfi / 5) + + # Test Mass + A1 = np.pi * (Rwo ** 2 - Rwi ** 2) + A2 = np.pi * (Rwi ** 2 - Rfi ** 2) + V1 = A1 * 0.2 + V2 = A2 * 1.0 + m1 = V1 * 1e3 + m2 = V2 * 1e3 + m = m1 + m2 + f = 0.2 + self.assertAlmostEqual(self.outputs["stiffener_mass"], m * 4) + self.assertAlmostEqual(self.outputs["stiffener_z_cg"], s_stiff.mean() * 100) + self.assertGreater(self.outputs["stiffener_cost"], 1e3) + + # Test moment + I_web = member.I_cyl(Rwi, Rwo, 0.2, m1) + I_fl = member.I_cyl(Rfi, Rwi, 1.0, m2) + I_sec = (I_web + I_fl).flatten() + + I = np.zeros(6) + I[0] = np.sum(I_sec[0] + m * z_stiff ** 2.0) + I[1] = I[0] + I[2] = 4 * I_sec[2] + npt.assert_almost_equal(self.outputs["stiffener_I_base"], I) + + s_full = self.inputs["s_full"] + key = list(self.mem.sections.keys()) + stiffs = np.array([[0.095, 0.105], [0.295, 0.305], [0.495, 0.505], [0.695, 0.705]]) + expect = np.unique(np.r_[s_full, 0.9, stiffs.flatten()]) + npt.assert_almost_equal(key, expect) + for k in key: + instiff = np.any(np.logical_and(k >= stiffs[:, 0], k < stiffs[:, 1])) + if instiff: + a = f * A1 + A2 + 1.1 * np.pi * 0.25 * (10.0 ** 2 - 9.9 ** 2) + self.assertAlmostEqual(self.mem.sections[k].A, a) + self.assertGreater(self.mem.sections[k].Ixx, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertGreater(self.mem.sections[k].Iyy, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertGreater(self.mem.sections[k].Izz, 2 * 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].t, 5 - np.sqrt(25 - a / np.pi)) + elif k == 1.0: + self.assertEqual(self.mem.sections[k], None) + continue + else: + self.assertAlmostEqual(self.mem.sections[k].A, 1.1 * np.pi * 0.25 * (10.0 ** 2 - 9.9 ** 2)) + self.assertAlmostEqual(self.mem.sections[k].Ixx, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Iyy, 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].Izz, 2 * 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + self.assertAlmostEqual(self.mem.sections[k].t, 1.1 * 0.05) + if k < 0.9: + self.assertAlmostEqual(self.mem.sections[k].rho, 1e3) + self.assertAlmostEqual(self.mem.sections[k].E, 1e6) + self.assertAlmostEqual(self.mem.sections[k].G, 1e5) + else: + self.assertAlmostEqual(self.mem.sections[k].rho, 0.01) + self.assertAlmostEqual(self.mem.sections[k].E, 1e8) + self.assertAlmostEqual(self.mem.sections[k].G, 1e7) + + self.assertAlmostEqual(self.mem.sections[k].D, 10.0) + + def testBallast(self): + self.mem.add_main_sections(self.inputs, self.outputs) + self.mem.add_ballast_sections(self.inputs, self.outputs) + + area = 0.25 * np.pi * 9.9 ** 2 + h = 10 * np.pi / area + cg_perm = (2 * 0.5 * h + 4 * (8 + 0.5 * h)) / 6 + m_perm = np.pi * 6e4 + + I_perm = np.zeros(6) + I_perm[2] = 0.5 * m_perm * 0.25 * 9.9 ** 2 + I_perm[0] = ( + m_perm * (3 * 0.25 * 9.9 ** 2 + h ** 2) / 12.0 + + (1 / 3) * m_perm * (0.5 * h) ** 2 + + (2 / 3) * m_perm * (8 + 0.5 * h) ** 2 + ) + I_perm[1] = I_perm[0] + + self.assertAlmostEqual(self.outputs["ballast_mass"], m_perm) + self.assertAlmostEqual(self.outputs["ballast_cost"], np.pi * 20e4) + self.assertAlmostEqual(self.outputs["ballast_z_cg"], cg_perm) + npt.assert_almost_equal(self.outputs["ballast_I_base"], I_perm) + self.assertAlmostEqual(self.outputs["variable_ballast_capacity"], area * 32) + + def testBallastWithGhost(self): + self.inputs["s_ghost1"] = 0.1 + self.inputs["s_ghost2"] = 1.0 + self.mem.add_main_sections(self.inputs, self.outputs) + self.mem.add_ballast_sections(self.inputs, self.outputs) + + area = 0.25 * np.pi * 9.9 ** 2 + h = 10 * np.pi / area + cg_perm = (2 * (10 + 0.5 * h) + 4 * (18 + 0.5 * h)) / 6 + m_perm = np.pi * 6e4 + + I_perm = np.zeros(6) + I_perm[2] = 0.5 * m_perm * 0.25 * 9.9 ** 2 + I_perm[0] = ( + m_perm * (3 * 0.25 * 9.9 ** 2 + h ** 2) / 12.0 + + (1 / 3) * m_perm * (10 + 0.5 * h) ** 2 + + (2 / 3) * m_perm * (18 + 0.5 * h) ** 2 + ) + I_perm[1] = I_perm[0] + + self.assertAlmostEqual(self.outputs["ballast_mass"], m_perm) + self.assertAlmostEqual(self.outputs["ballast_cost"], np.pi * 20e4) + self.assertAlmostEqual(self.outputs["ballast_z_cg"], cg_perm) + npt.assert_almost_equal(self.outputs["ballast_I_base"], I_perm, 6) + self.assertAlmostEqual(self.outputs["variable_ballast_capacity"], area * 32) + + def testMassProp(self): + self.mem.add_main_sections(self.inputs, self.outputs) + self.mem.add_bulkhead_sections(self.inputs, self.outputs) + self.mem.add_ring_stiffener_sections(self.inputs, self.outputs) + self.mem.add_ballast_sections(self.inputs, self.outputs) + self.mem.compute_mass_properties(self.inputs, self.outputs) + + m_shell = np.pi * 0.25 * (10.0 ** 2 - 9.9 ** 2) * 1e3 * 1.1 * 100.0 + R_i = 0.5 * 10 - 0.05 + cg_shell = 50 + + nbulk = len(self.inputs["bulkhead_grid"]) + m_bulk = 1.1 * 1e3 * np.pi * R_i ** 2 * 1.0 + cg_bulk = 100 * self.inputs["bulkhead_grid"].mean() + + Rwo = 0.5 * (10 - 2 * 0.05) + Rwi = Rwo - 0.5 + Rfi = Rwi - 0.3 + A1 = np.pi * (Rwo ** 2 - Rwi ** 2) + A2 = np.pi * (Rwi ** 2 - Rfi ** 2) + V1 = A1 * 0.2 + V2 = A2 * 1.0 + m1 = V1 * 1e3 + m2 = V2 * 1e3 + m_stiff = m1 + m2 + cg_stiff = 50.0 + + area = 0.25 * np.pi * 9.9 ** 2 + h = 10 * np.pi / area + cg_perm = (2 * 0.5 * h + 4 * (8 + 0.5 * h)) / 6 + m_perm = np.pi * 6e4 + + m_tot = m_shell + nbulk * m_bulk + 5 * m_stiff + m_perm + self.assertAlmostEqual(self.outputs["total_mass"], m_tot) + self.assertAlmostEqual(self.outputs["structural_mass"], m_tot - m_perm) + self.assertAlmostEqual( + self.outputs["z_cg"], (50 * (m_shell + 5 * m_stiff) + nbulk * m_bulk * cg_bulk + m_perm * cg_perm) / m_tot + ) + self.assertEqual( + self.outputs["total_cost"], + self.outputs["shell_cost"] + + self.outputs["ballast_cost"] + + self.outputs["bulkhead_cost"] + + self.outputs["stiffener_cost"], + ) + self.assertEqual( + self.outputs["structural_cost"], + self.outputs["shell_cost"] + self.outputs["bulkhead_cost"] + self.outputs["stiffener_cost"], + ) + + def testNodalFinish(self): + self.outputs["z_cg"] = 40.0 + self.mem.add_main_sections(self.inputs, self.outputs) + self.mem.nodal_discretization(self.inputs, self.outputs) + + s_full = self.inputs["s_full"] + s_all = self.outputs["s_all"] + nout = np.where(s_all == NULL)[0][0] + self.assertEqual(nout, len(s_full) + 3) + npt.assert_almost_equal(s_all[nout:], NULL) + npt.assert_almost_equal(s_all[:nout], np.sort(np.r_[s_full, 0.44, 0.55, 0.66])) + + npt.assert_almost_equal(self.outputs["center_of_mass"], np.array([22, 10, -12])) + npt.assert_almost_equal(self.outputs["nodes_xyz"][nout:, :], NULL) + npt.assert_almost_equal(self.outputs["nodes_xyz"][:nout, 0], 20 + s_all[:nout] * 5) + npt.assert_almost_equal(self.outputs["nodes_xyz"][:nout, 1], 10) + npt.assert_almost_equal(self.outputs["nodes_xyz"][:nout, 2], -30 + s_all[:nout] * 45) + + nelem = nout - 1 + for var in ["D", "t", "A", "Ixx", "Iyy", "Izz", "rho", "G", "E"]: + npt.assert_almost_equal(self.outputs["section_" + var][nelem:], NULL) + npt.assert_almost_equal(self.outputs["section_D"][:nelem], 10.0) + npt.assert_almost_equal(self.outputs["section_t"][:nelem], 1.1 * 0.05) + npt.assert_almost_equal(self.outputs["section_Ixx"][:nelem], 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + npt.assert_almost_equal(self.outputs["section_Iyy"][:nelem], 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + npt.assert_almost_equal(self.outputs["section_Izz"][:nelem], 2 * 1.1 * np.pi * (10.0 ** 4 - 9.9 ** 4) / 64) + npt.assert_almost_equal(self.outputs["section_rho"][:nelem], 1e3) + npt.assert_almost_equal(self.outputs["section_E"][:nelem], 1e6) + npt.assert_almost_equal(self.outputs["section_G"][:nelem], 1e5) + + def testCompute(self): + self.mem.compute(self.inputs, self.outputs) + # 2 points added for bulkheads and stiffeners + # Bulkheads at 0,1 only get 1 new point + nbulk = len(self.inputs["bulkhead_grid"]) + s_all = self.outputs["s_all"] + nout = np.where(s_all == NULL)[0][0] + self.assertEqual(nout, NPTS + 3 + 2 * nbulk - 2 + 2 * 5) + + def testDeconflict(self): + self.inputs["bulkhead_grid"] = np.array([0.0, 0.1, 1.0]) + self.mem.add_main_sections(self.inputs, self.outputs) + self.mem.add_ring_stiffener_sections(self.inputs, self.outputs) + + s_full = self.inputs["s_full"] + key = list(self.mem.sections.keys()) + stiffs = np.array([[0.075, 0.085], [0.295, 0.305], [0.495, 0.505], [0.695, 0.705], [0.895, 0.905]]) + expect = np.unique(np.r_[s_full, stiffs.flatten()]) + npt.assert_almost_equal(key, expect) + + +class TestHydro(unittest.TestCase): + def setUp(self): + self.inputs = {} + self.outputs = {} + self.discrete_inputs = {} + self.discrete_outputs = {} + + # For Geometry call + n_height = 4 + npts = member.get_nfull(n_height) + self.inputs["s_full"] = np.linspace(0, 1.0, npts) + self.inputs["z_full"] = np.linspace(0, 50.0, npts) + self.inputs["d_full"] = 10.0 * np.ones(npts) + self.inputs["s_all"] = NULL * np.ones(member.MEMMAX) + self.inputs["s_all"][: 2 * npts] = np.linspace(0, 1.0, 2 * npts) + self.inputs["nodes_xyz"] = NULL * np.ones((member.MEMMAX, 3)) + self.inputs["nodes_xyz"][: 2 * npts, :] = np.c_[ + np.zeros(2 * npts), np.zeros(2 * npts), np.linspace(0, 50.0, 2 * npts) - 75 + ] + self.inputs["rho_water"] = 1e3 + + self.hydro = member.MemberHydro(n_height=n_height) + + def testVerticalSubmerged(self): + npts = self.inputs["s_full"].size + self.hydro.compute(self.inputs, self.outputs) + + rho_w = self.inputs["rho_water"] + V_expect = np.pi * 25.0 * 50.0 + cb_expect = np.array([0.0, 0.0, -50]) + Ixx = 0 # 0.25 * np.pi * 1e4 + Axx = 0 # np.pi * 1e2 + self.assertAlmostEqual(self.outputs["displacement"], V_expect) + self.assertAlmostEqual(self.outputs["buoyancy_force"], V_expect * rho_w * g) + npt.assert_almost_equal(self.outputs["center_of_buoyancy"], cb_expect) + self.assertEqual(self.outputs["idx_cb"], npts - 1) # Halfway node point + self.assertAlmostEqual(self.outputs["Iwater"], Ixx) + self.assertAlmostEqual(self.outputs["Awater"], Axx) + + m_a = np.zeros(6) + m_a[:2] = V_expect * rho_w + m_a[2] = 0.5 * (8.0 / 3.0) * rho_w * 125 + m_a[3:5] = np.pi * rho_w * 25.0 * ((-25 - cb_expect[-1]) ** 3.0 - (-75 - cb_expect[-1]) ** 3.0) / 3.0 + npt.assert_almost_equal(self.outputs["added_mass"], m_a, decimal=-5) + + def testVerticalWaterplane(self): + npts = self.inputs["s_full"].size + self.inputs["nodes_xyz"] = np.c_[np.zeros(2 * npts), np.zeros(2 * npts), np.linspace(0, 50.0, 2 * npts) - 25] + self.hydro.compute(self.inputs, self.outputs) + + rho_w = self.inputs["rho_water"] + V_expect = np.pi * 25.0 * 25.0 + cb_expect = np.array([0.0, 0.0, -12.5]) + Ixx = 0.25 * np.pi * 625 + Axx = np.pi * 25 + self.assertAlmostEqual(self.outputs["displacement"], V_expect) + self.assertAlmostEqual(self.outputs["buoyancy_force"], V_expect * rho_w * g) + npt.assert_almost_equal(self.outputs["center_of_buoyancy"], cb_expect) + self.assertEqual(self.outputs["idx_cb"], int(0.5 * npts)) + self.assertAlmostEqual(self.outputs["Iwater"], Ixx) + self.assertAlmostEqual(self.outputs["Awater"], Axx) + + m_a = np.zeros(6) + m_a[:2] = V_expect * rho_w + m_a[2] = 0.5 * (8.0 / 3.0) * rho_w * 125 + m_a[3:5] = np.pi * rho_w * 25.0 * ((0 - cb_expect[-1]) ** 3.0 - (-25 - cb_expect[-1]) ** 3.0) / 3.0 + npt.assert_almost_equal(self.outputs["added_mass"], m_a, decimal=-5) + + +class TestGroup(unittest.TestCase): + def testAll(self): + opt = {} + opt["n_height"] = [5] + opt["n_layers"] = [1] + opt["n_bulkheads"] = nbulk = [4] + opt["n_ballasts"] = [2] + opt["n_axial_joints"] = [3] + + prob = om.Problem() + + prob.model.add_subsystem("col", member.Member(column_options=opt, idx=0, n_mat=2), promotes=["*"]) + + prob.setup() + prob["s"] = np.linspace(0, 1, 5) + prob["layer_thickness"] = 0.05 * np.ones((1, 5)) + prob["height"] = 1e2 + prob["outer_diameter_in"] = 10 * np.ones(5) + prob["layer_materials"] = ["steel"] + prob["ballast_materials"] = ["slurry", "seawater"] + prob["E_mat"] = 1e9 * np.ones((2, 3)) + prob["G_mat"] = 1e8 * np.ones((2, 3)) + prob["sigma_y_mat"] = np.array([1e7, 1e7]) + prob["rho_mat"] = np.array([1e4, 1e5]) + prob["rho_water"] = 1025.0 + prob["unit_cost_mat"] = np.array([1e1, 2e1]) + prob["outfitting_factor_in"] = 1.1 + prob["material_names"] = ["steel", "slurry"] + prob["painting_cost_rate"] = 10.0 + prob["labor_cost_rate"] = 2.0 + + prob["bulkhead_grid"] = np.array([0.0, 0.1, 0.2, 1.0]) + prob["bulkhead_thickness"] = 1.0 * np.ones(nbulk) + + prob["ring_stiffener_web_thickness"] = 0.2 + prob["ring_stiffener_flange_thickness"] = 0.3 + prob["ring_stiffener_web_height"] = 0.5 + prob["ring_stiffener_flange_width"] = 1.0 + prob["ring_stiffener_spacing"] = 20.0 + + prob["ballast_grid"] = np.array([[0.0, 0.1], [0.1, 0.2]]) + prob["ballast_volume"] = np.pi * np.array([10.0, 0.0]) + + prob["grid_axial_joints"] = np.array([0.44, 0.55, 0.66]) + prob["joint1"] = np.array([20.0, 10.0, -30.0]) + prob["joint2"] = np.array([25.0, 10.0, 15.0]) + prob["s_ghost1"] = 0.0 + prob["s_ghost2"] = 1.0 + + # prob["mu_water"] = 1e-5 + # prob["water_depth"] = 100.0 + # prob["beta_wave"] = 0.0 + # prob["z0"] = 0.0 + # prob["Hsig_wave"] = 5.0 + # prob["Tsig_wave"] = 10.0 + # prob["zref"] = 100.0 + # prob["Uref"] = 10.0 + # prob["rho_air"] = 1.0 + # prob["mu_air"] = 1e-5 + # prob["shearExp"] = 0.1 + # prob["beta_wind"] = 0.0 + # prob["loading"] = "hydrostatic" + # prob["cd_usr"] = -1.0 + # prob["cm"] = 0.0 + # prob["Uc"] = 0.0 + # prob["yaw"] = 0.0 + + prob.run_model() + self.assertTrue(True) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestInputs)) + suite.addTest(unittest.makeSuite(TestFullDiscretization)) + suite.addTest(unittest.makeSuite(TestMemberComponent)) + suite.addTest(unittest.makeSuite(TestHydro)) + suite.addTest(unittest.makeSuite(TestGroup)) + return suite + + +if __name__ == "__main__": + result = unittest.TextTestRunner().run(suite()) + + if result.wasSuccessful(): + exit(0) + else: + exit(1) diff --git a/wisdem/test/test_floatingse/test_substructure.py b/wisdem/test/test_floatingse/test_substructure.py deleted file mode 100644 index b098a375e..000000000 --- a/wisdem/test/test_floatingse/test_substructure.py +++ /dev/null @@ -1,269 +0,0 @@ -import unittest - -import numpy as np -import numpy.testing as npt -import wisdem.floatingse.substructure as subs -from wisdem.commonse import gravity as g -from wisdem.commonse.vertical_cylinder import get_nfull - -NSECTIONS = 5 -NHEIGHT = NSECTIONS + 1 -NPTS = get_nfull(NHEIGHT) - - -class TestSubs(unittest.TestCase): - def setUp(self): - self.inputs = {} - self.outputs = {} - self.resids = {} - - self.inputs["structural_mass"] = 1e4 - self.inputs["structure_center_of_mass"] = 40.0 * np.ones(3) - self.inputs["structural_frequencies"] = 100.0 * np.ones(6) - self.inputs["total_force"] = 26.0 * np.ones(3) - self.inputs["total_moment"] = 5e4 * np.ones(3) - self.inputs["total_displacement"] = 1e4 - - self.inputs["mooring_mass"] = 20.0 - self.inputs["mooring_neutral_load"] = np.zeros((15, 3)) - self.inputs["mooring_neutral_load"][:3, :] = 5.0 * g - self.inputs["mooring_surge_restoring_force"] = 1e2 - self.inputs["mooring_pitch_restoring_force"] = 1e5 * np.ones((10, 3)) - self.inputs["mooring_pitch_restoring_force"][3:, :] = 0.0 - self.inputs["mooring_cost"] = 256.0 - self.inputs["mooring_stiffness"] = np.ones((6, 6)) - self.inputs["mooring_moments_of_inertia"] = np.array([10.0, 10.0, 2.0, 0.0, 0.0, 0.0]) - self.inputs["fairlead"] = 0.5 - self.inputs["fairlead_location"] = 0.1 - self.inputs["fairlead_radius"] = 5.0 - self.inputs["max_survival_heel"] = 10.0 - self.inputs["operational_heel"] = 10.0 - - self.inputs["pontoon_cost"] = 512.0 - - self.inputs["hsig_wave"] = 10.0 - - self.inputs["main_Iwaterplane"] = 150.0 - self.inputs["main_Awaterplane"] = 20.0 - self.inputs["main_mass"] = 2.0 * np.ones(NPTS - 1) - self.inputs["main_cost"] = 32.0 - self.inputs["main_freeboard"] = 10.0 - self.inputs["main_center_of_mass"] = -10.0 - self.inputs["main_center_of_buoyancy"] = -8.0 - self.inputs["main_added_mass"] = 2 * np.array([10.0, 10.0, 2.0, 30.0, 30.0, 0.0]) - self.inputs["main_moments_of_inertia"] = 1e2 * np.array([10.0, 10.0, 2.0, 0.0, 0.0, 0.0]) - - self.inputs["offset_Iwaterplane"] = 50.0 - self.inputs["offset_Awaterplane"] = 9.0 - self.inputs["offset_cost"] = 64.0 - self.inputs["offset_mass"] = np.ones(NPTS - 1) - self.inputs["offset_center_of_mass"] = -5.0 - self.inputs["offset_center_of_buoyancy"] = -4.0 - self.inputs["offset_added_mass"] = np.array([10.0, 10.0, 2.0, 30.0, 30.0, 0.0]) - self.inputs["offset_moments_of_inertia"] = 1e1 * np.array([10.0, 10.0, 2.0, 0.0, 0.0, 0.0]) - self.inputs["offset_freeboard"] = 10.0 - self.inputs["offset_draft"] = 15.0 - - self.inputs["tower_z_full"] = np.linspace(0, 90, 3 * NSECTIONS + 1) - self.inputs["tower_mass"] = 2e2 - self.inputs["tower_shell_cost"] = 2e5 - self.inputs["tower_d_full"] = 5.0 * np.ones(NPTS) - self.inputs["tower_d_base"] = self.inputs["tower_d_full"][0] - self.inputs["tower_I_base"] = 1e5 * np.array([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]) - self.inputs["rna_mass"] = 6e1 - self.inputs["rna_cg"] = np.array([0.0, 0.0, 5.0]) - self.inputs["rna_I"] = 1e5 * np.array([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]) - self.inputs["Rhub"] = 3.0 - - self.inputs["number_of_offset_columns"] = 3 - self.inputs["water_ballast_radius_vector"] = 40.0 * np.ones(5) - self.inputs["water_ballast_zpts_vector"] = np.array([-10, -9, -8, -7, -6]) - self.inputs["radius_to_offset_column"] = 20.0 - self.inputs["z_center_of_buoyancy"] = -2.0 - - self.inputs["rho_water"] = 1e3 - self.inputs["wave_period_range_low"] = 2.0 - self.inputs["wave_period_range_high"] = 20.0 - - self.mysemi = subs.Substructure(n_height_main=NHEIGHT, n_height_off=NHEIGHT, n_height_tow=NHEIGHT) - self.mysemiG = subs.SubstructureGeometry(n_height_main=NHEIGHT, n_height_off=NHEIGHT) - - def testSetGeometry(self): - self.inputs["number_of_offset_columns"] = 3 - self.inputs["Rhub"] = 3.0 - self.inputs["tower_d_base"] = 10.0 - self.inputs["main_d_full"] = 2 * np.array([10.0, 10.0, 10.0]) - self.inputs["offset_d_full"] = 2 * np.array([10.0, 10.0, 10.0]) - self.inputs["offset_z_nodes"] = np.array([-35.0, -15.0, 15.0]) - self.inputs["main_z_nodes"] = np.array([-35.0, -15.0, 15.0]) - self.inputs["radius_to_offset_column"] = 25.0 - self.inputs["fairlead_location"] = 0.1 - self.inputs["fairlead_offset_from_shell"] = 1.0 - self.inputs["offset_freeboard"] = 10.0 - self.inputs["offset_draft"] = 15.0 - self.mysemiG.compute(self.inputs, self.outputs) - - # Semi - self.assertEqual(self.outputs["fairlead"], 30.0) - self.assertEqual(self.outputs["fairlead_radius"], 11.0 + 25.0) - self.assertEqual(self.outputs["main_offset_spacing"], 25.0 - 10.0 - 10.0) - self.assertEqual(self.outputs["tower_transition_buffer"], 10 - 5.0) - self.assertEqual(self.outputs["offset_freeboard_heel_margin"], 10.0 - 25.0 * np.sin(np.deg2rad(10.0))) - self.assertEqual(self.outputs["offset_draft_heel_margin"], 15.0 - 25.0 * np.sin(np.deg2rad(10.0))) - - # Spar - self.inputs["number_of_offset_columns"] = 0 - self.mysemiG.compute(self.inputs, self.outputs) - self.assertEqual(self.outputs["fairlead"], 30.0) - self.assertEqual(self.outputs["fairlead_radius"], 11.0) - self.assertEqual(self.outputs["main_offset_spacing"], 25.0 - 10.0 - 10.0) - self.assertEqual(self.outputs["tower_transition_buffer"], 10 - 5.0) - - def testBalance(self): - self.mysemi.balance(self.inputs, self.outputs) - m_water = 1e3 * 1e4 - 1e4 - 15 - z_data = self.inputs["water_ballast_zpts_vector"] - h_data = z_data - z_data[0] - h_expect = np.interp(m_water, 1e3 * h_data * np.pi * self.inputs["water_ballast_radius_vector"] ** 2, h_data) - cg_expect_z = (1e4 * 40.0 + m_water * (-10 + 0.5 * h_expect)) / (1e4 + m_water) - cg_expect_xy = 1e4 * 40.0 / (1e4 + m_water) - - self.assertEqual(self.outputs["variable_ballast_mass"], m_water) - self.assertEqual(self.outputs["variable_ballast_height_ratio"], h_expect / 4.0) - npt.assert_almost_equal(self.outputs["center_of_mass"], np.array([cg_expect_xy, cg_expect_xy, cg_expect_z])) - - self.inputs["number_of_offset_columns"] = 0 - self.mysemi.balance(self.inputs, self.outputs) - - self.assertEqual(self.outputs["variable_ballast_mass"], m_water) - self.assertEqual(self.outputs["variable_ballast_height_ratio"], h_expect / 4.0) - npt.assert_almost_equal(self.outputs["center_of_mass"], np.array([cg_expect_xy, cg_expect_xy, cg_expect_z])) - - def testStability(self): - self.inputs["mooring_pitch_restoring_force"] = 0.0 * np.ones((10, 3)) - self.outputs["center_of_mass"] = np.array([0.0, 0.0, -1.0]) - self.mysemi.compute_stability(self.inputs, self.outputs) - - I_expect = 150.0 + (50.0 + 9.0 * (20.0 * np.cos(np.deg2rad(np.array([0.0, 120.0, 240.0])))) ** 2).sum() - static_expect = -1.0 + 2.0 - meta_expect = I_expect / 1e4 - static_expect - wind_fact = np.cos(np.deg2rad(10.0)) ** 2.0 - self.assertEqual(self.outputs["buoyancy_to_gravity"], static_expect) - self.assertEqual(self.outputs["metacentric_height"], meta_expect) - self.assertEqual(self.outputs["offset_force_ratio"], 26.0 / 1e2) - self.assertAlmostEqual( - self.outputs["heel_moment_ratio"], - (wind_fact * 5e4) / (1e4 * g * 1e3 * np.sin(np.deg2rad(10)) * np.abs(meta_expect)), - ) - - self.inputs["number_of_offset_columns"] = 0 - self.mysemi.compute_stability(self.inputs, self.outputs) - - I_expect = 150.0 - meta_expect = I_expect / 1e4 - static_expect - self.assertEqual(self.outputs["buoyancy_to_gravity"], static_expect) - self.assertEqual(self.outputs["metacentric_height"], meta_expect) - self.assertEqual(self.outputs["offset_force_ratio"], 26.0 / 1e2) - self.assertAlmostEqual( - self.outputs["heel_moment_ratio"], - (wind_fact * 5e4) / (1e4 * g * 1e3 * np.sin(np.deg2rad(10)) * np.abs(meta_expect)), - ) - - self.inputs["fairlead"] = 1.0 - self.inputs["mooring_pitch_restoring_force"][:3, -1] = 1.0 - self.assertAlmostEqual( - self.outputs["heel_moment_ratio"], - (wind_fact * 5e4) / (1 * 5 + 1e4 * g * 1e3 * np.sin(np.deg2rad(10)) * np.abs(meta_expect)), - ) - - def testPeriods(self): - # Spar first - self.inputs["structure_center_of_mass"] = np.array([0.0, 0.0, -40.0]) - self.inputs["number_of_offset_columns"] = 0 - self.mysemi.balance(self.inputs, self.outputs) - self.inputs["main_center_of_mass"] = self.outputs["center_of_mass"][-1] - self.inputs["main_center_of_buoyancy"] = self.outputs["center_of_mass"][-1] + 2.0 - self.inputs["tower_mass"] = 0.0 - self.inputs["rna_mass"] = 0.0 - self.inputs["rna_I"] = 1e2 * np.array([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]) - self.inputs["tower_I_base"] = 1e2 * np.array([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]) - self.mysemi.compute_stability(self.inputs, self.outputs) - self.mysemi.compute_rigid_body_periods(self.inputs, self.outputs) - - m_struct = self.inputs["structural_mass"] - m_water = self.outputs["variable_ballast_mass"] - z_cg = self.inputs["main_center_of_mass"] - z_water = self.outputs["variable_ballast_center_of_mass"] - I_water = self.outputs["variable_ballast_moments_of_inertia"] - M_expect = np.zeros(6) - M_expect[:3] = m_struct + m_water - M_expect[3:] = self.inputs["main_moments_of_inertia"][:3] - M_expect[3:5] += I_water[:2] + m_water * (z_water - z_cg) ** 2 - M_expect[-1] += I_water[2] - M_expect[3:] += 2e2 - npt.assert_equal(self.outputs["mass_matrix"], M_expect) - - A_expect = np.zeros(6) - A_expect[:3] = self.inputs["main_added_mass"][:3] - A_expect[3:5] = self.inputs["main_added_mass"][3:5] + A_expect[0] * (2.0) ** 2 - npt.assert_equal(self.outputs["added_mass_matrix"], A_expect) - - rho_w = self.inputs["rho_water"] - K_expect = np.zeros(6) - K_expect[2] = rho_w * g * 20.0 # waterplane area for main - K_expect[3:5] = rho_w * g * self.outputs["metacentric_height"] * 1e4 # Total displacement - npt.assert_almost_equal(self.outputs["hydrostatic_stiffness"], K_expect) - - T_expect = ( - 2 * np.pi * np.sqrt((M_expect + A_expect) / (1e-6 + K_expect + np.diag(self.inputs["mooring_stiffness"]))) - ) - npt.assert_almost_equal(self.outputs["rigid_body_periods"], T_expect) - - def testMargins(self): - - self.mysemi.balance(self.inputs, self.outputs) - self.mysemi.compute_stability(self.inputs, self.outputs) - self.mysemi.compute_rigid_body_periods(self.inputs, self.outputs) - self.mysemi.check_frequency_margins(self.inputs, self.outputs) - - myones = np.ones(6) - T_sys = self.outputs["rigid_body_periods"] - T_wave_low = self.inputs["wave_period_range_low"] * myones - T_wave_high = self.inputs["wave_period_range_high"] * myones - f_struct = self.inputs["structural_frequencies"] - T_struct = 1.0 / f_struct - - T_wave_high[-1] = 1e-16 - T_wave_low[-1] = 1e30 - - ind = T_sys > T_wave_low - npt.assert_equal(self.outputs["period_margin_high"][ind], T_sys[ind] / T_wave_high[ind]) - - ind = T_sys < T_wave_high - npt.assert_equal(self.outputs["period_margin_low"][ind], T_sys[ind] / T_wave_low[ind]) - - ind = T_struct > T_wave_low - npt.assert_equal(self.outputs["modal_margin_high"][ind], T_struct[ind] / T_wave_high[ind]) - - ind = T_struct < T_wave_high - npt.assert_equal(self.outputs["modal_margin_low"][ind], T_struct[ind] / T_wave_low[ind]) - - def testCost(self): - self.mysemi.compute_costs(self.inputs, self.outputs) - c_expect = 256.0 + 512.0 + 32.0 + 3 * 64.0 + 2e5 - self.assertEqual(self.outputs["total_cost"], c_expect) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestSubs)) - return suite - - -if __name__ == "__main__": - result = unittest.TextTestRunner().run(suite()) - - if result.wasSuccessful(): - exit(0) - else: - exit(1) diff --git a/wisdem/test/test_gluecode/test_gc_loadinputs.py b/wisdem/test/test_gluecode/test_gc_loadinputs.py index 01fe320f1..6fc3e477c 100644 --- a/wisdem/test/test_gluecode/test_gc_loadinputs.py +++ b/wisdem/test/test_gluecode/test_gc_loadinputs.py @@ -60,19 +60,19 @@ def testOptFlags(self): self.myobj.set_opt_flags() self.assertEqual( self.myobj.analysis_options["design_variables"]["blade"]["aero_shape"]["twist"]["n_opt"], - self.myobj.modeling_options["RotorSE"]["n_span"], + self.myobj.modeling_options["WISDEM"]["RotorSE"]["n_span"], ) self.assertEqual( self.myobj.analysis_options["design_variables"]["blade"]["aero_shape"]["chord"]["n_opt"], - self.myobj.modeling_options["RotorSE"]["n_span"], + self.myobj.modeling_options["WISDEM"]["RotorSE"]["n_span"], ) self.assertEqual( self.myobj.analysis_options["design_variables"]["blade"]["structure"]["spar_cap_ss"]["n_opt"], - self.myobj.modeling_options["RotorSE"]["n_span"], + self.myobj.modeling_options["WISDEM"]["RotorSE"]["n_span"], ) self.assertEqual( self.myobj.analysis_options["design_variables"]["blade"]["structure"]["spar_cap_ps"]["n_opt"], - self.myobj.modeling_options["RotorSE"]["n_span"], + self.myobj.modeling_options["WISDEM"]["RotorSE"]["n_span"], ) diff --git a/wisdem/test/test_gluecode/test_gluecode.py b/wisdem/test/test_gluecode/test_gluecode.py index 126a7c421..8278be00a 100644 --- a/wisdem/test/test_gluecode/test_gluecode.py +++ b/wisdem/test/test_gluecode/test_gluecode.py @@ -26,10 +26,10 @@ def test5MW(self): ) self.assertAlmostEqual(wt_opt["re.precomp.blade_mass"][0], 16403.682326940743, 2) - self.assertAlmostEqual(wt_opt["rp.AEP"][0] * 1.0e-6, 24.0801229107, 2) - self.assertAlmostEqual(wt_opt["financese.lcoe"][0] * 1.0e3, 51.77125010334704, 2) - self.assertAlmostEqual(wt_opt["rs.tip_pos.tip_deflection"][0], 4.1949743155, 1) - self.assertAlmostEqual(wt_opt["towerse.z_param"][-1], 87.6974416, 1) + self.assertAlmostEqual(wt_opt["rp.AEP"][0] * 1.0e-6, 23.8821935913, 2) + self.assertAlmostEqual(wt_opt["financese.lcoe"][0] * 1.0e3, 51.6455656178, 2) + self.assertAlmostEqual(wt_opt["rs.tip_pos.tip_deflection"][0], 4.2027339083, 1) + self.assertAlmostEqual(wt_opt["towerse.z_param"][-1], 87.7, 2) def test15MW(self): ## IEA 15MW @@ -39,10 +39,10 @@ def test15MW(self): ) self.assertAlmostEqual(wt_opt["re.precomp.blade_mass"][0], 73310.0985877902, 1) - self.assertAlmostEqual(wt_opt["rp.AEP"][0] * 1.0e-6, 78.0371305939, 1) - self.assertAlmostEqual(wt_opt["financese.lcoe"][0] * 1.0e3, 67.61437081321105, 1) - self.assertAlmostEqual(wt_opt["rs.tip_pos.tip_deflection"][0], 22.7002324979, 1) - self.assertAlmostEqual(wt_opt["towerse.z_param"][-1], 144.386, 1) + self.assertAlmostEqual(wt_opt["rp.AEP"][0] * 1.0e-6, 77.6585454480, 1) + self.assertAlmostEqual(wt_opt["financese.lcoe"][0] * 1.0e3, 65.0620671670, 1) + self.assertAlmostEqual(wt_opt["rs.tip_pos.tip_deflection"][0], 22.6934383489, 1) + self.assertAlmostEqual(wt_opt["towerse.z_param"][-1], 144.386, 3) def test3p4MW(self): ## IEA 15MW @@ -52,10 +52,10 @@ def test3p4MW(self): ) self.assertAlmostEqual(wt_opt["re.precomp.blade_mass"][0], 14555.7435212969, 1) - self.assertAlmostEqual(wt_opt["rp.AEP"][0] * 1.0e-6, 13.7700592288, 1) - self.assertAlmostEqual(wt_opt["financese.lcoe"][0] * 1.0e3, 37.56052377907683, 1) - self.assertAlmostEqual(wt_opt["rs.tip_pos.tip_deflection"][0], 6.5292336115, 1) - self.assertAlmostEqual(wt_opt["towerse.z_param"][-1], 108.0, 1) + self.assertAlmostEqual(wt_opt["rp.AEP"][0] * 1.0e-6, 13.6037235499, 1) + self.assertAlmostEqual(wt_opt["financese.lcoe"][0] * 1.0e3, 37.4970510481, 1) + self.assertAlmostEqual(wt_opt["rs.tip_pos.tip_deflection"][0], 6.606075926116244, 1) + self.assertAlmostEqual(wt_opt["towerse.z_param"][-1], 108.0, 3) def suite(): diff --git a/wisdem/test/test_landbosse/test_landbosse.py b/wisdem/test/test_landbosse/test_landbosse.py index ca3906cf9..0a8d96620 100644 --- a/wisdem/test/test_landbosse/test_landbosse.py +++ b/wisdem/test/test_landbosse/test_landbosse.py @@ -20,20 +20,6 @@ def landbosse_costs_by_module_type_operation(): return landbosse_costs_by_module_type_operation -def test_landbosse(landbosse_costs_by_module_type_operation): - """ - This runs the regression test by comparing against the expected validation - data. - """ - OpenMDAODataframeCache._cache = {} # Clear the cache - expected_validation_data_sheets = OpenMDAODataframeCache.read_all_sheets_from_xlsx("ge15_expected_validation") - costs_by_module_type_operation = expected_validation_data_sheets["costs_by_module_type_operation"] - result = compare_expected_to_actual( - costs_by_module_type_operation, landbosse_costs_by_module_type_operation, "test.csv" - ) - assert result - - def compare_expected_to_actual(expected_df, actual_module_type_operation_list, validation_output_csv): """ This compares the expected costs as calculated by a prior model run @@ -105,7 +91,16 @@ def compare_expected_to_actual(expected_df, actual_module_type_operation_list, v else: return True - if result.wasSuccessful(): - exit(0) - else: - exit(1) + +def test_landbosse(landbosse_costs_by_module_type_operation): + """ + This runs the regression test by comparing against the expected validation + data. + """ + OpenMDAODataframeCache._cache = {} # Clear the cache + expected_validation_data_sheets = OpenMDAODataframeCache.read_all_sheets_from_xlsx("ge15_expected_validation") + costs_by_module_type_operation = expected_validation_data_sheets["costs_by_module_type_operation"] + result = compare_expected_to_actual( + costs_by_module_type_operation, landbosse_costs_by_module_type_operation, "test.csv" + ) + assert result diff --git a/wisdem/test/test_orbit/conftest.py b/wisdem/test/test_orbit/conftest.py index 6c301c89e..e5faed6fa 100644 --- a/wisdem/test/test_orbit/conftest.py +++ b/wisdem/test/test_orbit/conftest.py @@ -5,10 +5,9 @@ import pytest from marmot import Environment - from wisdem.orbit.core import Vessel -from wisdem.test.test_orbit.data import test_weather from wisdem.orbit.core.library import initialize_library, extract_library_specs +from wisdem.test.test_orbit.data import test_weather from wisdem.orbit.phases.install.cable_install import SimpleCable diff --git a/wisdem/test/test_orbit/data/library/__init__.py b/wisdem/test/test_orbit/data/library/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wisdem/test/test_orbit/data/library/cables/__init__.py b/wisdem/test/test_orbit/data/library/cables/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wisdem/test/test_orbit/data/library/defaults/__init__.py b/wisdem/test/test_orbit/data/library/defaults/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wisdem/test/test_orbit/data/library/project/__init__.py b/wisdem/test/test_orbit/data/library/project/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wisdem/test/test_orbit/data/library/project/config/__init__.py b/wisdem/test/test_orbit/data/library/project/config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wisdem/test/test_orbit/data/library/project/config/array_cable_install.yaml b/wisdem/test/test_orbit/data/library/project/config/array_cable_install.yaml index 3e61d45d2..c5c8282e4 100644 --- a/wisdem/test/test_orbit/data/library/project/config/array_cable_install.yaml +++ b/wisdem/test/test_orbit/data/library/project/config/array_cable_install.yaml @@ -18,6 +18,7 @@ array_system: - - 1.4 - 2 linear_density: 42.5 + system_cost: 100e6 plant: layout: grid num_turbines: 40 diff --git a/wisdem/test/test_orbit/data/library/project/config/complete_floating_project.yaml b/wisdem/test/test_orbit/data/library/project/config/complete_floating_project.yaml new file mode 100644 index 000000000..45fbc3ecf --- /dev/null +++ b/wisdem/test/test_orbit/data/library/project/config/complete_floating_project.yaml @@ -0,0 +1,50 @@ +OffshoreSubstationInstallation: + feeder: test_floating_barge +array_cable_install_vessel: test_cable_lay_vessel +array_system: + free_cable_length: 0.5 +array_system_design: + cables: + - XLPE_300mm_33kV +design_phases: +- ArraySystemDesign +- ExportSystemDesign +- MooringSystemDesign +- OffshoreSubstationDesign +- SemiSubmersibleDesign +export_cable_install_vessel: test_cable_lay_vessel +export_system_design: + cables: + - XLPE_300mm_33kV +install_phases: + ArrayCableInstallation: 0 + ExportCableInstallation: 0 + MooredSubInstallation: 0 + MooringSystemInstallation: 0 + OffshoreSubstationInstallation: 0 + TurbineInstallation: 0 +mooring_install_vessel: test_support_vessel +oss_install_vessel: test_floating_heavy_lift_vessel +plant: + layout: ring + num_turbines: 50 + row_spacing: 7 + substation_distance: 1 + turbine_spacing: 7 +port: + monthly_rate: 2000000.0 + sub_assembly_lines: 1 + turbine_assembly_cranes: 1 +site: + depth: 900 + distance: 100 + distance_to_landfall: 100 +substructure: + takt_time: 168 +support_vessel: test_support_vessel +towing_vessel: test_towing_vessel +towing_vessel_groups: + station_keeping_vessels: 2 + towing_vessels: 3 +turbine: 12MW_generic +wtiv: test_floating_heavy_lift_vessel diff --git a/wisdem/test/test_orbit/data/library/project/config/export_cable_install.yaml b/wisdem/test/test_orbit/data/library/project/config/export_cable_install.yaml index 4fe13a012..beccf930b 100644 --- a/wisdem/test/test_orbit/data/library/project/config/export_cable_install.yaml +++ b/wisdem/test/test_orbit/data/library/project/config/export_cable_install.yaml @@ -7,6 +7,7 @@ export_system: - - 20 - 0.5 linear_density: 50.0 + system_cost: 200e6 plant: layout: grid num_turbines: 40 diff --git a/wisdem/test/test_orbit/data/library/project/config/floating_oss_install.yaml b/wisdem/test/test_orbit/data/library/project/config/floating_oss_install.yaml new file mode 100644 index 000000000..d9dab2b15 --- /dev/null +++ b/wisdem/test/test_orbit/data/library/project/config/floating_oss_install.yaml @@ -0,0 +1,20 @@ +feeder: test_floating_barge +num_feeders: 1 +num_substations: 1 +offshore_substation_substructure: + deck_space: 200 + length: 50 + type: Monopile + mass: 400 + unit_cost: 5e6 +offshore_substation_topside: + deck_space: 200 + mass: 400 + unit_cost: 100e6 +oss_install_vessel: test_floating_heavy_lift_vessel +port: + monthly_rate: 100000 + num_cranes: 1 +site: + depth: 500 + distance: 40 diff --git a/wisdem/test/test_orbit/data/library/project/config/floating_turbine_install_feeder.yaml b/wisdem/test/test_orbit/data/library/project/config/floating_turbine_install_feeder.yaml new file mode 100644 index 000000000..5780036ab --- /dev/null +++ b/wisdem/test/test_orbit/data/library/project/config/floating_turbine_install_feeder.yaml @@ -0,0 +1,24 @@ +feeder: test_floating_barge +num_feeders: 1 +plant: + num_turbines: 20 +port: + monthly_rate: 100000 + name: Test Port + num_cranes: 1 +site: + depth: 500 + distance: 50 +turbine: + blade: + deck_space: 100 + mass: 100 + hub_height: 100 + nacelle: + deck_space: 200 + mass: 400 + tower: + deck_space: 100 + mass: 400 + length: 100 +wtiv: test_floating_heavy_lift_vessel diff --git a/wisdem/test/test_orbit/data/library/project/config/moored_install.yaml b/wisdem/test/test_orbit/data/library/project/config/moored_install.yaml index 90b0b5ffc..47cd8bb0a 100644 --- a/wisdem/test/test_orbit/data/library/project/config/moored_install.yaml +++ b/wisdem/test/test_orbit/data/library/project/config/moored_install.yaml @@ -11,6 +11,7 @@ site: substructure: takt_time: 168 towing_speed: 6 + unit_cost: 12e6 support_vessel: test_support_vessel towing_vessel: test_towing_vessel towing_vessel_groups: diff --git a/wisdem/test/test_orbit/data/library/project/config/moored_install_no_supply.yaml b/wisdem/test/test_orbit/data/library/project/config/moored_install_no_supply.yaml index 7a0782a25..249abd857 100644 --- a/wisdem/test/test_orbit/data/library/project/config/moored_install_no_supply.yaml +++ b/wisdem/test/test_orbit/data/library/project/config/moored_install_no_supply.yaml @@ -9,6 +9,7 @@ site: substructure: takt_time: 0 towing_speed: 6 + unit_cost: 12e6 support_vessel: test_support_vessel towing_vessel: test_towing_vessel towing_vessel_groups: diff --git a/wisdem/test/test_orbit/data/library/project/config/mooring_system_install.yaml b/wisdem/test/test_orbit/data/library/project/config/mooring_system_install.yaml index a9e34a609..7d32e74b8 100644 --- a/wisdem/test/test_orbit/data/library/project/config/mooring_system_install.yaml +++ b/wisdem/test/test_orbit/data/library/project/config/mooring_system_install.yaml @@ -8,3 +8,5 @@ mooring_system: num_lines: 3 line_mass: 500 anchor_mass: 100 + anchor_cost: 5e5 + line_cost: 1.5e6 diff --git a/wisdem/test/test_orbit/data/library/project/config/multi_wtiv_mono_install.yaml b/wisdem/test/test_orbit/data/library/project/config/multi_wtiv_mono_install.yaml index 7812e2e0f..74f22bfde 100644 --- a/wisdem/test/test_orbit/data/library/project/config/multi_wtiv_mono_install.yaml +++ b/wisdem/test/test_orbit/data/library/project/config/multi_wtiv_mono_install.yaml @@ -4,6 +4,7 @@ monopile: diameter: 10 length: 50 mass: 350 + unit_cost: 5e6 num_feeders: 1 plant: num_turbines: 20 @@ -17,6 +18,7 @@ site: transition_piece: deck_space: 250 mass: 350 + unit_cost: 3e6 turbine: hub_height: 100 wtiv: test_wtiv diff --git a/wisdem/test/test_orbit/data/library/project/config/oss_install.yaml b/wisdem/test/test_orbit/data/library/project/config/oss_install.yaml index 7c6f1945b..a7b00dd02 100644 --- a/wisdem/test/test_orbit/data/library/project/config/oss_install.yaml +++ b/wisdem/test/test_orbit/data/library/project/config/oss_install.yaml @@ -6,9 +6,11 @@ offshore_substation_substructure: length: 50 type: Monopile mass: 400 + unit_cost: 5e6 offshore_substation_topside: deck_space: 200 mass: 400 + unit_cost: 100e6 oss_install_vessel: test_heavy_lift_vessel port: monthly_rate: 100000 diff --git a/wisdem/test/test_orbit/data/library/project/config/oss_multi_feeder_substation_install.yaml b/wisdem/test/test_orbit/data/library/project/config/oss_multi_feeder_substation_install.yaml index 6f325b41c..6167b0b99 100644 --- a/wisdem/test/test_orbit/data/library/project/config/oss_multi_feeder_substation_install.yaml +++ b/wisdem/test/test_orbit/data/library/project/config/oss_multi_feeder_substation_install.yaml @@ -6,9 +6,11 @@ offshore_substation_substructure: length: 50 type: Monopile mass: 400 + unit_cost: 5e6 offshore_substation_topside: deck_space: 200 mass: 400 + unit_cost: 100e6 oss_install_vessel: test_heavy_lift_vessel port: monthly_rate: 100000 diff --git a/wisdem/test/test_orbit/data/library/project/config/project_manager.yaml b/wisdem/test/test_orbit/data/library/project/config/project_manager.yaml index 47187b966..46ce05e3b 100644 --- a/wisdem/test/test_orbit/data/library/project/config/project_manager.yaml +++ b/wisdem/test/test_orbit/data/library/project/config/project_manager.yaml @@ -10,6 +10,7 @@ monopile: diameter: 10 length: 50 mass: 350 + unit_cost: 5e6 plant: num_turbines: 10 turbine_spacing: 7 @@ -21,6 +22,7 @@ site: transition_piece: deck_space: 250 mass: 350 + unit_cost: 2e6 turbine: blade: deck_space: 100 diff --git a/wisdem/test/test_orbit/data/library/project/config/scour_protection_install.yaml b/wisdem/test/test_orbit/data/library/project/config/scour_protection_install.yaml index fd23223ce..f4600895d 100644 --- a/wisdem/test/test_orbit/data/library/project/config/scour_protection_install.yaml +++ b/wisdem/test/test_orbit/data/library/project/config/scour_protection_install.yaml @@ -4,7 +4,8 @@ plant: port: monthly_rate: 100000 scour_protection: - tons_per_substructure: 2000 + tonnes_per_substructure: 2000 + cost_per_tonne: 45 spi_vessel: test_scour_protection_vessel site: depth: 40 diff --git a/wisdem/test/test_orbit/data/library/project/config/single_wtiv_mono_install.yaml b/wisdem/test/test_orbit/data/library/project/config/single_wtiv_mono_install.yaml index 68adaccf0..9e4c65ff8 100644 --- a/wisdem/test/test_orbit/data/library/project/config/single_wtiv_mono_install.yaml +++ b/wisdem/test/test_orbit/data/library/project/config/single_wtiv_mono_install.yaml @@ -3,6 +3,7 @@ monopile: diameter: 10 length: 50 mass: 350 + unit_cost: 5e6 plant: num_turbines: 20 port: @@ -14,6 +15,7 @@ site: transition_piece: deck_space: 250 mass: 350 + unit_cost: 3e6 turbine: hub_height: 100 wtiv: test_wtiv diff --git a/wisdem/test/test_orbit/data/library/turbines/__init__.py b/wisdem/test/test_orbit/data/library/turbines/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wisdem/test/test_orbit/data/library/vessels/__init__.py b/wisdem/test/test_orbit/data/library/vessels/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/wisdem/test/test_orbit/data/library/vessels/test_cable_lay_vessel.yaml b/wisdem/test/test_orbit/data/library/vessels/test_cable_lay_vessel.yaml index c9df9a726..73bfb1a82 100644 --- a/wisdem/test/test_orbit/data/library/vessels/test_cable_lay_vessel.yaml +++ b/wisdem/test/test_orbit/data/library/vessels/test_cable_lay_vessel.yaml @@ -3,7 +3,6 @@ transport_specs: max_windspeed: 25 # m/s transit_speed: 11.5 # km/hr vessel_specs: - beam_length: 30.0 # m day_rate: 50000 # USD/day, cost of operating vessel with crew min_draft: 4.8 # m overall_length: 99.0 # m diff --git a/wisdem/test/test_orbit/data/library/vessels/test_feeder.yaml b/wisdem/test/test_orbit/data/library/vessels/test_feeder.yaml index 807db0420..c2785b028 100644 --- a/wisdem/test/test_orbit/data/library/vessels/test_feeder.yaml +++ b/wisdem/test/test_orbit/data/library/vessels/test_feeder.yaml @@ -5,7 +5,6 @@ jacksys_specs: leg_pen: 5 # m max_depth: 40 # m max_extension: 60 # m - num_legs: 4 speed_above_depth: 0.5 # m/min speed_below_depth: 0.5 # m/min storage_specs: @@ -17,8 +16,4 @@ transport_specs: max_windspeed: 20 # m/s transit_speed: 6 # km/h vessel_specs: - beam_length: 35 # m day_rate: 50000 # USD/day - max_draft: 5 # m - min_draft: 4 # m - overall_length: 60 # m diff --git a/wisdem/test/test_orbit/data/library/vessels/test_floating_barge.yaml b/wisdem/test/test_orbit/data/library/vessels/test_floating_barge.yaml new file mode 100644 index 000000000..1fcd8ef8f --- /dev/null +++ b/wisdem/test/test_orbit/data/library/vessels/test_floating_barge.yaml @@ -0,0 +1,14 @@ +crane_specs: + max_lift: 500 # t +dynamic_positioning_specs: + class: 2 # 1, 2 or 3 +storage_specs: + max_cargo: 8000 # t + max_deck_load: 8 # t/m^2 + max_deck_space: 1000 # m^2 +transport_specs: + max_waveheight: 2.5 # m + max_windspeed: 20 # m/s + transit_speed: 6 # km/h +vessel_specs: + day_rate: 120000 # USD/day diff --git a/wisdem/test/test_orbit/data/library/vessels/test_floating_heavy_lift_vessel.yaml b/wisdem/test/test_orbit/data/library/vessels/test_floating_heavy_lift_vessel.yaml new file mode 100644 index 000000000..44b9f7e9b --- /dev/null +++ b/wisdem/test/test_orbit/data/library/vessels/test_floating_heavy_lift_vessel.yaml @@ -0,0 +1,16 @@ +crane_specs: + max_hook_height: 72 # m + max_lift: 5500 # t + max_windspeed: 15 # m/s +dynamic_positioning_specs: + class: 2 # 1, 2 or 3 +storage_specs: + max_cargo: 8000 # t + max_deck_load: 15 # t/m^2 + max_deck_space: 4000 # m^2 +transport_specs: + max_waveheight: 2.5 # m + max_windspeed: 20 # m/s + transit_speed: 7 # km/h +vessel_specs: + day_rate: 500000 # USD/day diff --git a/wisdem/test/test_orbit/data/library/vessels/test_heavy_lift_vessel.yaml b/wisdem/test/test_orbit/data/library/vessels/test_heavy_lift_vessel.yaml index a63057d9f..9eddae7c6 100644 --- a/wisdem/test/test_orbit/data/library/vessels/test_heavy_lift_vessel.yaml +++ b/wisdem/test/test_orbit/data/library/vessels/test_heavy_lift_vessel.yaml @@ -1,16 +1,13 @@ crane_specs: - boom_length: 100 # m max_hook_height: 72 # m max_lift: 5500 # t max_windspeed: 15 # m/s - radius: 30 # m jacksys_specs: air_gap: 10 # m, distance from MSL to jacked up height leg_length: 110 # m leg_pen: 5 # m max_depth: 75 # m max_extension: 85 # m - num_legs: 6 speed_above_depth: 0.5 # m/min speed_below_depth: 0.5 # m/min storage_specs: @@ -23,5 +20,3 @@ transport_specs: transit_speed: 7 # km/h vessel_specs: day_rate: 500000 # USD/day - max_draft: 4.5 # m - overall_length: 102.75 # m diff --git a/wisdem/test/test_orbit/data/library/vessels/test_phase_specific_wtiv.yaml b/wisdem/test/test_orbit/data/library/vessels/test_phase_specific_wtiv.yaml index 8ae383127..b382722cf 100644 --- a/wisdem/test/test_orbit/data/library/vessels/test_phase_specific_wtiv.yaml +++ b/wisdem/test/test_orbit/data/library/vessels/test_phase_specific_wtiv.yaml @@ -1,17 +1,14 @@ name: Phase Specific WTIV crane_specs: - boom_length: 100 # m max_hook_height: 100 # m max_lift: 1200 # t max_windspeed: 15 # m/s - radius: 30 # m jacksys_specs: air_gap: 10 # m, distance from MSL to jacked up height leg_length: 110 # m leg_pen: 5 # m max_depth: 75 # m max_extension: 85 # m - num_legs: 6 speed_above_depth: 1 # m/min speed_below_depth: 2.5 # m/min storage_specs: @@ -23,8 +20,4 @@ transport_specs: max_windspeed: 20 # m/s transit_speed: 10 # km/h vessel_specs: - beam_length: 50 # m day_rate: 250000 # USD/day - max_draft: 6 # m - min_draft: 5 # m - overall_length: 150 # m diff --git a/wisdem/test/test_orbit/data/library/vessels/test_scour_protection_vessel.yaml b/wisdem/test/test_orbit/data/library/vessels/test_scour_protection_vessel.yaml index 83d0b7947..bb2fe5cfb 100644 --- a/wisdem/test/test_orbit/data/library/vessels/test_scour_protection_vessel.yaml +++ b/wisdem/test/test_orbit/data/library/vessels/test_scour_protection_vessel.yaml @@ -8,8 +8,4 @@ transport_specs: max_windspeed: 25 # m/s transit_speed: 11.5 # km/hr vessel_specs: - beam_length: 35 # m day_rate: 50000 # USD/day - max_draft: 5 # m - min_draft: 4 # m - overall_length: 60 # m diff --git a/wisdem/test/test_orbit/data/library/vessels/test_towing_vessel.yaml b/wisdem/test/test_orbit/data/library/vessels/test_towing_vessel.yaml index b1de857d4..5aa4bf588 100644 --- a/wisdem/test/test_orbit/data/library/vessels/test_towing_vessel.yaml +++ b/wisdem/test/test_orbit/data/library/vessels/test_towing_vessel.yaml @@ -3,8 +3,4 @@ transport_specs: max_windspeed: 20 # m/s transit_speed: 6 # km/h vessel_specs: - beam_length: 35 # m day_rate: 30000 # USD/day - max_draft: 5 # m - min_draft: 4 # m - overall_length: 60 # m diff --git a/wisdem/test/test_orbit/data/library/vessels/test_wtiv.yaml b/wisdem/test/test_orbit/data/library/vessels/test_wtiv.yaml index 899be7a7b..41b70505c 100644 --- a/wisdem/test/test_orbit/data/library/vessels/test_wtiv.yaml +++ b/wisdem/test/test_orbit/data/library/vessels/test_wtiv.yaml @@ -1,17 +1,14 @@ name: Example WTIV crane_specs: - boom_length: 100 # m max_hook_height: 100 # m max_lift: 1200 # t max_windspeed: 15 # m/s - radius: 30 # m jacksys_specs: air_gap: 10 # m, distance from MSL to jacked up height leg_length: 110 # m leg_pen: 5 # m max_depth: 75 # m max_extension: 85 # m - num_legs: 6 speed_above_depth: 1 # m/min speed_below_depth: 2.5 # m/min storage_specs: @@ -23,8 +20,4 @@ transport_specs: max_windspeed: 20 # m/s transit_speed: 10 # km/h vessel_specs: - beam_length: 50 # m day_rate: 250000 # USD/day - max_draft: 6 # m - min_draft: 5 # m - overall_length: 150 # m diff --git a/wisdem/test/test_orbit/data/library/vessels/test_wtiv_mobilize.yaml b/wisdem/test/test_orbit/data/library/vessels/test_wtiv_mobilize.yaml index 58034bb90..bc6b8bff4 100644 --- a/wisdem/test/test_orbit/data/library/vessels/test_wtiv_mobilize.yaml +++ b/wisdem/test/test_orbit/data/library/vessels/test_wtiv_mobilize.yaml @@ -1,17 +1,14 @@ name: Example WTIV crane_specs: - boom_length: 100 # m max_hook_height: 100 # m max_lift: 1200 # t max_windspeed: 15 # m/s - radius: 30 # m jacksys_specs: air_gap: 10 # m, distance from MSL to jacked up height leg_length: 110 # m leg_pen: 5 # m max_depth: 75 # m max_extension: 85 # m - num_legs: 6 speed_above_depth: 1 # m/min speed_below_depth: 2.5 # m/min storage_specs: @@ -23,10 +20,6 @@ transport_specs: max_windspeed: 20 # m/s transit_speed: 10 # km/h vessel_specs: - beam_length: 50 # m day_rate: 250000 # USD/day - max_draft: 6 # m - min_draft: 5 # m - overall_length: 150 # m mobilization_days: 14 # days mobilization_mult: 1 # diff --git a/wisdem/test/test_orbit/phases/design/test_array_system_design.py b/wisdem/test/test_orbit/phases/design/test_array_system_design.py index 9da26db35..d2974b4a3 100644 --- a/wisdem/test/test_orbit/phases/design/test_array_system_design.py +++ b/wisdem/test/test_orbit/phases/design/test_array_system_design.py @@ -10,7 +10,6 @@ import numpy as np import pytest - from wisdem.orbit.core.library import extract_library_specs from wisdem.orbit.phases.design import ArraySystemDesign, CustomArraySystemDesign from wisdem.orbit.core.exceptions import LibraryItemNotFoundError diff --git a/wisdem/test/test_orbit/phases/design/test_cable.py b/wisdem/test/test_orbit/phases/design/test_cable.py index 0f07d7a39..fa42c5e0d 100644 --- a/wisdem/test/test_orbit/phases/design/test_cable.py +++ b/wisdem/test/test_orbit/phases/design/test_cable.py @@ -11,7 +11,6 @@ import numpy as np import pytest - from wisdem.orbit.phases.design._cables import Cable, Plant cables = { diff --git a/wisdem/test/test_orbit/phases/design/test_export_system_design.py b/wisdem/test/test_orbit/phases/design/test_export_system_design.py index a41570e69..b0994f8d1 100644 --- a/wisdem/test/test_orbit/phases/design/test_export_system_design.py +++ b/wisdem/test/test_orbit/phases/design/test_export_system_design.py @@ -8,7 +8,6 @@ from copy import deepcopy import pytest - from wisdem.orbit.core.library import extract_library_specs from wisdem.orbit.phases.design import ExportSystemDesign diff --git a/wisdem/test/test_orbit/phases/design/test_mooring_system_design.py b/wisdem/test/test_orbit/phases/design/test_mooring_system_design.py index 0e1ed270c..51ca9b229 100644 --- a/wisdem/test/test_orbit/phases/design/test_mooring_system_design.py +++ b/wisdem/test/test_orbit/phases/design/test_mooring_system_design.py @@ -9,7 +9,6 @@ from copy import deepcopy import pytest - from wisdem.orbit.phases.design import MooringSystemDesign base = { @@ -29,7 +28,7 @@ def test_depth_sweep(depth): m.run() assert m.design_result - assert m.total_phase_cost + assert m.total_cost @pytest.mark.parametrize("rating", range(3, 15, 1)) @@ -42,7 +41,7 @@ def test_rating_sweeip(rating): m.run() assert m.design_result - assert m.total_phase_cost + assert m.total_cost def test_drag_embedment_fixed_length(): diff --git a/wisdem/test/test_orbit/phases/design/test_oss_design.py b/wisdem/test/test_orbit/phases/design/test_oss_design.py index 867078d78..e3c4199e5 100644 --- a/wisdem/test/test_orbit/phases/design/test_oss_design.py +++ b/wisdem/test/test_orbit/phases/design/test_oss_design.py @@ -8,7 +8,6 @@ from itertools import product import pytest - from wisdem.orbit.phases.design import OffshoreSubstationDesign base = { @@ -45,7 +44,7 @@ def test_parameter_sweep(depth, num_turbines, turbine_rating): assert 200 <= o._outputs["offshore_substation_topside"]["mass"] <= 5000 # Check valid substation cost - assert 1e6 <= o.total_phase_cost <= 300e6 + assert 1e6 <= o.total_cost <= 300e6 def test_oss_kwargs(): @@ -67,7 +66,7 @@ def test_oss_kwargs(): o = OffshoreSubstationDesign(base) o.run() - base_cost = o.total_phase_cost + base_cost = o.total_cost for k, v in test_kwargs.items(): @@ -77,6 +76,6 @@ def test_oss_kwargs(): o = OffshoreSubstationDesign(config) o.run() - cost = o.total_phase_cost + cost = o.total_cost assert cost != base_cost diff --git a/wisdem/test/test_orbit/phases/design/test_project_development.py b/wisdem/test/test_orbit/phases/design/test_project_development.py deleted file mode 100644 index 267be2f57..000000000 --- a/wisdem/test/test_orbit/phases/design/test_project_development.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Tests for the `ProjectDevelopment` class.""" - -__author__ = "Jake Nunemaker" -__copyright__ = "Copyright 2020, National Renewable Energy Laboratory" -__maintainer__ = "Jake Nunemaker" -__email__ = "jake.nunemaker@nrel.gov" - -import os -from copy import deepcopy - -import pytest - -from wisdem.orbit.phases.design import ProjectDevelopment - -base = { - "project_development": { - "site_auction_cost": 100e6, # USD - "site_auction_duration": 0, # hrs - "site_assessment_plan_cost": 0.5e6, # USD - "site_assessment_plan_duration": 8760, # hrs - "site_assessment_cost": 50e6, # USD - "site_assessment_duration": 43800, # hrs - "construction_operations_plan_cost": 1e6, # USD - "construction_operations_plan_duration": 43800, # hrs - "boem_review_cost": 0, # No cost to developer - "boem_review_duration": 8760, # hrs - "design_install_plan_cost": 0.25e6, # USD - "design_install_plan_duration": 8760, # hrs - } -} - - -def test_run(): - - dev = ProjectDevelopment(base) - dev.run() - - -def test_defaults_found(): - - for k, _ in base["project_development"].items(): - - new = deepcopy(base) - new["project_development"].pop(k) - - dev = ProjectDevelopment(new) - dev.run() diff --git a/wisdem/test/test_orbit/phases/design/test_scour_protection_design.py b/wisdem/test/test_orbit/phases/design/test_scour_protection_design.py index c22d07b2e..042075b38 100644 --- a/wisdem/test/test_orbit/phases/design/test_scour_protection_design.py +++ b/wisdem/test/test_orbit/phases/design/test_scour_protection_design.py @@ -10,7 +10,6 @@ import numpy as np import pytest - from wisdem.orbit.phases.design import ScourProtectionDesign config_min_defined = { @@ -40,7 +39,6 @@ def test_default_setup(): assert scour.phi == 33.5 assert scour.equilibrium == 1.3 assert scour.rock_density == 2600 - assert scour.total_phase_time == 0.0 def test_fully_defined_setup(): @@ -52,7 +50,6 @@ def test_fully_defined_setup(): assert scour.phi == design["soil_friction_angle"] assert scour.equilibrium == design["scour_depth_equilibrium"] assert scour.rock_density == design["rock_density"] - assert scour.total_phase_time == design["design_time"] @pytest.mark.parametrize( @@ -81,11 +78,4 @@ def test_total_cost(config): * config["scour_protection_design"]["cost_per_tonne"] * scour.scour_protection_tonnes ) - assert scour.total_phase_cost == pytest.approx(cost, rel=1e-8) - - -def test_design_result(): - scour = ScourProtectionDesign(config_min_defined) - scour.run() - - assert scour.design_result == {"scour_protection": {"tons_per_substructure": scour.scour_protection_tonnes}} + assert scour.total_cost == pytest.approx(cost, rel=1e-8) diff --git a/wisdem/test/test_orbit/phases/design/test_semisubmersible_design.py b/wisdem/test/test_orbit/phases/design/test_semisubmersible_design.py index 39dc3b78f..e5f7ac826 100644 --- a/wisdem/test/test_orbit/phases/design/test_semisubmersible_design.py +++ b/wisdem/test/test_orbit/phases/design/test_semisubmersible_design.py @@ -8,7 +8,6 @@ from itertools import product import pytest - from wisdem.orbit.phases.design import SemiSubmersibleDesign base = { @@ -49,7 +48,7 @@ def test_design_kwargs(): s = SemiSubmersibleDesign(base) s.run() - base_cost = s.total_phase_cost + base_cost = s.total_cost for k, v in test_kwargs.items(): @@ -59,6 +58,6 @@ def test_design_kwargs(): s = SemiSubmersibleDesign(config) s.run() - cost = s.total_phase_cost + cost = s.total_cost assert cost != base_cost diff --git a/wisdem/test/test_orbit/phases/design/test_spar_design.py b/wisdem/test/test_orbit/phases/design/test_spar_design.py index 888cf6839..2e39dfdef 100644 --- a/wisdem/test/test_orbit/phases/design/test_spar_design.py +++ b/wisdem/test/test_orbit/phases/design/test_spar_design.py @@ -8,7 +8,6 @@ from itertools import product import pytest - from wisdem.orbit.phases.design import SparDesign base = { @@ -49,7 +48,7 @@ def test_design_kwargs(): s = SparDesign(base) s.run() - base_cost = s.total_phase_cost + base_cost = s.total_cost for k, v in test_kwargs.items(): @@ -59,6 +58,6 @@ def test_design_kwargs(): s = SparDesign(config) s.run() - cost = s.total_phase_cost + cost = s.total_cost assert cost != base_cost diff --git a/wisdem/test/test_orbit/phases/install/cable_install/test_array_install.py b/wisdem/test/test_orbit/phases/install/cable_install/test_array_install.py index 6705e1772..324828bdb 100644 --- a/wisdem/test/test_orbit/phases/install/cable_install/test_array_install.py +++ b/wisdem/test/test_orbit/phases/install/cable_install/test_array_install.py @@ -12,12 +12,11 @@ import pandas as pd import pytest - from wisdem.orbit import ProjectManager -from wisdem.test.test_orbit.data import test_weather from wisdem.orbit.core.library import extract_library_specs from wisdem.orbit.core.defaults import process_times as pt from wisdem.orbit.phases.install import ArrayCableInstallation +from wisdem.test.test_orbit.data import test_weather base_config = extract_library_specs("config", "array_cable_install") simul_config = deepcopy(base_config) diff --git a/wisdem/test/test_orbit/phases/install/cable_install/test_export_install.py b/wisdem/test/test_orbit/phases/install/cable_install/test_export_install.py index 8d3b2ab56..8fd7b18a9 100644 --- a/wisdem/test/test_orbit/phases/install/cable_install/test_export_install.py +++ b/wisdem/test/test_orbit/phases/install/cable_install/test_export_install.py @@ -12,12 +12,11 @@ import pandas as pd import pytest - from wisdem.orbit import ProjectManager -from wisdem.test.test_orbit.data import test_weather from wisdem.orbit.core.library import extract_library_specs from wisdem.orbit.core.defaults import process_times as pt from wisdem.orbit.phases.install import ExportCableInstallation +from wisdem.test.test_orbit.data import test_weather base_config = extract_library_specs("config", "export_cable_install") simul_config = deepcopy(base_config) @@ -173,7 +172,10 @@ def test_kwargs_for_export_install(): def test_kwargs_for_export_install_in_ProjectManager(): - new_export_system = {"cable": {"linear_density": 50.0, "sections": [1000], "number": 1}} + new_export_system = { + "cable": {"linear_density": 50.0, "sections": [1000], "number": 1}, + "system_cost": 200e6, + } new_site = {"distance": 50, "depth": 20} base = deepcopy(base_config) base["export_system"] = new_export_system diff --git a/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_install.py b/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_install.py index 043891848..bcd45085a 100644 --- a/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_install.py +++ b/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_install.py @@ -10,12 +10,11 @@ import pandas as pd import pytest - from wisdem.orbit import ProjectManager -from wisdem.test.test_orbit.data import test_weather from wisdem.orbit.core.library import extract_library_specs from wisdem.orbit.core.defaults import process_times as pt from wisdem.orbit.phases.install import MonopileInstallation +from wisdem.test.test_orbit.data import test_weather config_wtiv = extract_library_specs("config", "single_wtiv_mono_install") config_wtiv_feeder = extract_library_specs("config", "multi_wtiv_mono_install") diff --git a/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_tasks.py b/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_tasks.py index 7218e0501..f6e0f1474 100644 --- a/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_tasks.py +++ b/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_tasks.py @@ -9,7 +9,6 @@ import pytest - from wisdem.orbit.core.exceptions import MissingComponent from wisdem.orbit.phases.install.monopile_install.common import ( drive_monopile, @@ -52,7 +51,6 @@ def test_task(env, wtiv, task, log, args): (upend_monopile, "Upend Monopile", [100]), (lower_monopile, "Lower Monopile", []), (drive_monopile, "Drive Monopile", []), - (lower_transition_piece, "Lower TP", []), ], ) def test_task_fails(env, feeder, task, log, args): diff --git a/wisdem/test/test_orbit/phases/install/mooring_install/test_mooring_install.py b/wisdem/test/test_orbit/phases/install/mooring_install/test_mooring_install.py index ee0e2fb27..20c77da9a 100644 --- a/wisdem/test/test_orbit/phases/install/mooring_install/test_mooring_install.py +++ b/wisdem/test/test_orbit/phases/install/mooring_install/test_mooring_install.py @@ -12,12 +12,11 @@ import pandas as pd import pytest - from wisdem.orbit import ProjectManager -from wisdem.test.test_orbit.data import test_weather from wisdem.orbit.core.library import extract_library_specs from wisdem.orbit.core.defaults import process_times as pt from wisdem.orbit.phases.install import MooringSystemInstallation +from wisdem.test.test_orbit.data import test_weather config = extract_library_specs("config", "mooring_system_install") @@ -29,7 +28,7 @@ def test_simulation_creation(): assert sim.env assert sim.port assert sim.vessel - assert sim.number_systems + assert sim.num_systems @pytest.mark.parametrize("weather", (None, test_weather), ids=["no_weather", "test_weather"]) diff --git a/wisdem/test/test_orbit/phases/install/oss_install/test_oss_install.py b/wisdem/test/test_orbit/phases/install/oss_install/test_oss_install.py index 70446a667..4e130be98 100644 --- a/wisdem/test/test_orbit/phases/install/oss_install/test_oss_install.py +++ b/wisdem/test/test_orbit/phases/install/oss_install/test_oss_install.py @@ -10,22 +10,23 @@ import pandas as pd import pytest - from wisdem.orbit import ProjectManager -from wisdem.test.test_orbit.data import test_weather from wisdem.orbit.core.library import extract_library_specs from wisdem.orbit.core.defaults import process_times as pt from wisdem.orbit.phases.install import OffshoreSubstationInstallation +from wisdem.test.test_orbit.data import test_weather +from wisdem.orbit.core.exceptions import MissingComponent config_single = extract_library_specs("config", "oss_install") +config_floating = extract_library_specs("config", "floating_oss_install") config_multi = extract_library_specs("config", "oss_install") config_multi["num_feeders"] = 2 @pytest.mark.parametrize( "config", - (config_single, config_multi), - ids=["single_feeder", "multi_feeder"], + (config_single, config_multi, config_floating), + ids=["single_feeder", "multi_feeder", "floating"], ) def test_simulation_setup(config): @@ -41,25 +42,29 @@ def test_simulation_setup(config): @pytest.mark.parametrize( "config", - (config_single, config_multi), - ids=["single_feeder", "multi_feeder"], + (config_single, config_multi, config_floating), + ids=["single_feeder", "multi_feeder", "floating"], ) def test_vessel_initialization(config): sim = OffshoreSubstationInstallation(config) assert sim.oss_vessel - assert sim.oss_vessel.jacksys assert sim.oss_vessel.crane + js = sim.oss_vessel._jacksys_specs + dp = sim.oss_vessel._dp_specs + + if not any([js, dp]): + assert False + for feeder in sim.feeders: - assert feeder.jacksys assert feeder.storage @pytest.mark.parametrize( "config", - (config_single, config_multi), - ids=["single_feeder", "multi_feeder"], + (config_single, config_multi, config_floating), + ids=["single_feeder", "multi_feeder", "floating"], ) @pytest.mark.parametrize("weather", (None, test_weather), ids=["no_weather", "test_weather"]) def test_for_complete_logging(weather, config): diff --git a/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_common.py b/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_common.py index 3da75c077..c5a5dbdfa 100644 --- a/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_common.py +++ b/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_common.py @@ -8,12 +8,8 @@ import pandas as pd import pytest - from wisdem.orbit.core import WetStorage -from wisdem.orbit.phases.install.quayside_assembly_tow.common import ( - TurbineAssemblyLine, - SubstructureAssemblyLine, -) +from wisdem.orbit.phases.install.quayside_assembly_tow.common import TurbineAssemblyLine, SubstructureAssemblyLine @pytest.mark.parametrize( diff --git a/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_gravity_based.py b/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_gravity_based.py index 7b00f7cc4..4c62cd54a 100644 --- a/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_gravity_based.py +++ b/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_gravity_based.py @@ -8,10 +8,9 @@ import pandas as pd import pytest - -from wisdem.test.test_orbit.data import test_weather from wisdem.orbit.core.library import extract_library_specs from wisdem.orbit.phases.install import GravityBasedInstallation +from wisdem.test.test_orbit.data import test_weather config = extract_library_specs("config", "moored_install") no_supply = extract_library_specs("config", "moored_install_no_supply") diff --git a/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_moored.py b/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_moored.py index 9bc79fe84..d424f67ae 100644 --- a/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_moored.py +++ b/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_moored.py @@ -8,10 +8,9 @@ import pandas as pd import pytest - -from wisdem.test.test_orbit.data import test_weather from wisdem.orbit.core.library import extract_library_specs from wisdem.orbit.phases.install import MooredSubInstallation +from wisdem.test.test_orbit.data import test_weather config = extract_library_specs("config", "moored_install") no_supply = extract_library_specs("config", "moored_install_no_supply") diff --git a/wisdem/test/test_orbit/phases/install/scour_protection_install/test_scour_protection.py b/wisdem/test/test_orbit/phases/install/scour_protection_install/test_scour_protection.py index 72bef4d3b..d822b7d9c 100644 --- a/wisdem/test/test_orbit/phases/install/scour_protection_install/test_scour_protection.py +++ b/wisdem/test/test_orbit/phases/install/scour_protection_install/test_scour_protection.py @@ -12,12 +12,11 @@ import pandas as pd import pytest - from wisdem.orbit import ProjectManager -from wisdem.test.test_orbit.data import test_weather from wisdem.orbit.core.library import extract_library_specs from wisdem.orbit.core.defaults import process_times as pt from wisdem.orbit.phases.install import ScourProtectionInstallation +from wisdem.test.test_orbit.data import test_weather config = extract_library_specs("config", "scour_protection_install") @@ -30,7 +29,7 @@ def test_simulation_creation(): assert sim.port assert sim.spi_vessel assert sim.num_turbines - assert sim.tons_per_substructure + assert sim.tonnes_per_substructure @pytest.mark.parametrize("weather", (None, test_weather), ids=["no_weather", "test_weather"]) diff --git a/wisdem/test/test_orbit/phases/install/turbine_install/test_turbine_install.py b/wisdem/test/test_orbit/phases/install/turbine_install/test_turbine_install.py index bf1130ea0..9d3770079 100644 --- a/wisdem/test/test_orbit/phases/install/turbine_install/test_turbine_install.py +++ b/wisdem/test/test_orbit/phases/install/turbine_install/test_turbine_install.py @@ -10,24 +10,24 @@ import pandas as pd import pytest - from wisdem.orbit import ProjectManager -from wisdem.test.test_orbit.data import test_weather from wisdem.orbit.core.library import extract_library_specs from wisdem.orbit.core.defaults import process_times as pt from wisdem.orbit.phases.install import TurbineInstallation +from wisdem.test.test_orbit.data import test_weather config_wtiv = extract_library_specs("config", "turbine_install_wtiv") config_long_mobilize = extract_library_specs("config", "turbine_install_long_mobilize") config_wtiv_feeder = extract_library_specs("config", "turbine_install_feeder") config_wtiv_multi_feeder = deepcopy(config_wtiv_feeder) config_wtiv_multi_feeder["num_feeders"] = 2 +floating = extract_library_specs("config", "floating_turbine_install_feeder") @pytest.mark.parametrize( "config", - (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder), - ids=["wtiv_only", "single_feeder", "multi_feeder"], + (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder, floating), + ids=["wtiv_only", "single_feeder", "multi_feeder", "floating"], ) def test_simulation_setup(config): @@ -49,22 +49,27 @@ def test_simulation_setup(config): @pytest.mark.parametrize( "config", - (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder), - ids=["wtiv_only", "single_feeder", "multi_feeder"], + (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder, floating), + ids=["wtiv_only", "single_feeder", "multi_feeder", "floating"], ) def test_vessel_creation(config): sim = TurbineInstallation(config) assert sim.wtiv - assert sim.wtiv.jacksys assert sim.wtiv.crane assert sim.wtiv.storage + js = sim.wtiv._jacksys_specs + dp = sim.wtiv._dp_specs + + if not any([js, dp]): + assert False + if config.get("feeder", None) is not None: assert len(sim.feeders) == config["num_feeders"] for feeder in sim.feeders: - assert feeder.jacksys + # assert feeder.jacksys assert feeder.storage @@ -80,8 +85,8 @@ def test_vessel_mobilize(config, expected): @pytest.mark.parametrize( "config", - (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder), - ids=["wtiv_only", "single_feeder", "multi_feeder"], + (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder, floating), + ids=["wtiv_only", "single_feeder", "multi_feeder", "floating"], ) @pytest.mark.parametrize("weather", (None, test_weather), ids=["no_weather", "test_weather"]) def test_for_complete_logging(weather, config): @@ -104,8 +109,8 @@ def test_for_complete_logging(weather, config): @pytest.mark.parametrize( "config", - (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder), - ids=["wtiv_only", "single_feeder", "multi_feeder"], + (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder, floating), + ids=["wtiv_only", "single_feeder", "multi_feeder", "floating"], ) def test_for_complete_installation(config): diff --git a/wisdem/test/test_orbit/test_design_install_phase_interactions.py b/wisdem/test/test_orbit/test_design_install_phase_interactions.py index ff4223350..d2469efa1 100644 --- a/wisdem/test/test_orbit/test_design_install_phase_interactions.py +++ b/wisdem/test/test_orbit/test_design_install_phase_interactions.py @@ -7,92 +7,82 @@ from copy import deepcopy from wisdem.orbit import ProjectManager +from numpy.testing import assert_almost_equal +from wisdem.orbit.core.library import extract_library_specs -config = { - "wtiv": "test_wtiv", - "site": {"depth": 20, "distance": 20, "mean_windspeed": 9}, - "plant": {"num_turbines": 20}, - "turbine": { - "hub_height": 130, - "rotor_diameter": 154, - "rated_windspeed": 11, - }, - "port": {"num_cranes": 1, "monthly_rate": 2e6}, - "monopile": { - "type": "Monopile", - "length": 60, - "diameter": 8, - "deck_space": 0, - "mass": 600, - }, - "transition_piece": { - "type": "Transition Piece", - "deck_space": 0, - "mass": 500, - }, - "monopile_design": {}, - "design_phases": ["MonopileDesign"], - "install_phases": ["MonopileInstallation"], -} - - -def test_monopile_definition(): - - test_config = deepcopy(config) - _ = test_config.pop("transition_piece") - - project = ProjectManager(test_config) - project.run_project() - - for key, value in config["monopile"].items(): - if key == "type": - continue +fixed = extract_library_specs("config", "complete_project") +floating = extract_library_specs("config", "complete_floating_project") - assert project.config["monopile"][key] == value - for key, value in config["transition_piece"].items(): - if key == "type": - continue +def test_fixed_phase_cost_passing(): - assert project.config["transition_piece"][key] != value + project = ProjectManager(fixed) + project.run_project() + assert_almost_equal( + project.phases["MonopileDesign"].total_cost, + project.phases["MonopileInstallation"].system_capex, + ) -def test_transition_piece_definition(): + assert_almost_equal( + project.phases["ScourProtectionDesign"].total_cost, + project.phases["ScourProtectionInstallation"].system_capex, + ) - test_config = deepcopy(config) - _ = test_config.pop("monopile") + assert_almost_equal( + project.phases["ArraySystemDesign"].total_cost, + project.phases["ArrayCableInstallation"].system_capex, + ) - project = ProjectManager(test_config) - project.run_project() + assert_almost_equal( + project.phases["ExportSystemDesign"].total_cost, + project.phases["ExportCableInstallation"].system_capex, + ) - for key, value in config["monopile"].items(): - if key == "type": - continue + assert_almost_equal( + project.phases["OffshoreSubstationDesign"].total_cost, + project.phases["OffshoreSubstationInstallation"].system_capex, + ) - assert project.config["monopile"][key] != value - for key, value in config["transition_piece"].items(): - if key == "type": - continue +def test_floating_phase_cost_passing(): - assert project.config["transition_piece"][key] == value + project = ProjectManager(floating) + project.run_project() + assert_almost_equal( + project.phases["MooringSystemDesign"].total_cost, + project.phases["MooringSystemInstallation"].system_capex, + ) -def test_mono_and_tp_definition(): + assert_almost_equal( + project.phases["SemiSubmersibleDesign"].total_cost, + project.phases["MooredSubInstallation"].system_capex, + ) - test_config = deepcopy(config) + assert_almost_equal( + project.phases["ArraySystemDesign"].total_cost, + project.phases["ArrayCableInstallation"].system_capex, + ) - project = ProjectManager(test_config) - project.run_project() + assert_almost_equal( + project.phases["ExportSystemDesign"].total_cost, + project.phases["ExportCableInstallation"].system_capex, + ) - for key, value in config["monopile"].items(): - if key == "type": - continue + assert_almost_equal( + project.phases["OffshoreSubstationDesign"].total_cost, + project.phases["OffshoreSubstationInstallation"].system_capex, + ) - assert project.config["monopile"][key] == value + spar = deepcopy(floating) + spar["design_phases"].remove("SemiSubmersibleDesign") + spar["design_phases"].append("SparDesign") - for key, value in config["transition_piece"].items(): - if key == "type": - continue + project = ProjectManager(spar) + project.run_project() - assert project.config["transition_piece"][key] == value + assert_almost_equal( + project.phases["SparDesign"].total_cost, + project.phases["MooredSubInstallation"].system_capex, + ) diff --git a/wisdem/test/test_orbit/test_project_manager.py b/wisdem/test/test_orbit/test_project_manager.py index 8b3c4c52d..33b2671d0 100644 --- a/wisdem/test/test_orbit/test_project_manager.py +++ b/wisdem/test/test_orbit/test_project_manager.py @@ -8,17 +8,11 @@ import pandas as pd import pytest - from wisdem.orbit import ProjectManager -from wisdem.test.test_orbit.data import test_weather from wisdem.orbit.manager import ProjectProgress from wisdem.orbit.core.library import extract_library_specs -from wisdem.orbit.core.exceptions import ( - MissingInputs, - PhaseNotFound, - WeatherProfileError, - PhaseDependenciesInvalid, -) +from wisdem.test.test_orbit.data import test_weather +from wisdem.orbit.core.exceptions import MissingInputs, PhaseNotFound, WeatherProfileError, PhaseDependenciesInvalid weather_df = pd.DataFrame(test_weather).set_index("datetime") @@ -169,7 +163,10 @@ def test_chained_dependencies(): config_chained = deepcopy(config) config_chained["spi_vessel"] = "test_scour_protection_vessel" - config_chained["scour_protection"] = {"tons_per_substructure": 200} + config_chained["scour_protection"] = { + "tonnes_per_substructure": 200, + "cost_per_tonne": 45, + } config_chained["install_phases"] = { "ScourProtectionInstallation": 0, "MonopileInstallation": ("ScourProtectionInstallation", 0.1), @@ -407,7 +404,10 @@ def test_circular_dependencies(): circular_deps = deepcopy(config) circular_deps["spi_vessel"] = "test_scour_protection_vessel" - circular_deps["scour_protection"] = {"tons_per_substructure": 200} + circular_deps["scour_protection"] = { + "tonnes_per_substructure": 200, + "cost_per_tonne": 45, + } circular_deps["install_phases"] = { "ScourProtectionInstallation": 0, "MonopileInstallation": ("TurbineInstallation", 0.1), @@ -423,7 +423,10 @@ def test_dependent_phase_ordering(): wrong_order = deepcopy(config) wrong_order["spi_vessel"] = "test_scour_protection_vessel" - wrong_order["scour_protection"] = {"tons_per_substructure": 200} + wrong_order["scour_protection"] = { + "tonnes_per_substructure": 200, + "cost_per_tonne": 45, + } wrong_order["install_phases"] = { "ScourProtectionInstallation": ("TurbineInstallation", 0.1), "TurbineInstallation": ("MonopileInstallation", 0.1), @@ -603,3 +606,65 @@ def test_npv(): project = ProjectManager(config) project.run_project() assert project.npv != baseline + + +def test_soft_costs(): + + project = ProjectManager(complete_project) + baseline = project.soft_capex + + config = deepcopy(complete_project) + config["project_parameters"] = {"construction_insurance": 50} + project = ProjectManager(config) + assert project.soft_capex != baseline + + config = deepcopy(complete_project) + config["project_parameters"] = {"construction_financing": 190} + project = ProjectManager(config) + assert project.soft_capex != baseline + + config = deepcopy(complete_project) + config["project_parameters"] = {"contingency": 320} + project = ProjectManager(config) + assert project.soft_capex != baseline + + config = deepcopy(complete_project) + config["project_parameters"] = {"contingency": 320} + project = ProjectManager(config) + assert project.soft_capex != baseline + + config = deepcopy(complete_project) + config["project_parameters"] = {"commissioning": 50} + project = ProjectManager(config) + assert project.soft_capex != baseline + + config = deepcopy(complete_project) + config["project_parameters"] = {"decommissioning": 50} + project = ProjectManager(config) + assert project.soft_capex != baseline + + +def test_project_costs(): + + project = ProjectManager(complete_project) + baseline = project.project_capex + + config = deepcopy(complete_project) + config["project_parameters"] = {"site_auction_price": 50e6} + project = ProjectManager(config) + assert project.project_capex != baseline + + config = deepcopy(complete_project) + config["project_parameters"] = {"site_assessment_cost": 25e6} + project = ProjectManager(config) + assert project.project_capex != baseline + + config = deepcopy(complete_project) + config["project_parameters"] = {"construction_plan_cost": 25e6} + project = ProjectManager(config) + assert project.project_capex != baseline + + config = deepcopy(complete_project) + config["project_parameters"] = {"installation_plan_cost": 25e6} + project = ProjectManager(config) + assert project.project_capex != baseline diff --git a/wisdem/test/test_rotorse/rail.npz b/wisdem/test/test_rotorse/rail.npz new file mode 100644 index 0000000000000000000000000000000000000000..308ab4bc8664d23328dcb79349520f0377c977a5 GIT binary patch literal 103014 zcmd42XH-Jiuvt|=!WD_wIV!O)7W@uq;YprAc*uq-h;4fT4=c$bW4!1GVu{6M4U%t+LmGL_J zRmK;L|F6f1zen-^|4QK$nMWE-6k_i3!t&UOrH=7)a;~25T$HV*UJdU$TJtLeg{nHfx0{=Sx z`Tl=3#s8BbGBW;;DUxHj`%b(sJxPvH{oQ=Mo1Ppa@ev!2?M@Bga^qUyv|JC&xT(9Uh;@^+S8) zHt)948H}LrZ^1ofGR(!*wS$Q2)0hc9E7kGYlNf*fPA;2MB$#PoZg*eoag4{a-aGJ| z2*b8CYgtuBh;hb*;j?@{is6=J+Kv@Gg4ymU@UW;BhBRpSf|=5>cKA5HUFjJTX_#nWq)7^-g(wnt}i6gKCnX*!?5 zoVo5?5oAJ&IZJu>g|-?A=ArqQaDvlBn1u1Ipqc(782OjUhms>dL4l$<`1RBtu&*Ox zQ<5E^)A5-6c4H1s>?K8}TSlUk50zJl%h!<^->rev%Rf=2WY)Cb0YR;>ZLQHT5pgY< z`>Bc<;p4T<8{}gxmrv9VC3!6R)t{)X4LJRq@yChUf2Yc;w*A{bsUpT%mE&U@z5iCG z3dx@db}xn?E~g2OTSrnrTvoe5KfVz<`U9{`6&E@jsO$7`14jT4$+*_>-^Hm?EtOmATt zSnA->^Q6R5&uVyjO)2ojiE<$Qlz(K2uoyC$yuXya&4&;|d|@HeY=~V{Kl~z=4nv#% zwiVXNV3TSgEo&4DGz_+(IV>T-QPhI(7Uv3|Q7ILP#zPQkSUaB(C=MR(u_9+zHQ@IE zDbv+zedv{tIjH@t2b3@Nmyg*$`46LNi|xAeznOCSFJ`JtP@g?~>=GKCWr$@AE(ReH zEo*FTDv)`L3Y_Vwg%96<-`nD9g_v9U+KpGbV9Dx*eo9a;tj_rzz4@gVDhBLI*pKx= z=+r`rmPa30S0o9Gc=o|}#UNG8PA?4BnPQz2djPQF7Zd3^0V7HL&S0;* z7jiGscEyoog0f`b^&fcYki#Z;)rut*1}fz@--{%{r&W3ZjkEFaUM?ljn==7A&Ta$; zOb7o5Q|5+Cs{dq)5NA`^|5k%yTZdD6;#V$hXCn~yUoWOxOnfcz=Jb)cT92?EpLC%i3#xfZ0jV@!T&OP0wetj9EQo5c! zN^}{+%=qHtPkuJc@A}5H&xK3A^ZWQILfWv)g1kvOZ0N>0r=`5sIL~sC|gP?4&@68Syq$ z&?g|r6V{e-K7sE5WCO$u@4wsm z51P&trAhpgroWaxYa@&QTBcYep2xqHNW`Q@XB-qUM`PNErnL+vLofyPT8*aBuQ4?# z0s+`vFN|iU^u>$Z?wI}DkUfvw=a@|K(UWwo4j5TUZT4$N>@j>*N=wZfc9;aL5WPm8 z9mb04=&>DoI}BEL`Qm1$4d#c;F9wYiYm8i6$bxT-3d+wOU!ur2gzTT%`x>v zEHTt6rkIY^9GXuQ#+cM0=ey5e7-DwluB0U`=wWuq82gRobufxd9eJcOS{N@ua=9Qi zHH_iZ@OEjO62|uRK`dLiH0B7oCj&~rVqW$+_9(m%z~~mqdcAwVf!Tj~!{nYJ?SHs_ zWvV>m|H0JV|5lqi6nrKZM$v>xE9lF*qFRgTZl$fgwx5gfdKPGMa5V|@LTYlKq$dL7 zUE^;#SssjWVb?uS!oI@nXJ6E~Qs#wGCfm?6-|@s~2E9Kme&~t$GVM2sQTD{J?rBl8 zj<{oNbF;`E-gLz@Z+tX=?&*X{(V_j~)N6||X!g#Hd2flazpL0T#%6*sVj3coxT=rA zh<^F_FzOLTcS@I8NvI>wJl&@eRX zH@=LSSkc+TOQFT+(-!VgN}a$c|HiizD%^t|t^K0wJ+rWW+@yEgyY@e9%1~l;^`A@~ z!`YPNe=ScciV=l_A}peJ)SCMnlo>^DPIK|?{-zhb@J{yHiN@2Sy(3o?35f|rMe24< zzxMsWE->y%30Z!^DrO3pU7=aQJ_~!Vs4Xy$)!bm97hIpkrl}`1YqLyYjaFs!NG3rV8ovEl=iHy1UrO7sRV-DJGLv=Ka3bU#E?b6G+VQ`)eIpO6%8^fJshEmA^jeYg zag4^Ay@@f1=M2W$RM2rUr@q8ma=A9JCOKf2>P+JA`9H-zy%_X(VM-V4v>K<>OQ(({ zyWTHwB~KRnI?+>j%KkQ%G&@$ZGvz84FFr}YZKQzpGmeXzVH+uS zvyY1!TCd(^bUH2?DPXdbeEhhm&$oMW*R_a6D_j^4qU?!8IhT|%d=HL^y4=qARZC1L zO8F77Px>7d^*=H?8Z?J5YV}g)V#--O(U)=#v-OxmEF}@fkxX+BD`>iNJ^tY7*Bf}4@ltEbM@|!PMhRPOhVZR;hK|cFz!`(IP`IP+V zq1pv({eTqRwB0OrpI=&E{M#6|{>G5L4$mNVntt4O+@l+7oj$-a>)3#;5uu8@lwFK1 zweC$PzmtaLrjRwgt^O9vcZ`CL!N?UWN$WFlMO_c;>cD=a>Ok^8nEJ}}_36Je<>d6= z%G7!8c*Y7GQ&(*$Fkjwtil*5(W~c$JXq(ONpT7i;i~h!|HgZ_Q6P0yu{g&?h9b56U zxWvY87yIb?UeNNlE$ox?JLg^rtz%`CQ9x~5q5yXoA?=dCf3vEcej{!0`^9l(*2O{!Pt^*!)4V@ zckE-PV=U*(&9Ni$+kLOR?qd_@><#JU`LL8na_G3^X#Rt#w|&Bq|IU=-e=SpkIv>BX zGYDbD8h#V(HSAy?t;O8ZQu~D!Ec@7LPPdO$nrBvfK>P)3-H@xGOtOVNbZ-wxky*kD z&WUBt-kQT=#Z1_Ur6;kh_C6h}6=PUsJY8~rkr8a{nUv6BfkEsK*Vio54ZT?Y;Y4c} zxi)MBL)TYdzj|!Y)AmLFU*%Y~X|-&BvwSR}+WYmlW@%WqD}oPf(xb6Hg0!}Nrh!=5 znXoih$`{y?pxfei1ny&(9Q|E%qKxaZnXIA&XKm`#PH#7VDX^^5B~QhpEYhp{AjKT{ zE?ETLWv$f11}{xBoi+`Tlz&{2xTwm4KzVY?m!8Rzq zc3H)CgrnFqapO!o+Yl)+LdVyThTaY-4pdKUgU_Ayb)FOXsA_hx?F!n4DM1ldk&F_Q zk!N(k5WNl8E!s)(ZdM_mQ`=857TfTxAjV8#x)yz(dT{T>&27*VHu5VAYe2e^w4w5N zxc-Y0ZfsziQQN#G-&pk)+-%dk{B*7rEd;mCG1zVak6GgbDw_^;!D6x6`lEulJ&rdv~nN%{PH?kMdZeULO*htovknViRf}{Fan`)sF(y zKH#S%Y=CFCQRZOU06H6d3vTjkfKqB^Qdij^s(+wLYgn}ol#dy$H_#9oJu#?}E4mIM z6%U)EDuH;a1}o;>nj>);tje&Ij9;w7&@wB|X3&FyhCCFJ5}L%sqo)DPu}6DQD6ihPKo z>oN%QCaKMdO(KJd1(K1~C7`mRAncu(L^+GW_3p|`Q0n8>^w3}mb<;aJM3ygtiYKFV zz}ysyKW>h+85UtE`qciR;xuyfz;sH!TmYws)SP_<)95glOz6$ZJXG?;)MgRQpi5fO z*Ii}j;pchV=xd5IC`8Rz*fsAX6sl4g%lgkCY3m2sm6RVLNARRpNyQB6KFZ%6;4}y8 zUdoS(re@I7WRn=7nOQ(saNXaXL8q*Jxi-aTA(6kGOY7GRsu%TGB-We(2XXz;$us$G<9ZFE@zE^l#*cI=1UI+goPzbBE>x1A$c|2|-dE7G)hJ&E>-n$}*X^uqFe zLxC0YNfei{B_!b216+cqh5AD$koUKKuAjqQa94@ATZ?o8MJGnFg!6X79b;Q#A@6aN zq&WF#J-QthDK6wWeIG-iY}RxSDB8eWZ}DP-;TYm>iSclWXn}ggZ-n%Hqp00VrE%s; zGkiI;VY)3eiZmtP%r|v5LK$gM@7b6UWNc%T)cCvsX3GcBDSkTzjPw%9C;2Gx(qmJapsaxN(cI7-$(A3 zlLj%JwnCHl+mT-)eXXEj3aB+Wo>V{HhRRyDM!$$B0pp9rGFjAuMuWST)@2evDdc(N zoZ^#^+>`o zQ>NT96eO8-K4v@Cq0df~TIbt>Ku@Fp>oaUE>Qr_AY~Ju1YHE&B7|_=sB}0`9HY&bw zI#xwkqPh|xXWgYN)t5lXZkQ)(SdN(Fzlf?1xq-X0%E2OaDSAMD;#0r)bFdR&(y!?& zLT=cVqJ&U8Fne=hn)+=4GRMFE{A{x&IEHp>SwGH0B$EAhiMqzXFUTSo`y>m|n`v~$ z{n7zL;-J)ArgZe(%q*#4PYq6R3n?sZCZj%~zKxtH1(+Dgd9e{0kL=51->xR#fulr> z)e+9`&@TDX`_UuULF*>Vj^eREL`G-Zc*}tjqxCjnssw9C34)G7 zuc^yjV0B_4S>rb^jJ;?{<~SM%+P?#?FT~w}Tf+=71WxZDJp6h1w;%;5d}Q#+Ei@jU zHecau(ldJ97bs8aFrLY*gejrCR|-+DfxM4d`U+7E6z*c2 zvT}o9SBUf5E&f_iyWr+NqZkUOGNjMun$$u2C=I#e^>CP*M}?Ki^>CxUoQz2!62cqx zI<{vTpcF6vaanc@JdPV{P-kcYjf_T>295-fGqz%%)M*BilYG_U47g~m-NW~rX)U0g z*IAS+lmauo9b-XTtss6Q>14Z88k`~GJ{>324s>675;v#Qfr^Gr+v;Tp1Riqv-Z9IB z#aZf`to@xZ8h4MZo+Sq|WqnSlGj+qjZw$v-&pdc{*LOtOst2wQ$(|8EQ2` zz7E2@7~<)^=31D=OB*JQ90E*J!X@T!bwK{;gTaRIFhok1xZ(3RKx?$O%aMs;cy=#v zy56-B`0yuCtm6oLGFeS#9Bl%M5Rrp!ic$Dn`uWn>trmE1Q*!Vna}+MybN=SZZw0UL zGc@CvF=#5y&lKWqhZik#2JHP~aJgKmHM6z@9#6dEGn1F13nv-FeKG2Cy>^f^W32nWF1VZfn z5dW*=yvq0_d||(T=_1Vl+!$dXVwai%n|sY9rl$s>M`I!@FLw%Zebn8miHD%NQD7gR zY#QQBgD?~)hG9lHk%+-~8VGl9pSy5o1UOlRnzSmW;nS$~RMz=X7|w`zp?P!$in#lI zUviDX>(ItSKCv0ds+*m@BQXwD#0)k8&u1Xu=E&Ptoe5Cu{mNSSVFq5F#D0F|J_$Vj z0jsm!Gw{9a_!slkDL9)$Y#XyS1Gqr4)IjGnP{g%cocb{XCXI<=uHR;0;3$XuFI=+p zQ7;9bW5_JXgiCZqqwD)N2BZKCFMeE@a0^S)YORlJN=2(DMX6bWL zi_7c4_faLs9w)02>C6{-{@x&l84 zgV5lU31B*XwGw()AngXjW$NN_5O!o%CDvGl{+!p!Z@-QKi)>M^{l`_fo8qEV$36yc zJfp7j7_I^P!W*FmrBT3R{*i>gv<7FWT7CwbjR4j8{RMZObyz;vU^Dk(7>J|jryh^4 z!v;5r+ZT@^*bn)c|Mcz#L~?|&>^KjCMwrXgwbBhZ#cN@uhx0og=6;uWaA6aUFn1(3 zDfPoM?rZ7YuA3m;zVC0u*$1T(-c`3IHbLz>`ywG;FW?D;l-%at0^I}V5~a6YFvKJj zQ0KA*KVpSvooPD2x3117vUv+qf8)#Vy=jGTxwTapqHQ>{x#fLpuL(Y7$SV$EaQ^tE zbNaf<4ItRk93^D44W4Sx{(@6~PtUXJ0Y^n()M zF>!rgHn|O1cBzStU3tJcZlDu|(+k#}2j>~B)4=s4_L~JxPi!7Zy7JvA{BNCs_dj}L zufFQT?jKp{rnbRyrlm>FG8|3p9gdKeY{N&ZS67d`NJBjso&8t7 z+mNOspK9HjhdRSVIxX*SL(*#|o>H?C)bZfX1+w$o5D@g)sD`Kt&EU6YCr)oc9hIr4 zW_>NnJ*mgK9=Qdz8{D_z92!ue1)1u31>F27#_2RLH6v%yLm`b{n^4rjJ)(nJk>efz z%O~D%!iwpa95#gx6utnt318t>^sIjI~397`J@ewLJQl%W@iPY>-yId6d2 zeIlz7`94HCE_>SP)CRmhbaCRw`A3S8z9Y*K>o7ZUSun|W09~*2QWauY2TU?s{q5jE z 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 qXT?MC86JX Date: Sun, 17 Jan 2021 18:04:29 -0700 Subject: [PATCH 2/4] adapt code to new wisdem --- weis/aeroelasticse/openmdao_openfast.py | 8 ++++---- weis/control/dac.py | 2 +- weis/control/tune_rosco.py | 2 +- weis/glue_code/gc_LoadInputs.py | 11 +++-------- weis/glue_code/gc_ROSCOInputs.py | 4 ++-- weis/glue_code/glue_code.py | 6 +++--- weis/inputs/modeling_schema.yaml | 4 ---- weis/inputs/validation.py | 2 +- 8 files changed, 15 insertions(+), 24 deletions(-) diff --git a/weis/aeroelasticse/openmdao_openfast.py b/weis/aeroelasticse/openmdao_openfast.py index 7ed994e95..cb6453bc0 100644 --- a/weis/aeroelasticse/openmdao_openfast.py +++ b/weis/aeroelasticse/openmdao_openfast.py @@ -95,10 +95,10 @@ def setup(self): # tower properties self.add_input('fore_aft_modes', val=np.zeros((n_freq_tower,5)), desc='6-degree polynomial coefficients of mode shapes in the flap direction (x^2..x^6, no linear or constant term)') self.add_input('side_side_modes', val=np.zeros((n_freq_tower,5)), desc='6-degree polynomial coefficients of mode shapes in the edge direction (x^2..x^6, no linear or constant term)') - self.add_input('sec_loc', val=np.zeros(nFull-1), desc='normalized sectional location') - self.add_input('mass_den', val=np.zeros(nFull-1), units='kg/m', desc='sectional mass per unit length') - self.add_input('foreaft_stff', val=np.zeros(nFull-1), units='N*m**2', desc='sectional fore-aft bending stiffness per unit length about the Y_E elastic axis') - self.add_input('sideside_stff', val=np.zeros(nFull-1), units='N*m**2', desc='sectional side-side bending stiffness per unit length about the Y_E elastic axis') + self.add_input('sec_loc', val=np.zeros(n_height-1), desc='normalized sectional location') + self.add_input('mass_den', val=np.zeros(n_height-1), units='kg/m', desc='sectional mass per unit length') + self.add_input('foreaft_stff', val=np.zeros(n_height-1), units='N*m**2', desc='sectional fore-aft bending stiffness per unit length about the Y_E elastic axis') + self.add_input('sideside_stff', val=np.zeros(n_height-1), units='N*m**2', desc='sectional side-side bending stiffness per unit length about the Y_E elastic axis') self.add_input('tower_section_height', val=np.zeros(n_height-1), units='m', desc='parameterized section heights along cylinder') self.add_input('tower_outer_diameter', val=np.zeros(n_height), units='m', desc='cylinder diameter at corresponding locations') self.add_input('tower_monopile_z', val=np.zeros(n_height), units='m', desc='z-coordinates of tower and monopile used in TowerSE') diff --git a/weis/control/dac.py b/weis/control/dac.py index 344d3cfc4..d602ec580 100644 --- a/weis/control/dac.py +++ b/weis/control/dac.py @@ -186,7 +186,7 @@ def initialize(self): self.options.declare('opt_options') def setup(self): - rotorse_options = self.options['modeling_options']['RotorSE'] + rotorse_options = self.options['modeling_options']['WISDEM']['RotorSE'] self.n_span = n_span = rotorse_options['n_span'] self.n_te_flaps = n_te_flaps = rotorse_options['n_te_flaps'] self.n_tab = rotorse_options['n_tab'] diff --git a/weis/control/tune_rosco.py b/weis/control/tune_rosco.py index 30761c68f..651fb7c93 100644 --- a/weis/control/tune_rosco.py +++ b/weis/control/tune_rosco.py @@ -361,7 +361,7 @@ def initialize(self): def setup(self): modeling_options = self.options['modeling_options'] - rotorse_options = modeling_options['RotorSE'] + rotorse_options = modeling_options['WISDEM']['RotorSE'] self.n_span = n_span = rotorse_options['n_span'] self.n_aoa = n_aoa = rotorse_options['n_aoa']# Number of angle of attacks self.n_Re = n_Re = rotorse_options['n_Re'] # Number of Reynolds, so far hard set at 1 diff --git a/weis/glue_code/gc_LoadInputs.py b/weis/glue_code/gc_LoadInputs.py index 56a1eec1b..b15e1df9c 100644 --- a/weis/glue_code/gc_LoadInputs.py +++ b/weis/glue_code/gc_LoadInputs.py @@ -15,11 +15,6 @@ def __init__(self, fname_input_wt, fname_input_modeling, fname_input_analysis): self.wt_init = sch.load_geometry_yaml(fname_input_wt) self.analysis_options = sch.load_analysis_yaml(fname_input_analysis) - self.modeling_options['RotorSE'] = self.modeling_options['WISDEM']['RotorSE'] - self.modeling_options['DriveSE'] = self.modeling_options['WISDEM']['DriveSE'] - self.modeling_options['TowerSE'] = self.modeling_options['WISDEM']['TowerSE'] - self.modeling_options['BOS'] = self.modeling_options['WISDEM']['BOS'] - self.set_run_flags() self.set_openmdao_vectors() self.set_openmdao_vectors_control() @@ -69,11 +64,11 @@ def set_openfast_data(self): def set_openmdao_vectors_control(self): # Distributed aerodynamic control devices along blade - self.modeling_options['RotorSE']['n_te_flaps'] = 0 + self.modeling_options['WISDEM']['RotorSE']['n_te_flaps'] = 0 if 'aerodynamic_control' in self.wt_init['components']['blade']: if 'te_flaps' in self.wt_init['components']['blade']['aerodynamic_control']: - self.modeling_options['RotorSE']['n_te_flaps'] = len(self.wt_init['components']['blade']['aerodynamic_control']['te_flaps']) - self.modeling_options['RotorSE']['n_tab'] = 3 + self.modeling_options['WISDEM']['RotorSE']['n_te_flaps'] = len(self.wt_init['components']['blade']['aerodynamic_control']['te_flaps']) + self.modeling_options['WISDEM']['RotorSE']['n_tab'] = 3 else: exit('A distributed aerodynamic control device is provided in the yaml input file, but not supported by wisdem.') diff --git a/weis/glue_code/gc_ROSCOInputs.py b/weis/glue_code/gc_ROSCOInputs.py index 9dbb74d09..fe24d8de4 100644 --- a/weis/glue_code/gc_ROSCOInputs.py +++ b/weis/glue_code/gc_ROSCOInputs.py @@ -17,9 +17,9 @@ def assign_ROSCO_values(wt_opt, modeling_options, control): wt_opt['tune_rosco_ivc.ss_pcgain'] = control['setpoint_smooth']['ss_pcgain'] wt_opt['tune_rosco_ivc.ps_percent'] = control['pitch']['ps_percent'] # Check for proper Flp_Mode, print warning - if modeling_options['RotorSE']['n_tab'] > 1 and modeling_options['Level3']['ROSCO']['Flp_Mode'] == 0: + if modeling_options['WISDEM']['RotorSE']['n_tab'] > 1 and modeling_options['Level3']['ROSCO']['Flp_Mode'] == 0: raise Exception('A distributed aerodynamic control device is specified in the geometry yaml, but Flp_Mode is zero in the modeling options.') - if modeling_options['RotorSE']['n_tab'] == 1 and modeling_options['Level3']['ROSCO']['Flp_Mode'] > 0: + if modeling_options['WISDEM']['RotorSE']['n_tab'] == 1 and modeling_options['Level3']['ROSCO']['Flp_Mode'] > 0: raise Exception('Flp_Mode is non zero in the modeling options, but no distributed aerodynamic control device is specified in the geometry yaml.') return wt_opt diff --git a/weis/glue_code/glue_code.py b/weis/glue_code/glue_code.py index 51d5cc1b7..c5153bc2c 100644 --- a/weis/glue_code/glue_code.py +++ b/weis/glue_code/glue_code.py @@ -498,7 +498,7 @@ def setup(self): self.connect('generator.C_Fes' , 'drivese_post.generator.C_Fes') self.connect('generator.C_PM' , 'drivese_post.generator.C_PM') - if modeling_options['GeneratorSE']['type'] in ['pmsg_outer']: + if modeling_options['WISDEM']['GeneratorSE']['type'] in ['pmsg_outer']: self.connect('generator.N_c' , 'drivese_post.generator.N_c') self.connect('generator.b' , 'drivese_post.generator.b') self.connect('generator.c' , 'drivese_post.generator.c') @@ -516,13 +516,13 @@ def setup(self): self.connect('generator.B_tmax' , 'drivese_post.generator.B_tmax') self.connect('rp.powercurve.rated_mech', 'drivese_post.generator.P_mech') - if modeling_options['GeneratorSE']['type'] in ['eesg','pmsg_arms','pmsg_disc']: + if modeling_options['WISDEM']['GeneratorSE']['type'] in ['eesg','pmsg_arms','pmsg_disc']: self.connect('generator.tau_p' , 'drivese_post.generator.tau_p') self.connect('generator.h_ys' , 'drivese_post.generator.h_ys') self.connect('generator.h_yr' , 'drivese_post.generator.h_yr') self.connect('generator.b_arm' , 'drivese_post.generator.b_arm') - elif modeling_options['GeneratorSE']['type'] in ['scig','dfig']: + elif modeling_options['WISDEM']['GeneratorSE']['type'] in ['scig','dfig']: self.connect('generator.B_symax' , 'drivese_post.generator.B_symax') self.connect('generator.S_Nmax' , 'drivese_post.generator.S_Nmax') diff --git a/weis/inputs/modeling_schema.yaml b/weis/inputs/modeling_schema.yaml index c1d4ee1a7..6d215f667 100644 --- a/weis/inputs/modeling_schema.yaml +++ b/weis/inputs/modeling_schema.yaml @@ -12,10 +12,6 @@ properties: type: boolean default: False description: Prints additional outputs to screen (and to a file log in the future) - WISDEM: - type: object - default: {} - description: Options for running WISDEM. No further options are included in this file. They are populated using the modeling schema in the WISDEM project in python. Level1: type: object default: {} diff --git a/weis/inputs/validation.py b/weis/inputs/validation.py index 4651e78af..0b1f217e0 100644 --- a/weis/inputs/validation.py +++ b/weis/inputs/validation.py @@ -39,7 +39,7 @@ def write_geometry_yaml(instance, foutput): def get_modeling_schema(): wisdem_schema = load_yaml(fschema_model_wisdem) weis_schema = load_yaml(fschema_model) - weis_schema['properties']['WISDEM'] = wisdem_schema + weis_schema['properties']['WISDEM'] = wisdem_schema['properties']['WISDEM'] return weis_schema def load_modeling_yaml(finput): From af839564a803e776b45f30495cb25b2f230edca9 Mon Sep 17 00:00:00 2001 From: pibo Date: Sun, 17 Jan 2021 18:10:13 -0700 Subject: [PATCH 3/4] fix old bug with TD ratio --- weis/glue_code/gc_PoseOptimization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weis/glue_code/gc_PoseOptimization.py b/weis/glue_code/gc_PoseOptimization.py index c9a862a40..c3ac96099 100644 --- a/weis/glue_code/gc_PoseOptimization.py +++ b/weis/glue_code/gc_PoseOptimization.py @@ -254,7 +254,7 @@ def set_constraints(self, wt_opt): if blade_constraints['tip_deflection']['flag']: if self.blade_opt['structure']['spar_cap_ss']['flag'] or self.blade_opt['structure']['spar_cap_ps']['flag']: - wt_opt.model.add_constraint('tcons.tip_deflection_ratio', upper=blade_constraints['tip_deflection']['ratio']) + wt_opt.model.add_constraint('tcons.tip_deflection_ratio', upper=1.) else: print('WARNING: the tip deflection is set to be constrained, but spar caps thickness is not an active design variable. The constraint is not enforced.') From 070061837a9a86f1d9f0c05a37cda4635940aa0d Mon Sep 17 00:00:00 2001 From: Pietro Bortolotti Date: Mon, 18 Jan 2021 09:10:58 -0700 Subject: [PATCH 4/4] fix tip deflection(again?) --- weis/glue_code/gc_PoseOptimization.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/weis/glue_code/gc_PoseOptimization.py b/weis/glue_code/gc_PoseOptimization.py index c3ac96099..820f9b271 100644 --- a/weis/glue_code/gc_PoseOptimization.py +++ b/weis/glue_code/gc_PoseOptimization.py @@ -117,7 +117,7 @@ def set_objective(self, wt_opt): elif self.opt['merit_figure'] == 'LCOE': wt_opt.model.add_objective('financese.lcoe', ref = 0.1) elif self.opt['merit_figure'] == 'blade_tip_deflection': - wt_opt.model.add_objective('tcons.tip_deflection_ratio') + wt_opt.model.add_objective('tcons_post.tip_deflection_ratio') elif self.opt['merit_figure'] == 'tower_mass': wt_opt.model.add_objective('towerse.tower_mass') elif self.opt['merit_figure'] == 'tower_cost': @@ -254,7 +254,7 @@ def set_constraints(self, wt_opt): if blade_constraints['tip_deflection']['flag']: if self.blade_opt['structure']['spar_cap_ss']['flag'] or self.blade_opt['structure']['spar_cap_ps']['flag']: - wt_opt.model.add_constraint('tcons.tip_deflection_ratio', upper=1.) + wt_opt.model.add_constraint('tcons_post.tip_deflection_ratio', upper=1.) else: print('WARNING: the tip deflection is set to be constrained, but spar caps thickness is not an active design variable. The constraint is not enforced.')