From afd4efa86432cbc3f7badf34fc700c716eec9f89 Mon Sep 17 00:00:00 2001 From: pibo Date: Mon, 28 Dec 2020 10:01:06 -0700 Subject: [PATCH 1/8] we_mode can be 1 --- weis/inputs/modeling_schema.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/weis/inputs/modeling_schema.yaml b/weis/inputs/modeling_schema.yaml index c1d4ee1a7..f5ccc07c9 100644 --- a/weis/inputs/modeling_schema.yaml +++ b/weis/inputs/modeling_schema.yaml @@ -2338,9 +2338,9 @@ properties: description: Setpoint Smoother mode {0, no setpoint smoothing, 1, use setpoint smoothing} WE_Mode: type: integer - enum: [0, 2] + enum: [0, 1, 2] default: 2 - description: Wind speed estimator mode {0 = One-second low pass filtered hub height wind speed, 2 = Extended Kalman Filter} + description: Wind speed estimator mode {0 = One-second low pass filtered hub height wind speed, 1 = Immersion and Invariance Estimator, 2 = Extended Kalman Filter} PS_Mode: type: integer enum: [0, 1] From 9aebc4db856976a35993a7d162b6a1a51e1b286f Mon Sep 17 00:00:00 2001 From: pibo Date: Mon, 28 Dec 2020 10:19:27 -0700 Subject: [PATCH 2/8] mod options key is rotorse not blade --- weis/glue_code/gc_PoseOptimization.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/weis/glue_code/gc_PoseOptimization.py b/weis/glue_code/gc_PoseOptimization.py index c9a862a40..356667ff5 100644 --- a/weis/glue_code/gc_PoseOptimization.py +++ b/weis/glue_code/gc_PoseOptimization.py @@ -19,7 +19,7 @@ def get_number_design_variables(self): if self.blade_opt['aero_shape']['chord']['flag']: n_DV += self.blade_opt['aero_shape']['chord']['n_opt'] - 3 if self.blade_opt['aero_shape']['af_positions']['flag']: - n_DV += self.modeling['blade']['n_af_span'] - self.blade_opt['aero_shape']['af_positions']['af_start'] - 1 + n_DV += self.modeling['RotorSE']['n_af_span'] - self.blade_opt['aero_shape']['af_positions']['af_start'] - 1 if self.blade_opt['structure']['spar_cap_ss']['flag']: n_DV += self.blade_opt['structure']['spar_cap_ss']['n_opt'] - 2 if self.blade_opt['structure']['spar_cap_ps']['flag'] and not self.blade_opt['structure']['spar_cap_ps']['equal_to_suction']: @@ -33,9 +33,9 @@ def get_number_design_variables(self): if self.opt['design_variables']['control']['servo']['flap_control']['flag']: n_DV += 2 if self.opt['design_variables']['control']['flaps']['te_flap_end']['flag']: - n_DV += self.modeling['blade']['n_te_flaps'] + n_DV += self.modeling['RotorSE']['n_te_flaps'] if self.opt['design_variables']['control']['flaps']['te_flap_ext']['flag']: - n_DV += self.modeling['blade']['n_te_flaps'] + n_DV += self.modeling['RotorSE']['n_te_flaps'] if self.tower_opt['outer_diameter']['flag']: n_DV += self.modeling['tower']['n_height'] if self.tower_opt['layer_thickness']['flag']: @@ -157,7 +157,7 @@ def set_design_variables(self, wt_opt, wt_init): wt_opt.model.add_design_var('blade.opt_var.chord_opt_gain', indices = indices, lower=chord_options['min_gain'], upper=chord_options['max_gain']) if self.blade_opt['aero_shape']['af_positions']['flag']: - n_af = self.modeling['blade']['n_af_span'] + n_af = self.modeling['RotorSE']['n_af_span'] indices = range(self.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() From 095d67544cf4e36c62e2cb78797f7db937ee17f6 Mon Sep 17 00:00:00 2001 From: pibo Date: Mon, 28 Dec 2020 12:33:31 -0700 Subject: [PATCH 3/8] clean up --- weis/glue_code/gc_LoadInputs.py | 2 +- weis/glue_code/sbatch_eagle.sh | 9 +- weis/inputs/geometry_defaults.yaml | 475 ----------------------------- weis/inputs/geometry_schema.yaml | 1 + 4 files changed, 5 insertions(+), 482 deletions(-) delete mode 100644 weis/inputs/geometry_defaults.yaml diff --git a/weis/glue_code/gc_LoadInputs.py b/weis/glue_code/gc_LoadInputs.py index 56a1eec1b..12e7c8917 100644 --- a/weis/glue_code/gc_LoadInputs.py +++ b/weis/glue_code/gc_LoadInputs.py @@ -75,7 +75,7 @@ def set_openmdao_vectors_control(self): self.modeling_options['RotorSE']['n_te_flaps'] = len(self.wt_init['components']['blade']['aerodynamic_control']['te_flaps']) self.modeling_options['RotorSE']['n_tab'] = 3 else: - exit('A distributed aerodynamic control device is provided in the yaml input file, but not supported by wisdem.') + raise Exception('A distributed aerodynamic control device is provided in the yaml input file, but not supported by wisdem.') def update_ontology_control(self, wt_opt): # Update controller diff --git a/weis/glue_code/sbatch_eagle.sh b/weis/glue_code/sbatch_eagle.sh index 4e8f8cbdd..f4cf6d340 100644 --- a/weis/glue_code/sbatch_eagle.sh +++ b/weis/glue_code/sbatch_eagle.sh @@ -14,13 +14,10 @@ nDV=11 # Number of design variables (x2 for central difference) nOF=100 # Number of openfast runs per finite-difference evaluation nC=$(( nDV + nDV * nOF )) # Number of cores needed. Make sure to request an appropriate number of nodes = N / 36 -source deactivate - module purge -module load conda -module load mkl/2019.1.144 cmake/3.12.3 -module load gcc/8.2.0 +module load comp-intel intel-mpi mkl +module unload gcc -conda activate weis-env +source activate weis-env mpirun -np $nC python runWEIS.py diff --git a/weis/inputs/geometry_defaults.yaml b/weis/inputs/geometry_defaults.yaml deleted file mode 100644 index a623f9ded..000000000 --- a/weis/inputs/geometry_defaults.yaml +++ /dev/null @@ -1,475 +0,0 @@ -name: turbine_example_yaml -description: version from May 15th, 2020 -# Components -components: - # Blade - blade: - # Blade - Outer Shape BEM - outer_shape_bem: - airfoil_position: - grid: [0.0, 0.4, 0.8, 1.0] - labels: [cylinder, DU00-W2-350, DU08-W-210, DU08-W-210] - chord: - grid: [0.0, 0.5, 1.0] - values: [2.6, 3.0, 0.2] - twist: &twist - grid: [0.0, 0.3, 1.0] - values: [0.349, 0.1, -0.05] - pitch_axis: - grid: [0.0, 0.7, 1.0] - values: [0.5, 0.25, 0.25] - reference_axis: - x: &id001 - grid: [0.0, 0.2, 0.7, 1.0] - values: [0.0, 0.0, -1.0, -2.5] - y: &id002 - grid: [0.0, 1.0] - values: [0.0, 0.0] - z: &id003 - grid: [0.0, 1.0] - values: [0.0, 63.0] - # Blade - Elastic properties - elastic_properties_mb: - six_x_six: - reference_axis: - x: *id001 - y: *id002 - z: *id003 - twist: *twist - stiff_matrix: - grid: [0.0, 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] - inertia_matrix: - grid: [0.0, 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] - # Internal Structure 2D FEM - internal_structure_2d_fem: - reference_axis: - x: *id001 - y: *id002 - z: *id003 - # Webs - webs: - - name: fore_web - rotation: - grid: [0.1, 0.94] - values: [-0.1, -0.0] - offset_y_pa: - grid: [0.1, 0.94] - values: [-0.2, -0.3] - # Web 2 - - name: rear_web - start_nd_arc: - values: [0.2, 0.3] - grid: [0.1, 0.94] - end_nd_arc: - values: [0.7, 0.6] - grid: [0.1, 0.94] - # Layers - layers: - # Layer 1 - - name: UV_protection - material: paint - thickness: - grid: [0.0, 1.0] - values: [0.0005, 0.0005] - start_nd_arc: - values: [0.0, 0.0] - grid: [0.0, 1.0] - end_nd_arc: - values: [1.0, 1.0] - grid: [0.0, 1.0] - # Layer 2 - - name: Shell_skin - material: triax - fiber_orientation: - grid: [0.0, 1.0] - values: [0.0, 0.0] - thickness: - grid: [0.0, 1.0] - values: [0.065, 0.001] - # Layer 3 - - name: Spar_cap_ss - material: ud - thickness: - grid: [0.1, 0.5, 0.94] - values: [0.01, 0.05, 0.002] - fiber_orientation: - grid: [0.1, 0.94] - values: [0.0, 0.0] - width: - grid: [0.1, 0.94] - values: [0.8, 0.8] - offset_y_pa: - grid: [0.1, 0.94] - values: [0.042, 0.246] - rotation: - fixed: twist - side: suction - # Layer 4 - - name: LE_reinf - material: ud - thickness: - grid: [0.1, 0.8] - values: [0.003, 0.001] - midpoint_nd_arc: - fixed: LE - width: - grid: [0.1, 0.8] - values: [0.4, 0.4] - # Layer 5 - - name: TE_reinforcement - material: ud - thickness: - grid: [0.1, 0.8] - values: [0.001, 0.001] - midpoint_nd_arc: - fixed: TE - width: - grid: [0.1, 0.8] - values: [0.8, 0.8] - # Layer 6 - - name: TE_SS_filler - material: balsa - thickness: - grid: [0.1, 0.8] - values: [0.020, 0.010] - start_nd_arc: - fixed: TE_reinforcement - end_nd_arc: - fixed: Spar_cap_ss - # Web layer - - name: Web_aft_skin - material: biax - web: fore_web - thickness: - grid: [0.1, 0.94] - values: [0.001, 0.001] - # Hub - hub: - outer_shape_bem: - diameter: 4. - cone_angle: 0.0523 # 3 deg - drag_coefficient: 0.5 - elastic_properties_mb: - system_mass: 21834.7 - system_inertia: [57184.5, 57184.5, 57184.5, 0.0, 0.0, 0.0] - system_center_mass: [5.0, 0.0, 0.0] - # Nacelle - nacelle: - outer_shape_bem: - uptilt_angle: 0.08726 # 5 deg - distance_tt_hub: 2. - overhang: 5. - length: 12. - width: 6. - height: 5. - drag_coefficient: 0.5 - elastic_properties_mb: - above_yaw_mass: 118260.1 - yaw_mass: 3130.5 - inertia: [50326.1, 691063.5, 640737.4, 0.0, 6093.0, 0.0] - center_mass: [1.791, 0.0, 0.339] - # end nacelle - drivetrain: - gear_ratio: 97 - shaft_ratio: 0.1 - planet_numbers: [3, 3, 1] - shrink_disc_mass: 1600 - carrier_mass: 8000 - flange_length: 0.5 - gearbox_input_xcm: 0.1 - hss_input_length: 1.5 - distance_hub2mb: 1.912 - yaw_motors_number: 1 - gearbox_efficiency: 0.95 - generator_efficiency: 0.98 - # Tower - 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.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.] - values: [0.0, 10.80, 21.61, 32.41, 43.22, 54.02, 64.82, 75.63, 86.43, 97.23, 108.00] - outer_diameter: - grid: *grid_tower - values: [5.99, 5.93, 5.93, 5.93, 5.93, 5.93, 5.85, 5.12, 4.36, 3.61, 3.00] - drag_coefficient: - grid: [0.0, 1.0] - values: [0.5, 0.5] - internal_structure_2d_fem: - outfitting_factor: 1.0 - reference_axis: *ref_axis_tower - layers: - - name: tower_wall - material: steel - thickness: - grid: *grid_tower - values: [0.05697, 0.05697, 0.05047, 0.04664, 0.03935, 0.03354, 0.02801, 0.02357, 0.02374, 0.02241, 0.02674] - # Monopile - monopile: - transition_piece_height: 0.0 - transition_piece_mass: 0.0 - transition_piece_cost: 0.0 - gravity_foundation_mass: 0.0 - suctionpile_depth: 0.0 - suctionpile_depth_diam_ratio: 0.0 - 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.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.] - values: [0.0, 10.80, 21.61, 32.41, 43.22, 54.02, 64.82, 75.63, 86.43, 97.23, 108.00] - outer_diameter: - grid: *grid_mono - values: [5.99, 5.93, 5.93, 5.93, 5.93, 5.93, 5.85, 5.12, 4.36, 3.61, 3.00] - drag_coefficient: - grid: [0.0, 1.0] - values: [0.5, 0.5] - internal_structure_2d_fem: - outfitting_factor: 1.0 - reference_axis: *ref_axis_mono - layers: - - name: tower_wall - material: steel - thickness: - grid: *grid_mono - values: [0.05697, 0.05697, 0.05047, 0.04664, 0.03935, 0.03354, 0.02801, 0.02357, 0.02374, 0.02241, 0.02674] - # Foundation - foundation: - height: 0. - # Floating platform - floating_platform: - joints: - - name: spar_keel - location: [0.0, 0.0, 0.0] - - name: spar_freeboard - location: [0.0, 0.0, 10.0] - transition: True - members: - - name: spar - joint1: spar_keel - joint2: spar_freeboard - outer_shape: - shape: circular - outer_diameter: - grid: [0.0, 0.8308, 0.8923, 1.0] - values: [9.4, 9.4, 6.5, 6.5] - internal_structure: - layers: - - name: spar_twall - material: steel - thickness: - grid: [0.0, 1.0] - values: [0.05, 0.05] - bulkhead: - material: steel - thickness: - grid: [0.0, 0.1, 0.5, 0.8308, 1.0] - values: [0.1, 0.05, 0.05, 0.05, 0.05] - ballast: - - variable_flag: False - material: slurry - volume: 1e3 - grid: [0.0, 0.1] - - variable_flag: True - grid: [0.1, 0.5] - # Mooring lines - mooring: - nodes: - - name: line1_anchor - node_type: fixed - location: [853.87, 0.0, -320.0] - anchor_type: drag_embedment - - name: line2_anchor - node_type: fixed - location: [-426.94, 739.47, -320.0] - anchor_type: drag_embedment - - name: line3_anchor - node_type: fixed - location: [-426.94, -739.47, -320.0] - anchor_type: drag_embedment - - name: line1_vessel - node_type: vessel - location: [5.2, 0.0, -70.0] - fairlead_type: rigid - - name: line2_vessel - node_type: vessel - location: [-2.6, 4.5, -70.0] - fairlead_type: rigid - - name: line3_vessel - node_type: vessel - location: [-2.6, -4.5, -70.0] - fairlead_type: rigid - lines: - - name: line1 - node1: line1_anchor - node2: line1_vessel - line_type: main - unstretched_length: 902.2 - - name: line2 - node1: line2_anchor - node2: line2_vessel - line_type: main - unstretched_length: 902.2 - - name: line3 - node1: line3_anchor - node2: line3_vessel - line_type: main - unstretched_length: 902.2 - line_types: - - name: main - diameter: 0.09 - mass_density: 77.7066 - stiffness: 384.243e6 - breaking_load: 1e8 - cost: 100.0 - transverse_added_mass: 1.0 - tangential_added_mass: 0.0 - transverse_drag: 1.6 - tangential_drag: 0.1 - anchor_types: - - name: drag_embedment - mass: 1e3 - cost: 1e4 - max_vertical_load: 0.0 - max_lateral_load: 1e5 - # RNA - RNA: - elastic_properties_mb: - mass: 1.E+7 - inertia: [1.E+7, 0.0, 0.0, 0.0, 0.0, 0.0] - center_mass: [1., 0.0, 0.5] -# Airfoils -airfoils: - - name: DU08-W-210 - coordinates: - x: [1.00000, 0.99000, 0.98000, 0.97000, 0.96000, 0.95000, 0.94000, 0.93000, 0.92000, 0.91000, 0.90000, 0.87500, 0.85000, 0.82500, 0.80000, 0.77500, 0.75000, 0.72500, 0.70000, 0.67500, 0.65000, 0.62500, 0.60000, 0.57500, 0.55000, 0.52500, 0.50000, 0.47500, 0.45000, 0.44000, 0.43000, 0.42000, 0.41000, 0.40000, 0.39000, 0.38000, 0.37000, 0.36000, 0.35000, 0.34000, 0.33000, 0.32000, 0.31000, 0.30000, 0.29000, 0.28000, 0.27000, 0.26000, 0.25000, 0.24000, 0.23000, 0.22000, 0.21000, 0.20000, 0.19000, 0.18000, 0.17000, 0.16000, 0.15000, 0.14000, 0.13000, 0.12000, 0.11000, 0.10000, 0.09500, 0.09000, 0.08500, 0.08000, 0.07500, 0.07000, 0.06500, 0.06000, 0.05500, 0.05000, 0.04500, 0.04000, 0.03500, 0.03000, 0.02500, 0.02000, 0.01750, 0.01500, 0.01250, 0.01000, 0.00900, 0.00800, 0.00700, 0.00600, 0.00500, 0.00400, 0.00300, 0.00200, 0.00175, 0.00150, 0.00125, 0.00100, 0.00075, 0.00050, 0.00040, 0.00030, 0.00020, 0.00010, 0.00000, 0.00010, 0.00020, 0.00030, 0.00040, 0.00050, 0.00075, 0.00100, 0.00125, 0.00150, 0.00175, 0.00200, 0.00300, 0.00400, 0.00500, 0.00600, 0.00700, 0.00800, 0.00900, 0.01000, 0.01250, 0.01500, 0.01750, 0.02000, 0.02500, 0.03000, 0.03500, 0.04000, 0.04500, 0.05000, 0.05500, 0.06000, 0.06500, 0.07000, 0.07500, 0.08000, 0.08500, 0.09000, 0.09500, 0.10000, 0.11000, 0.12000, 0.13000, 0.14000, 0.15000, 0.16000, 0.17000, 0.18000, 0.19000, 0.20000, 0.21000, 0.22000, 0.23000, 0.24000, 0.25000, 0.26000, 0.27000, 0.28000, 0.29000, 0.30000, 0.31000, 0.32000, 0.33000, 0.34000, 0.35000, 0.36000, 0.37000, 0.38000, 0.39000, 0.40000, 0.41000, 0.42000, 0.43000, 0.44000, 0.45000, 0.47500, 0.50000, 0.52500, 0.55000, 0.57500, 0.60000, 0.62500, 0.65000, 0.67500, 0.70000, 0.72500, 0.75000, 0.77500, 0.80000, 0.82500, 0.85000, 0.87500, 0.90000, 0.91000, 0.92000, 0.93000, 0.94000, 0.95000, 0.96000, 0.97000, 0.98000, 0.99000, 1.00000] - y: [0.00139, 0.00403, 0.00656, 0.00907, 0.01155, 0.01400, 0.01644, 0.01886, 0.02128, 0.02370, 0.02610, 0.03209, 0.03801, 0.04386, 0.04965, 0.05535, 0.06095, 0.06644, 0.07183, 0.07711, 0.08228, 0.08729, 0.09212, 0.09675, 0.10116, 0.10529, 0.10912, 0.11259, 0.11567, 0.11677, 0.11779, 0.11873, 0.11959, 0.12036, 0.12104, 0.12162, 0.12209, 0.12245, 0.12268, 0.12277, 0.12270, 0.12247, 0.12208, 0.12155, 0.12086, 0.12002, 0.11903, 0.11789, 0.11661, 0.11519, 0.11361, 0.11190, 0.11003, 0.10801, 0.10583, 0.10348, 0.10098, 0.09830, 0.09545, 0.09241, 0.08917, 0.08572, 0.08204, 0.07812, 0.07605, 0.07392, 0.07170, 0.06941, 0.06703, 0.06455, 0.06198, 0.05929, 0.05649, 0.05355, 0.05047, 0.04721, 0.04377, 0.04009, 0.03615, 0.03185, 0.02953, 0.02708, 0.02445, 0.02161, 0.02040, 0.01914, 0.01781, 0.01640, 0.01489, 0.01326, 0.01145, 0.00934, 0.00874, 0.00808, 0.00736, 0.00656, 0.00565, 0.00456, 0.00406, 0.00349, 0.00283, 0.00198, 0.00000, -0.00197, -0.00279, -0.00341, -0.00394, -0.00440, -0.00539, -0.00623, -0.00697, -0.00765, -0.00827, -0.00884, -0.01087, -0.01258, -0.01410, -0.01549, -0.01677, -0.01796, -0.01909, -0.02017, -0.02267, -0.02496, -0.02710, -0.02910, -0.03279, -0.03613, -0.03919, -0.04201, -0.04463, -0.04708, -0.04938, -0.05156, -0.05362, -0.05558, -0.05744, -0.05922, -0.06092, -0.06254, -0.06410, -0.06559, -0.06838, -0.07095, -0.07330, -0.07545, -0.07741, -0.07920, -0.08082, -0.08227, -0.08356, -0.08469, -0.08568, -0.08652, -0.08721, -0.08777, -0.08819, -0.08847, -0.08862, -0.08864, -0.08853, -0.08829, -0.08792, -0.08743, -0.08682, -0.08610, -0.08526, -0.08433, -0.08329, -0.08216, -0.08093, -0.07960, -0.07818, -0.07667, -0.07507, -0.07337, -0.07159, -0.06678, -0.06150, -0.05578, -0.04973, -0.04343, -0.03694, -0.03038, -0.02385, -0.01751, -0.01151, -0.00597, -0.00105, 0.00312, 0.00635, 0.00855, 0.00973, 0.00988, 0.00902, 0.00841, 0.00766, 0.00678, 0.00578, 0.00468, 0.00349, 0.00223, 0.00095, -0.00033, -0.00139] - relative_thickness: 0.21 - aerodynamic_center: 0.25 - polars: - - configuration: Polars computed from Ellypsis3D at DTU - re: 6.00E+06 - c_l: - grid: &grid003 [-3.141592653589793, -3.054326, -2.967060, -2.879793, -2.792527, -2.705260, -2.617994, -2.530727, -2.443461, -2.356194, -2.268928, -2.181662, -2.094395, -2.007129, -1.919862, -1.832596, -1.745329, -1.658063, -1.570796, -1.483530, -1.396263, -1.308997, -1.221730, -1.134464, -1.047198, -0.959931, -0.872665, -0.785398, -0.698132, -0.663225, -0.628319, -0.593412, -0.558505, -0.523599, -0.488692, -0.453786, -0.418879, -0.383972, -0.349066, -0.331613, -0.314159, -0.296706, -0.279253, -0.261799, -0.244346, -0.235619, -0.226893, -0.218166, -0.209440, -0.200713, -0.191986, -0.183260, -0.174533, -0.165806, -0.157080, -0.148353, -0.139626, -0.130900, -0.122173, -0.113446, -0.104720, -0.095993, -0.087266, -0.078540, -0.069813, -0.061087, -0.052360, -0.043633, -0.034907, -0.026180, -0.017453, -0.008727, 0.000000, 0.008727, 0.017453, 0.026180, 0.034907, 0.043633, 0.052360, 0.061087, 0.069813, 0.078540, 0.087266, 0.095993, 0.104720, 0.113446, 0.122173, 0.130900, 0.139626, 0.148353, 0.157080, 0.165806, 0.174533, 0.183260, 0.191986, 0.200713, 0.209440, 0.218166, 0.226893, 0.235619, 0.244346, 0.261799, 0.279253, 0.296706, 0.314159, 0.331613, 0.349066, 0.383972, 0.418879, 0.453786, 0.488692, 0.523599, 0.558505, 0.593412, 0.628319, 0.663225, 0.698132, 0.785398, 0.872665, 0.959931, 1.047198, 1.134464, 1.221730, 1.308997, 1.396263, 1.483530, 1.570796, 1.658063, 1.745329, 1.832596, 1.919862, 2.007129, 2.094395, 2.181662, 2.268928, 2.356194, 2.443461, 2.530727, 2.617994, 2.705260, 2.792527, 2.879793, 2.967060, 3.054326, 3.141592653589793] - values: [0.000000, 0.250261, 0.500522, 0.654570, 0.808618, 0.764463, 0.720308, 0.695745, 0.671182, 0.636331, 0.601480, 0.547889, 0.494297, 0.422213, 0.350129, 0.264892, 0.179654, 0.089827, 0.000000, -0.089827, -0.179654, -0.264892, -0.350129, -0.422213, -0.494297, -0.547889, -0.601480, -0.640504, -0.671182, -0.681652, -0.691442, -0.700866, -0.710322, -0.720308, -0.731459, -0.744593, -0.760782, -0.781463, -0.808618, -0.825459, -0.820000, -0.813739, -0.782412, -0.751085, -0.719757, -0.704094, -0.688430, -0.672767, -0.657103, -0.641440, -0.625776, -0.610113, -0.594449, -0.578786, -0.563122, -0.535502, -0.507882, -0.437324, -0.366100, -0.303500, -0.241500, -0.180000, -0.118700, -0.057700, 0.003000, 0.063500, 0.124200, 0.184600, 0.244700, 0.305100, 0.365100, 0.425100, 0.484800, 0.544800, 0.604300, 0.663700, 0.723100, 0.782300, 0.841000, 0.900100, 0.958500, 1.016600, 1.074500, 1.131900, 1.188000, 1.241900, 1.291000, 1.333400, 1.372500, 1.411800, 1.451100, 1.488700, 1.516200, 1.542200, 1.562600, 1.580400, 1.587100, 1.548550, 1.510000, 1.460000, 1.410000, 1.340000, 1.300000, 1.260000, 1.200000, 1.179230, 1.155170, 1.116380, 1.086830, 1.063700, 1.044940, 1.029010, 1.014750, 1.001240, 0.987774, 0.973789, 0.958831, 0.915006, 0.859257, 0.782698, 0.706138, 0.603162, 0.500185, 0.378417, 0.256649, 0.128325, 0.000000, -0.089827, -0.179654, -0.264892, -0.350129, -0.422213, -0.494297, -0.547889, -0.601480, -0.636331, -0.671182, -0.695745, -0.720308, -0.764463, -0.808618, -0.639050, -0.469481, -0.234741, 0.000000] - c_d: - grid: *grid003 - values: [0.037696, 0.037696, 0.075392, 0.123869, 0.172346, 0.268898, 0.365449, 0.483875, 0.602301, 0.728306, 0.854311, 0.972686, 1.091060, 1.187505, 1.283950, 1.346815, 1.409680, 1.431370, 1.453060, 1.431370, 1.409680, 1.346815, 1.283950, 1.187505, 1.091060, 0.972686, 0.854311, 0.728313, 0.602301, 0.552754, 0.504060, 0.456458, 0.410179, 0.365449, 0.322487, 0.281501, 0.242691, 0.206247, 0.172346, 0.156402, 0.141154, 0.127962, 0.114770, 0.101579, 0.088387, 0.081791, 0.075195, 0.068599, 0.062003, 0.055407, 0.048811, 0.042215, 0.035620, 0.029024, 0.022428, 0.017031, 0.011634, 0.010187, 0.009000, 0.007600, 0.007200, 0.006900, 0.006700, 0.006500, 0.006400, 0.006400, 0.006300, 0.006300, 0.006300, 0.006300, 0.006300, 0.006300, 0.006400, 0.006400, 0.006400, 0.006500, 0.006500, 0.006600, 0.006700, 0.006800, 0.006900, 0.007000, 0.007100, 0.007300, 0.007600, 0.008100, 0.009000, 0.010500, 0.012000, 0.013400, 0.014500, 0.015600, 0.016500, 0.017800, 0.019500, 0.021500, 0.035000, 0.048500, 0.062000, 0.075500, 0.089000, 0.102500, 0.116000, 0.129500, 0.143000, 0.156402, 0.172346, 0.206247, 0.242691, 0.281501, 0.322487, 0.365449, 0.410179, 0.456458, 0.504060, 0.552754, 0.602301, 0.728313, 0.854311, 0.972686, 1.091060, 1.187505, 1.283950, 1.346815, 1.409680, 1.431370, 1.453060, 1.431370, 1.409680, 1.346815, 1.283950, 1.187505, 1.091060, 0.972686, 0.854311, 0.728306, 0.602301, 0.483875, 0.365449, 0.268898, 0.172346, 0.125383, 0.078419, 0.039210, 0.037696] - c_m: - grid: *grid003 - values: [0.200000, 0.200000, 0.400000, 0.150251, -0.099498, -0.066070, -0.032643, -0.007560, 0.017523, 0.043217, 0.068911, 0.095380, 0.121850, 0.147056, 0.172261, 0.193441, 0.214620, 0.229082, 0.243543, 0.249606, 0.255669, 0.253550, 0.251430, 0.243076, 0.234722, 0.223451, 0.212179, 0.201624, 0.193688, 0.191741, 0.190773, 0.191024, 0.192794, 0.196465, 0.202543, 0.211705, 0.224897, 0.243475, 0.269455, 0.286133, 0.280000, 0.266268, 0.226580, 0.186891, 0.147203, 0.127359, 0.107514, 0.087670, 0.067826, 0.047982, 0.028137, 0.008293, -0.011551, -0.031396, -0.051240, -0.068842, -0.086445, -0.095137, -0.102500, -0.104800, -0.106800, -0.108500, -0.110100, -0.111600, -0.113000, -0.114300, -0.115500, -0.116800, -0.117900, -0.119000, -0.120200, -0.121300, -0.122400, -0.123500, -0.124500, -0.125400, -0.126400, -0.127400, -0.128300, -0.129100, -0.130000, -0.130700, -0.131400, -0.132100, -0.132500, -0.132600, -0.132100, -0.130400, -0.128300, -0.126300, -0.124200, -0.121700, -0.121700, -0.121700, -0.121700, -0.122200, -0.123800, -0.125400, -0.127000, -0.128600, -0.130200, -0.131800, -0.133400, -0.135000, -0.136600, -0.138200, -0.147988, -0.172041, -0.191567, -0.208095, -0.222586, -0.235664, -0.247743, -0.259103, -0.269937, -0.280378, -0.290519, -0.314934, -0.338376, -0.360567, -0.382758, -0.402880, -0.423002, -0.440107, -0.457211, -0.470099, -0.482986, -0.489049, -0.495111, -0.492992, -0.490872, -0.482519, -0.474165, -0.462893, -0.451621, -0.442376, -0.433130, -0.434519, -0.435908, -0.472403, -0.508897, -0.504449, -0.500000, -0.250000, 0.2] -# Materials -materials: - - name: triax - description: Triax composite material used in the outer shell skin of the IEA-3.4-130-RWT - source: Link to report "https://www.nrel.gov/docs/fy19osti/73492.pdf" - orth: 1 - rho: 1845 - E: [21.79e+009, 14.67e+009, 14.67e+009] - G: [9.413e+009, 9.413e+009, 9.413e+009] - nu: [0.48, 0.48, 0.48] - Xt: [600.0e+006, 600.0e+006, 600.0e+006] - Xc: [600.0e+006, 600.0e+006, 600.0e+006] - S: [500.0e+006, 200.0e+006, 200.0e+006] - fvf: 0.4793 - fwf: 0.6754 - alpha: [0, 0, 0] - ply_t: 0.001 - waste: 0.15 - unit_cost: 2.86 - component_id: 2 - area_density_dry: 1.112 - fiber_density: 2600. - roll_mass: 181.4368 -# Assembly -assembly: - turbine_class: III - turbulence_class: A - drivetrain: Geared - rotor_orientation: Upwind - number_of_blades: 3 -# Control -control: - supervisory: - Vin: 3.0 - Vout: 25.0 - maxTS: 80. - rated_power: 5.e+6 - minOmega: 0.0 - maxOmega: 2.0 - pitch: - PC_zeta: 0.7 # Pitch controller desired damping ratio [-] - PC_omega: 0.5 # 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.1745 - min_pitch: 0 # Minimum pitch angle [rad], {default = 0 degrees} - torque: - control_type: tsr_tracking - tsr: 10. - VS_zeta: 0.7 # Torque controller desired damping ratio [-] - VS_omega: 0.2 # Torque controller desired natural frequency [rad/s] - max_torque_rate: 1500000. - VS_minspd: 0. # Minimum rotor speed [rad/s], {default = 0 rad/s} - VS_maxspd: 2. # 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: .01 # Pitch controller setpoint smoother gain bias percentage [%, <= 1 ], {default = 0.1%} - shutdown: - limit_type: gen_speed - limit_value: 2.0 -# Environment -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 - water_depth: 0.0 - soil_shear_modulus: 140.e+6 - soil_poisson: 0.4 -# Balance of station -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: 1. - interconnect_voltage: 130 - distance_to_site: 1. - distance_to_landfall: 1. - port_cost_per_month: 0.0 - site_auction_price: 0.0 - site_assessment_plan_cost: 0.0 - site_assessment_cost: 0.0 - construction_operations_plan_cost: 0.0 - boem_review_cost: 0.0 - design_install_plan_cost: 0.0 -# Costs -costs: - wake_loss_factor: 0.15 - fixed_charge_rate: 0.075 - bos_per_kW: 0.0 - opex_per_kW: 0.0 - turbine_number: 100 -# EOF diff --git a/weis/inputs/geometry_schema.yaml b/weis/inputs/geometry_schema.yaml index ec75cc7d5..36066a54e 100644 --- a/weis/inputs/geometry_schema.yaml +++ b/weis/inputs/geometry_schema.yaml @@ -133,6 +133,7 @@ properties: - pitch - torque optional: + - dac - setpoint_smooth - shutdown # might need to be required? - yaw From 77fecd7e8f4ad3295275482cea020a016b5b31d4 Mon Sep 17 00:00:00 2001 From: pibo Date: Tue, 29 Dec 2020 17:39:03 -0700 Subject: [PATCH 4/8] fix writing out control DVs --- weis/glue_code/gc_LoadInputs.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/weis/glue_code/gc_LoadInputs.py b/weis/glue_code/gc_LoadInputs.py index 12e7c8917..178448540 100644 --- a/weis/glue_code/gc_LoadInputs.py +++ b/weis/glue_code/gc_LoadInputs.py @@ -80,14 +80,15 @@ def set_openmdao_vectors_control(self): def update_ontology_control(self, wt_opt): # Update controller if self.modeling_options['flags']['control']: - self.wt_init['control']['tsr'] = float(wt_opt['control.rated_TSR']) - self.wt_init['control']['PC_omega'] = float(wt_opt['tune_rosco_ivc.PC_omega']) - self.wt_init['control']['PC_zeta'] = float(wt_opt['tune_rosco_ivc.PC_zeta']) - self.wt_init['control']['VS_omega'] = float(wt_opt['tune_rosco_ivc.VS_omega']) - self.wt_init['control']['VS_zeta'] = float(wt_opt['tune_rosco_ivc.VS_zeta']) - self.wt_init['control']['Flp_omega']= float(wt_opt['tune_rosco_ivc.Flp_omega']) - self.wt_init['control']['Flp_zeta'] = float(wt_opt['tune_rosco_ivc.Flp_zeta']) - self.wt_init['control']['IPC_Ki1p'] = float(wt_opt['tune_rosco_ivc.IPC_Ki1p']) + self.wt_init['control']['pitch']['PC_omega'] = float(wt_opt['tune_rosco_ivc.PC_omega']) + self.wt_init['control']['pitch']['PC_zeta'] = float(wt_opt['tune_rosco_ivc.PC_zeta']) + self.wt_init['control']['torque']['VS_omega'] = float(wt_opt['tune_rosco_ivc.VS_omega']) + self.wt_init['control']['torque']['VS_zeta'] = float(wt_opt['tune_rosco_ivc.VS_zeta']) + if self.modeling_options['Level3']['ROSCO']['Flp_Mode'] > 0: + self.wt_init['control']['dac']['Flp_omega']= float(wt_opt['tune_rosco_ivc.Flp_omega']) + self.wt_init['control']['dac']['Flp_zeta'] = float(wt_opt['tune_rosco_ivc.Flp_zeta']) + if 'IPC' in self.wt_init['control'].keys(): + self.wt_init['control']['IPC']['IPC_gain_1P'] = float(wt_opt['tune_rosco_ivc.IPC_Ki1p']) def write_options(self, fname_output): From 9a761fe6fa6e17dd907c92e451f9aacf636e93e5 Mon Sep 17 00:00:00 2001 From: pibo Date: Tue, 29 Dec 2020 18:13:47 -0700 Subject: [PATCH 5/8] Squashed 'WISDEM/' changes from 3f58d337f..0a49fdd01 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 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 8d631b59f bringing in other updates f725fc215 forgot water depth 3f4fb38ff restarting soil work git-subtree-dir: WISDEM git-subtree-split: 0a49fdd01c34d44f86a9d61a3d595e4219451551 --- docs/inputs/analysis_schema.rst | 8 +- .../02_reference_turbines/IEA-15-240-RWT.yaml | 2 +- .../IEA-3p4-130-RWT.yaml | 2 +- .../analysis_options.yaml | 4 +- examples/02_reference_turbines/nrel5mw.yaml | 2 +- examples/03_blade/blade.yaml | 2 +- .../05_tower_monopile/modeling_options.yaml | 14 +- examples/05_tower_monopile/monopile_direct.py | 4 + examples/05_tower_monopile/tower_direct.py | 8 +- .../09_weis_floating/nrel5mw-semi_oc4.yaml | 2 +- .../09_weis_floating/nrel5mw-spar_oc3.yaml | 2 +- wisdem/ccblade/ccblade_component.py | 3 + wisdem/commonse/environment.py | 42 +-- wisdem/commonse/fileIO.py | 1 + wisdem/commonse/vertical_cylinder.py | 31 +- wisdem/drivetrainse/drive_components.py | 14 +- wisdem/drivetrainse/generator.py | 98 +++++- wisdem/drivetrainse/generator_models.py | 18 +- wisdem/glue_code/gc_LoadInputs.py | 12 +- wisdem/glue_code/gc_PoseOptimization.py | 5 +- wisdem/glue_code/gc_WT_DataStruc.py | 1 + wisdem/glue_code/gc_WT_InitModel.py | 3 +- wisdem/glue_code/glue_code.py | 19 +- wisdem/glue_code/runWISDEM.py | 2 +- wisdem/inputs/analysis_schema.yaml | 7 +- wisdem/inputs/geometry_schema.yaml | 9 +- wisdem/inputs/modeling_schema.yaml | 12 +- .../landbosse_omdao/OpenMDAODataframeCache.py | 6 +- 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/rotorse/rotor_elasticity.py | 8 +- wisdem/rotorse/rotor_power.py | 23 +- .../test/test_drivetrainse/test_components.py | 24 +- .../test_drivetrainse/test_drivetrainse.py | 55 +++- .../test_generator_driver.py | 154 +++++++++ .../test_generator_models.py | 24 +- 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/test_rotor_power.py | 2 - wisdem/test/test_towerse/test_tower.py | 308 +++++++++++++++--- wisdem/towerse/tower.py | 134 +++++--- 137 files changed, 1743 insertions(+), 1318 deletions(-) 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 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 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/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..15f4d929f 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] 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/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/05_tower_monopile/modeling_options.yaml b/examples/05_tower_monopile/modeling_options.yaml index ed204991c..a29fa85a4 100644 --- a/examples/05_tower_monopile/modeling_options.yaml +++ b/examples/05_tower_monopile/modeling_options.yaml @@ -7,14 +7,16 @@ 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 + 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 + 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 diff --git a/examples/05_tower_monopile/monopile_direct.py b/examples/05_tower_monopile/monopile_direct.py index 4bc5bb2f9..1cd8cb696 100644 --- a/examples/05_tower_monopile/monopile_direct.py +++ b/examples/05_tower_monopile/monopile_direct.py @@ -39,6 +39,10 @@ modeling_options["TowerSE"]["buckling_length"] = 30.0 modeling_options["flags"]["monopile"] = True +# Monopile foundation +modeling_options["TowerSE"]["soil_springs"] = True +modeling_options["TowerSE"]["gravity_foundation"] = False + # safety factors modeling_options["TowerSE"]["gamma_f"] = 1.35 modeling_options["TowerSE"]["gamma_m"] = 1.3 diff --git a/examples/05_tower_monopile/tower_direct.py b/examples/05_tower_monopile/tower_direct.py index 91613a83b..8d4626196 100644 --- a/examples/05_tower_monopile/tower_direct.py +++ b/examples/05_tower_monopile/tower_direct.py @@ -30,6 +30,10 @@ modeling_options["TowerSE"]["buckling_length"] = 30.0 modeling_options["flags"]["monopile"] = False +# Monopile foundation only +modeling_options["TowerSE"]["soil_springs"] = False +modeling_options["TowerSE"]["gravity_foundation"] = False + # safety factors modeling_options["TowerSE"]["gamma_f"] = 1.35 modeling_options["TowerSE"]["gamma_m"] = 1.3 @@ -98,10 +102,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)) diff --git a/examples/09_weis_floating/nrel5mw-semi_oc4.yaml b/examples/09_weis_floating/nrel5mw-semi_oc4.yaml index b7be1443d..07d0d3a57 100644 --- a/examples/09_weis_floating/nrel5mw-semi_oc4.yaml +++ b/examples/09_weis_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/nrel5mw-spar_oc3.yaml b/examples/09_weis_floating/nrel5mw-spar_oc3.yaml index dda4e1387..3c7669ebb 100644 --- a/examples/09_weis_floating/nrel5mw-spar_oc3.yaml +++ b/examples/09_weis_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/wisdem/ccblade/ccblade_component.py b/wisdem/ccblade/ccblade_component.py index bb2689fee..47a7b3afe 100644 --- a/wisdem/ccblade/ccblade_component.py +++ b/wisdem/ccblade/ccblade_component.py @@ -3,6 +3,7 @@ import numpy as np import wisdem.ccblade._bem as _bem from wisdem.commonse.csystem import DirectionVector +from scipy.interpolate import PchipInterpolator cosd = lambda x: np.cos(np.deg2rad(x)) @@ -532,6 +533,8 @@ 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) 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/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/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/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/glue_code/gc_LoadInputs.py b/wisdem/glue_code/gc_LoadInputs.py index e2980a5f5..5a45ef3aa 100644 --- a/wisdem/glue_code/gc_LoadInputs.py +++ b/wisdem/glue_code/gc_LoadInputs.py @@ -236,6 +236,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 @@ -953,7 +963,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..52046bbb6 100644 --- a/wisdem/glue_code/gc_PoseOptimization.py +++ b/wisdem/glue_code/gc_PoseOptimization.py @@ -182,13 +182,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 diff --git a/wisdem/glue_code/gc_WT_DataStruc.py b/wisdem/glue_code/gc_WT_DataStruc.py index b00709634..05ff582c4 100644 --- a/wisdem/glue_code/gc_WT_DataStruc.py +++ b/wisdem/glue_code/gc_WT_DataStruc.py @@ -300,6 +300,7 @@ def setup(self): else: # If using simple (regression) generator scaling, this is an optional input to override default values n_pc = modeling_options["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))) diff --git a/wisdem/glue_code/gc_WT_InitModel.py b/wisdem/glue_code/gc_WT_InitModel.py index a6c5be4f7..eb500fc0e 100644 --- a/wisdem/glue_code/gc_WT_InitModel.py +++ b/wisdem/glue_code/gc_WT_InitModel.py @@ -599,7 +599,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"] @@ -643,6 +643,7 @@ 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_[ diff --git a/wisdem/glue_code/glue_code.py b/wisdem/glue_code/glue_code.py index d13fc7da0..58eeaf127 100644 --- a/wisdem/glue_code/glue_code.py +++ b/wisdem/glue_code/glue_code.py @@ -100,12 +100,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") @@ -153,7 +152,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 +175,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") @@ -445,6 +441,7 @@ def setup(self): 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") @@ -482,8 +479,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["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") @@ -604,10 +602,13 @@ def setup(self): 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.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("mooring.n_lines", "orbit.num_mooring_lines") 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..513415dd1 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 @@ -501,6 +505,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: @@ -737,7 +742,7 @@ properties: 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 diff --git a/wisdem/inputs/geometry_schema.yaml b/wisdem/inputs/geometry_schema.yaml index 2be97c1c8..31cb0546e 100644 --- a/wisdem/inputs/geometry_schema.yaml +++ b/wisdem/inputs/geometry_schema.yaml @@ -382,6 +382,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 +538,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..c0dc898b2 100644 --- a/wisdem/inputs/modeling_schema.yaml +++ b/wisdem/inputs/modeling_schema.yaml @@ -76,7 +76,7 @@ properties: description: Number of wind speeds to determine the Cp-Ct-Cq-surfaces regulation_reg_III: type: boolean - default: False + default: True description: Flag to derive the regulation trajectory in region III in terms of pitch and TSR spar_cap_ss: type: string @@ -216,7 +216,7 @@ properties: description: Buckling length factor in Eurocode safety check minimum: 1.0 maximum: 100.0 - default: 1.0 + default: 10.0 unit: m frame3dd: type: object @@ -243,6 +243,14 @@ properties: 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: {} diff --git a/wisdem/landbosse/landbosse_omdao/OpenMDAODataframeCache.py b/wisdem/landbosse/landbosse_omdao/OpenMDAODataframeCache.py index e8450822b..e2c6a347c 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: 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/rotorse/rotor_elasticity.py b/wisdem/rotorse/rotor_elasticity.py index 3b0abf94d..80c140833 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 diff --git a/wisdem/rotorse/rotor_power.py b/wisdem/rotorse/rotor_power.py index 1f76bc169..5498b9afe 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): @@ -62,6 +64,9 @@ def setup(self): 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"]) + # 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") @@ -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) 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..10dd773b6 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)) @@ -106,7 +108,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,6 +149,19 @@ 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 = {} @@ -199,6 +213,19 @@ 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 = {} @@ -310,6 +337,19 @@ 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 = {} @@ -372,6 +412,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_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/test_rotor_power.py b/wisdem/test/test_rotorse/test_rotor_power.py index dd24255c3..bd6cb7fec 100644 --- a/wisdem/test/test_rotorse/test_rotor_power.py +++ b/wisdem/test/test_rotorse/test_rotor_power.py @@ -1,6 +1,4 @@ import os -import copy -import time import unittest import numpy as np diff --git a/wisdem/test/test_towerse/test_tower.py b/wisdem/test/test_towerse/test_tower.py index a2332163e..2b1728902 100644 --- a/wisdem/test/test_towerse/test_tower.py +++ b/wisdem/test/test_towerse/test_tower.py @@ -33,6 +33,9 @@ def setUp(self): self.modeling_options["TowerSE"]["wind"] = "PowerWind" self.modeling_options["TowerSE"]["nLC"] = 1 + self.modeling_options["TowerSE"]["soil_springs"] = False + self.modeling_options["TowerSE"]["gravity_foundation"] = False + self.modeling_options["TowerSE"]["gamma_f"] = 1.0 self.modeling_options["TowerSE"]["gamma_m"] = 1.0 self.modeling_options["TowerSE"]["gamma_n"] = 1.0 @@ -425,7 +428,6 @@ def testTowerMass(self): npt.assert_equal(self.outputs["tower_section_center_of_mass"], self.inputs["cylinder_section_center_of_mass"]) self.assertEqual(self.outputs["monopile_mass"], 1e3 * 2.5 + 2 * 1e2) self.assertEqual(self.outputs["monopile_cost"], self.inputs["cylinder_cost"] * 2.5 / 4.0 + 1e3) - self.assertEqual(self.outputs["monopile_length"], 70.0) self.assertEqual(self.outputs["tower_mass"], 1e3 * (4 - 2.5)) self.assertEqual(self.outputs["tower_cost"], self.inputs["cylinder_cost"] * 1.5 / 4.0) npt.assert_equal(self.outputs["transition_piece_I"], 1e2 * 25 * np.r_[0.5, 0.5, 1.0, np.zeros(3)]) @@ -446,6 +448,7 @@ def testPreFrame(self): self.inputs["transition_piece_I"] = np.zeros(6) self.inputs["gravity_foundation_I"] = np.zeros(6) self.inputs["gravity_foundation_mass"] = 0.0 + self.inputs["suctionpile_depth"] = 0.0 self.inputs["rna_F"] = 1e5 * np.array( [ 2.0, @@ -460,7 +463,6 @@ def testPreFrame(self): 4.0, ] ) - self.inputs["k_monopile"] = np.zeros(6) self.inputs["E"] = 1e9 * np.ones(2) self.inputs["G"] = 1e8 * np.ones(2) self.inputs["sigma_y"] = 1e8 * np.ones(2) @@ -496,7 +498,121 @@ def testPreFrame(self): npt.assert_equal(self.outputs["Myy"], np.array([3e6])) npt.assert_equal(self.outputs["Mzz"], np.array([4e6])) - # Test Monopile + # Test Monopile no springs, no GBF + self.inputs["z_full"] = 10.0 * np.arange(-6, 7) + self.inputs["d_full"] = 6.0 * np.ones(self.inputs["z_full"].shape) + self.inputs["transition_piece_mass"] = 1e3 + self.inputs["transition_piece_cost"] = 1e4 + self.inputs["transition_piece_I"] = 1e3 * 9 * np.r_[0.5, 0.5, 1.0, np.zeros(3)] + self.inputs["transition_piece_height"] = 10.0 + self.inputs["gravity_foundation_mass"] = 0.0 # 1e4 + self.inputs["suctionpile_depth"] = 30.0 + self.inputs["rna_F"] = 1e5 * np.array( + [ + 2.0, + 3.0, + 4.0, + ] + ) + self.inputs["rna_M"] = 1e6 * np.array( + [ + 2.0, + 3.0, + 4.0, + ] + ) + self.inputs["k_soil"] = (20.0 + np.arange(6))[np.newaxis, :] * np.ones((2, 6)) + self.inputs["z_soil"] = np.r_[-30.0, 0.0] + + myobj = tow.TowerPreFrame(n_height=5, monopile=True, soil_springs=False) + myobj.compute(self.inputs, self.outputs) + + npt.assert_equal(self.outputs["kidx"], np.arange(4)) + npt.assert_equal(self.outputs["kx"], RIGID) + npt.assert_equal(self.outputs["ky"], RIGID) + npt.assert_equal(self.outputs["kz"], RIGID) + npt.assert_equal(self.outputs["ktx"], RIGID) + npt.assert_equal(self.outputs["kty"], RIGID) + npt.assert_equal(self.outputs["ktz"], RIGID) + + npt.assert_equal(self.outputs["midx"], np.array([12, 7, 0])) + npt.assert_equal(self.outputs["m"], np.array([1e5, 1e3, 0.0])) + npt.assert_equal(self.outputs["mrhox"], np.array([-3.0, 0.0, 0.0])) + npt.assert_equal(self.outputs["mrhoy"], np.array([0.0, 0.0, 0.0])) + npt.assert_equal(self.outputs["mrhoz"], np.array([1.0, 0.0, 0.0])) + npt.assert_equal(self.outputs["mIxx"], np.array([1e5, 1e3 * 9 * 0.5, 0])) + npt.assert_equal(self.outputs["mIyy"], np.array([1e5, 1e3 * 9 * 0.5, 0])) + npt.assert_equal(self.outputs["mIzz"], np.array([2e5, 1e3 * 9, 0])) + npt.assert_equal(self.outputs["mIxy"], np.zeros(3)) + npt.assert_equal(self.outputs["mIxz"], np.zeros(3)) + npt.assert_equal(self.outputs["mIyz"], np.zeros(3)) + + npt.assert_equal(self.outputs["plidx"], np.array([12])) + npt.assert_equal(self.outputs["Fx"], np.array([2e5])) + npt.assert_equal(self.outputs["Fy"], np.array([3e5])) + npt.assert_equal(self.outputs["Fz"], np.array([4e5])) + npt.assert_equal(self.outputs["Mxx"], np.array([2e6])) + npt.assert_equal(self.outputs["Myy"], np.array([3e6])) + npt.assert_equal(self.outputs["Mzz"], np.array([4e6])) + + # Test Monopile springs, no GBF + self.inputs["z_full"] = 10.0 * np.arange(-6, 7) + self.inputs["d_full"] = 6.0 * np.ones(self.inputs["z_full"].shape) + self.inputs["transition_piece_mass"] = 1e3 + self.inputs["transition_piece_cost"] = 1e4 + self.inputs["transition_piece_I"] = 1e3 * 9 * np.r_[0.5, 0.5, 1.0, np.zeros(3)] + self.inputs["transition_piece_height"] = 10.0 + self.inputs["gravity_foundation_mass"] = 0.0 # 1e4 + self.inputs["suctionpile_depth"] = 30.0 + self.inputs["rna_F"] = 1e5 * np.array( + [ + 2.0, + 3.0, + 4.0, + ] + ) + self.inputs["rna_M"] = 1e6 * np.array( + [ + 2.0, + 3.0, + 4.0, + ] + ) + self.inputs["k_soil"] = (20.0 + np.arange(6))[np.newaxis, :] * np.ones((2, 6)) + self.inputs["z_soil"] = np.r_[-30.0, 0.0] + + myobj = tow.TowerPreFrame(n_height=5, monopile=True, soil_springs=True) + myobj.compute(self.inputs, self.outputs) + + npt.assert_equal(self.outputs["kidx"], np.arange(4)) + npt.assert_equal(self.outputs["kx"], 20.0) + npt.assert_equal(self.outputs["ky"], 22.0) + npt.assert_equal(self.outputs["kz"], np.r_[24.0, np.zeros(3)]) + npt.assert_equal(self.outputs["ktx"], 21.0) + npt.assert_equal(self.outputs["kty"], 23.0) + npt.assert_equal(self.outputs["ktz"], 25.0) + + npt.assert_equal(self.outputs["midx"], np.array([12, 7, 0])) + npt.assert_equal(self.outputs["m"], np.array([1e5, 1e3, 0.0])) + npt.assert_equal(self.outputs["mrhox"], np.array([-3.0, 0.0, 0.0])) + npt.assert_equal(self.outputs["mrhoy"], np.array([0.0, 0.0, 0.0])) + npt.assert_equal(self.outputs["mrhoz"], np.array([1.0, 0.0, 0.0])) + npt.assert_equal(self.outputs["mIxx"], np.array([1e5, 1e3 * 9 * 0.5, 0])) + npt.assert_equal(self.outputs["mIyy"], np.array([1e5, 1e3 * 9 * 0.5, 0])) + npt.assert_equal(self.outputs["mIzz"], np.array([2e5, 1e3 * 9, 0])) + npt.assert_equal(self.outputs["mIxy"], np.zeros(3)) + npt.assert_equal(self.outputs["mIxz"], np.zeros(3)) + npt.assert_equal(self.outputs["mIyz"], np.zeros(3)) + + npt.assert_equal(self.outputs["plidx"], np.array([12])) + npt.assert_equal(self.outputs["Fx"], np.array([2e5])) + npt.assert_equal(self.outputs["Fy"], np.array([3e5])) + npt.assert_equal(self.outputs["Fz"], np.array([4e5])) + npt.assert_equal(self.outputs["Mxx"], np.array([2e6])) + npt.assert_equal(self.outputs["Myy"], np.array([3e6])) + npt.assert_equal(self.outputs["Mzz"], np.array([4e6])) + + # Test Monopile with GBF- TODO: THESE REACTIONS NEED THOUGHT self.inputs["z_full"] = 10.0 * np.arange(-6, 7) self.inputs["d_full"] = 6.0 * np.ones(self.inputs["z_full"].shape) self.inputs["transition_piece_mass"] = 1e3 @@ -505,6 +621,7 @@ def testPreFrame(self): self.inputs["transition_piece_I"] = 1e3 * 9 * np.r_[0.5, 0.5, 1.0, np.zeros(3)] self.inputs["gravity_foundation_I"] = 0.5 * 1e4 * 9 * np.r_[0.5, 0.5, 1.0, np.zeros(3)] self.inputs["gravity_foundation_mass"] = 1e4 + self.inputs["suctionpile_depth"] = 0.0 self.inputs["rna_F"] = 1e5 * np.array( [ 2.0, @@ -519,18 +636,19 @@ def testPreFrame(self): 4.0, ] ) - self.inputs["k_monopile"] = 20.0 + np.arange(6) + self.inputs["k_soil"] = (20.0 + np.arange(6))[np.newaxis, :] * np.ones((2, 6)) + self.inputs["z_soil"] = np.r_[-30.0, 0.0] - myobj = tow.TowerPreFrame(n_height=5, monopile=True) + myobj = tow.TowerPreFrame(n_height=5, monopile=True, gravity_foundation=True) myobj.compute(self.inputs, self.outputs) npt.assert_equal(self.outputs["kidx"], np.array([0])) - npt.assert_equal(self.outputs["kx"], 20.0 * np.ones(1)) - npt.assert_equal(self.outputs["ky"], 22.0 * np.ones(1)) - npt.assert_equal(self.outputs["kz"], 24.0 * np.ones(1)) - npt.assert_equal(self.outputs["ktx"], 21.0 * np.ones(1)) - npt.assert_equal(self.outputs["kty"], 23.0 * np.ones(1)) - npt.assert_equal(self.outputs["ktz"], 25.0 * np.ones(1)) + npt.assert_equal(self.outputs["kx"], np.array([RIGID])) + npt.assert_equal(self.outputs["ky"], np.array([RIGID])) + npt.assert_equal(self.outputs["kz"], np.array([RIGID])) + npt.assert_equal(self.outputs["ktx"], np.array([RIGID])) + npt.assert_equal(self.outputs["kty"], np.array([RIGID])) + npt.assert_equal(self.outputs["ktz"], np.array([RIGID])) npt.assert_equal(self.outputs["midx"], np.array([12, 7, 0])) npt.assert_equal(self.outputs["m"], np.array([1e5, 1e3, 1e4])) @@ -578,9 +696,6 @@ def testProblemLand(self): prob["sigma_y_mat"] = 1e8 prob["yaw"] = 0.0 - prob["suctionpile_depth"] = 0.0 - prob["G_soil"] = 1e7 - prob["nu_soil"] = 0.5 prob["rna_mass"] = 2e5 prob["rna_I"] = np.r_[1e5, 1e5, 2e5, np.zeros(3)] prob["rna_cg"] = np.array([-3.0, 0.0, 1.0]) @@ -621,7 +736,6 @@ def testProblemLand(self): npt.assert_equal(prob["tower_section_center_of_mass"], prob["cm.section_center_of_mass"]) self.assertEqual(prob["monopile_mass"], 0.0) self.assertEqual(prob["monopile_cost"], 0.0) - self.assertEqual(prob["monopile_length"], 0.0) npt.assert_almost_equal(prob["tower_mass"], mass_dens * 80.0) npt.assert_equal(prob["pre.kidx"], np.array([0], dtype=np.int_)) @@ -655,6 +769,8 @@ def testProblemLand(self): def testProblemFixedPile(self): self.modeling_options["TowerSE"]["n_height_monopile"] = 3 self.modeling_options["TowerSE"]["n_layers_monopile"] = 1 + self.modeling_options["TowerSE"]["soil_springs"] = True + self.modeling_options["TowerSE"]["gravity_foundation"] = False self.modeling_options["flags"]["monopile"] = True prob = om.Problem() @@ -665,7 +781,7 @@ def testProblemFixedPile(self): prob["water_depth"] = 30.0 prob["transition_piece_mass"] = 1e2 prob["transition_piece_cost"] = 1e3 - prob["gravity_foundation_mass"] = 1e4 + prob["gravity_foundation_mass"] = 0.0 # 1e4 prob["tower_s"] = np.linspace(0, 1, 3) prob["tower_foundation_height"] = 0.0 @@ -687,7 +803,6 @@ def testProblemFixedPile(self): prob["rho_mat"] = 1e4 prob["sigma_y_mat"] = 1e8 - prob["suctionpile_depth"] = 15.0 prob["outfitting_factor"] = 1.0 prob["yaw"] = 0.0 prob["G_soil"] = 1e7 @@ -736,27 +851,147 @@ def testProblemFixedPile(self): npt.assert_equal(prob["tower_I_base"][2:], prob["cm.I_base"][2:]) npt.assert_almost_equal( prob["tower_center_of_mass"], - (7.5 * mass_dens * 105.0 + 0.0 * 1e2 + 1e4 * -45.0) / (mass_dens * 105 + 1e2 + 1e4), + (7.5 * mass_dens * 105.0 + 0.0 * 1e2) / (mass_dens * 105 + 1e2), ) npt.assert_equal(prob["tower_section_center_of_mass"], prob["cm.section_center_of_mass"]) npt.assert_almost_equal(prob["monopile_cost"], (45.0 / 105.0) * prob["cm.cost"] + 1e3) - self.assertEqual(prob["monopile_length"], 45.0) - npt.assert_almost_equal(prob["monopile_mass"], mass_dens * 45.0 + 1e2 + 1e4) + npt.assert_almost_equal(prob["monopile_mass"], mass_dens * 45.0 + 1e2) npt.assert_almost_equal(prob["tower_mass"], mass_dens * 60.0) - npt.assert_equal(prob["pre.kidx"], np.array([0], dtype=np.int_)) + npt.assert_equal(prob["pre.kidx"], np.arange(4, dtype=np.int_)) npt.assert_array_less(prob["pre.kx"], RIGID) npt.assert_array_less(prob["pre.ky"], RIGID) - npt.assert_array_less(prob["pre.kz"], RIGID) + npt.assert_array_less(prob["pre.kz"][0], RIGID) npt.assert_array_less(prob["pre.ktx"], RIGID) npt.assert_array_less(prob["pre.kty"], RIGID) npt.assert_array_less(prob["pre.ktz"], RIGID) npt.assert_array_less(0.0, prob["pre.kx"]) npt.assert_array_less(0.0, prob["pre.ky"]) - npt.assert_array_less(0.0, prob["pre.kz"]) + npt.assert_array_less(0.0, prob["pre.kz"][0]) npt.assert_array_less(0.0, prob["pre.ktx"]) npt.assert_array_less(0.0, prob["pre.kty"]) npt.assert_array_less(0.0, prob["pre.ktz"]) + npt.assert_equal(0.0, prob["pre.kz"][1:]) + + npt.assert_equal(prob["pre.midx"], np.array([12, 6, 0])) + npt.assert_equal(prob["pre.m"], np.array([2e5, 1e2, 0])) + npt.assert_equal(prob["pre.mrhox"], np.array([-3.0, 0.0, 0.0])) + npt.assert_equal(prob["pre.mrhoy"], np.array([0.0, 0.0, 0.0])) + npt.assert_equal(prob["pre.mrhoz"], np.array([1.0, 0.0, 0.0])) + npt.assert_equal(prob["pre.mIxx"], np.array([1e5, 1e2 * 25 * 0.5, 0])) + npt.assert_equal(prob["pre.mIyy"], np.array([1e5, 1e2 * 25 * 0.5, 0])) + npt.assert_equal(prob["pre.mIzz"], np.array([2e5, 1e2 * 25, 0])) + npt.assert_equal(prob["pre.mIxy"], np.zeros(3)) + npt.assert_equal(prob["pre.mIxz"], np.zeros(3)) + npt.assert_equal(prob["pre.mIyz"], np.zeros(3)) + + npt.assert_equal(prob["pre.plidx"], np.array([12])) + npt.assert_equal(prob["pre.Fx"], np.array([2e3])) + npt.assert_equal(prob["pre.Fy"], np.array([3e3])) + npt.assert_equal(prob["pre.Fz"], np.array([4e3])) + npt.assert_equal(prob["pre.Mxx"], np.array([2e4])) + npt.assert_equal(prob["pre.Myy"], np.array([3e4])) + npt.assert_equal(prob["pre.Mzz"], np.array([4e4])) + npt.assert_almost_equal(prob["tower.base_F"], [4.61183362e04, 1.59353875e03, -2.94077236e07], 0) + npt.assert_almost_equal(prob["tower.base_M"], [-248566.38259147, -3286049.81237828, 40000.0], 0) + + def testProblemFixedPile_GBF(self): + self.modeling_options["TowerSE"]["n_height_monopile"] = 3 + self.modeling_options["TowerSE"]["n_layers_monopile"] = 1 + self.modeling_options["TowerSE"]["soil_springs"] = False + self.modeling_options["TowerSE"]["gravity_foundation"] = True + self.modeling_options["flags"]["monopile"] = True + + prob = om.Problem() + prob.model = tow.TowerSE(modeling_options=self.modeling_options) + prob.setup() + + prob["hub_height"] = 80.0 + prob["water_depth"] = 30.0 + prob["transition_piece_mass"] = 1e2 + prob["transition_piece_cost"] = 1e3 + prob["gravity_foundation_mass"] = 1e4 + + prob["tower_s"] = np.linspace(0, 1, 3) + prob["tower_foundation_height"] = 0.0 + prob["tower_height"] = 60.0 + prob["tower_outer_diameter_in"] = 10.0 * np.ones(3) + prob["tower_layer_thickness"] = 0.1 * np.ones(3).reshape((1, 3)) + prob["tower_outfitting_factor"] = 1.0 + hval = np.array([15.0, 30.0]) + prob["monopile_s"] = np.cumsum(np.r_[0, hval]) / hval.sum() + prob["monopile_foundation_height"] = -45.0 + prob["monopile_height"] = hval.sum() + prob["monopile_outer_diameter_in"] = 10.0 * np.ones(3) + prob["monopile_layer_thickness"] = 0.1 * np.ones(3).reshape((1, 3)) + prob["monopile_outfitting_factor"] = 1.0 + prob["tower_layer_materials"] = prob["monopile_layer_materials"] = ["steel"] + prob["material_names"] = ["steel"] + prob["E_mat"] = 1e9 * np.ones((1, 3)) + prob["G_mat"] = 1e8 * np.ones((1, 3)) + prob["rho_mat"] = 1e4 + prob["sigma_y_mat"] = 1e8 + + prob["outfitting_factor"] = 1.0 + prob["yaw"] = 0.0 + prob["rna_mass"] = 2e5 + prob["rna_I"] = np.r_[1e5, 1e5, 2e5, np.zeros(3)] + prob["rna_cg"] = np.array([-3.0, 0.0, 1.0]) + prob["wind_reference_height"] = 80.0 + prob["wind_z0"] = 0.0 + prob["cd_usr"] = -1.0 + prob["rho_air"] = 1.225 + prob["mu_air"] = 1.7934e-5 + prob["shearExp"] = 0.2 + prob["rho_water"] = 1025.0 + prob["mu_water"] = 1.3351e-3 + prob["beta_wind"] = prob["beta_wave"] = 0.0 + prob["Hsig_wave"] = 0.0 + prob["Tsig_wave"] = 1e3 + prob["wind.Uref"] = 15.0 + prob["pre.rna_F"] = 1e3 * np.array( + [ + 2.0, + 3.0, + 4.0, + ] + ) + prob["pre.rna_M"] = 1e4 * np.array( + [ + 2.0, + 3.0, + 4.0, + ] + ) + prob.run_model() + + # All other tests from above + mass_dens = 1e4 * (5.0 ** 2 - 4.9 ** 2) * np.pi + npt.assert_equal(prob["z_start"], -45.0) + npt.assert_equal(prob["transition_piece_height"], 0.0) + npt.assert_equal(prob["suctionpile_depth"], 15.0) + npt.assert_equal(prob["z_param"], np.array([-45.0, -30.0, 0.0, 30.0, 60.0])) + + self.assertEqual(prob["height_constraint"], 20.0) + npt.assert_almost_equal(prob["tower_cost"], (60.0 / 105.0) * prob["cm.cost"]) + npt.assert_equal(prob["tower_I_base"][:2], prob["cm.I_base"][:2] + 1e2 * 45 ** 2) + npt.assert_equal(prob["tower_I_base"][2:], prob["cm.I_base"][2:]) + npt.assert_almost_equal( + prob["tower_center_of_mass"], + (7.5 * mass_dens * 105.0 + 0.0 * 1e2 + (-45) * 1e4) / (mass_dens * 105 + 1e2 + 1e4), + ) + npt.assert_equal(prob["tower_section_center_of_mass"], prob["cm.section_center_of_mass"]) + npt.assert_almost_equal(prob["monopile_cost"], (45.0 / 105.0) * prob["cm.cost"] + 1e3) + npt.assert_almost_equal(prob["monopile_mass"], mass_dens * 45.0 + 1e2 + 1e4) + npt.assert_almost_equal(prob["tower_mass"], mass_dens * 60.0) + + npt.assert_equal(prob["pre.kidx"], 0) + npt.assert_equal(prob["pre.kx"], RIGID) + npt.assert_equal(prob["pre.ky"], RIGID) + npt.assert_equal(prob["pre.kz"], RIGID) + npt.assert_equal(prob["pre.ktx"], RIGID) + npt.assert_equal(prob["pre.kty"], RIGID) + npt.assert_equal(prob["pre.ktz"], RIGID) npt.assert_equal(prob["pre.midx"], np.array([12, 6, 0])) npt.assert_equal(prob["pre.m"], np.array([2e5, 1e2, 1e4])) @@ -778,12 +1013,14 @@ def testProblemFixedPile(self): npt.assert_equal(prob["pre.Myy"], np.array([3e4])) npt.assert_equal(prob["pre.Mzz"], np.array([4e4])) - npt.assert_almost_equal(prob["tower.base_F"], [3.86908254e04, 1.70778509e03, -3.39826364e07], 0) - npt.assert_almost_equal(prob["tower.base_M"], [-294477.83027742, -2732413.3684214, 40000.0], 0) + npt.assert_almost_equal(prob["tower.base_F"], [3.74393291e04, 1.84264671e03, -3.39826364e07], 0) + npt.assert_almost_equal(prob["tower.base_M"], [-294477.83027742, -2732413.3684215, 40000.0], 0) def testAddedMassForces(self): self.modeling_options["TowerSE"]["n_height_monopile"] = 3 self.modeling_options["TowerSE"]["n_layers_monopile"] = 1 + self.modeling_options["TowerSE"]["soil_springs"] = False + self.modeling_options["TowerSE"]["gravity_foundation"] = False self.modeling_options["flags"]["monopile"] = True prob = om.Problem() @@ -816,10 +1053,9 @@ def testAddedMassForces(self): prob["rho_mat"] = 1e4 prob["sigma_y_mat"] = 1e8 - prob["suctionpile_depth"] = 15.0 prob["yaw"] = 0.0 - prob["G_soil"] = 1e7 - prob["nu_soil"] = 0.5 + # prob["G_soil"] = 1e7 + # prob["nu_soil"] = 0.5 prob["rna_mass"] = 0.0 prob["rna_I"] = np.r_[1e5, 1e5, 2e5, np.zeros(3)] prob["rna_cg"] = np.array([-3.0, 0.0, 1.0]) @@ -855,12 +1091,12 @@ def testAddedMassForces(self): prob["rna_mass"] = 1e4 prob.run_model() - myFz -= 1e4 * g + myFz[3:] -= 1e4 * g npt.assert_almost_equal(prob["post.Fz"], myFz) prob["transition_piece_mass"] = 1e2 prob.run_model() - myFz[:6] -= 1e2 * g + myFz[3:6] -= 1e2 * g npt.assert_almost_equal(prob["post.Fz"], myFz) prob["gravity_foundation_mass"] = 1e3 @@ -946,7 +1182,6 @@ def test15MWmode_shapes(self): prob["rho_mat"] = 7850.0 prob["sigma_y_mat"] = 345e6 - prob["suctionpile_depth"] = 0.0 # 45.0 prob["yaw"] = 0.0 prob["transition_piece_mass"] = 0.0 # 100e3 prob["transition_piece_cost"] = 0.0 # 100e3 @@ -1019,9 +1254,6 @@ def testExampleRegression(self): # --------------- # --- wave --- - hmax = 0.0 - T = 1.0 - cm = 1.0 water_depth = 0.0 soilG = 140e6 soilnu = 0.4 @@ -1091,8 +1323,8 @@ def testExampleRegression(self): prob["tower_layer_materials"] = ["steel"] prob["material_names"] = ["steel"] prob["yaw"] = yaw - prob["G_soil"] = soilG - prob["nu_soil"] = soilnu + # prob["G_soil"] = soilG + # prob["nu_soil"] = soilnu # --- material props --- prob["E_mat"] = E * np.ones((1, 3)) prob["G_mat"] = G * np.ones((1, 3)) @@ -1148,7 +1380,7 @@ def testExampleRegression(self): npt.assert_almost_equal(prob["constr_taper"], [0.8225, 0.78419453]) npt.assert_almost_equal(prob["wind1.Uref"], [11.73732]) npt.assert_almost_equal(prob["tower1.f1"], [0.33214436], 5) - npt.assert_almost_equal(prob["post1.top_deflection"], [0.69728181]) + npt.assert_almost_equal(prob["post1.top_deflection"], [0.6988131]) npt.assert_almost_equal( prob["post1.stress"], [0.45829036, 0.41279744, 0.35017613, 0.31497356, 0.17978006, 0.12034969] ) @@ -1160,7 +1392,7 @@ def testExampleRegression(self): ) npt.assert_almost_equal(prob["wind2.Uref"], [70.0]) npt.assert_almost_equal(prob["tower2.f1"], [0.33218936], 5) - npt.assert_almost_equal(prob["post2.top_deflection"], [0.64374406]) + npt.assert_almost_equal(prob["post2.top_deflection"], [0.6440434]) npt.assert_almost_equal( prob["post2.stress"], [0.44626187, 0.3821702, 0.30578917, 0.25648781, 0.13131541, 0.10609859] ) diff --git a/wisdem/towerse/tower.py b/wisdem/towerse/tower.py index 79c5d4ec9..7175bd048 100644 --- a/wisdem/towerse/tower.py +++ b/wisdem/towerse/tower.py @@ -21,6 +21,8 @@ def find_nearest(array, value): return (np.abs(array - value)).argmin() +NPTS_SOIL = 10 + # ----------------- # Components # ----------------- @@ -38,7 +40,7 @@ class DiscretizationYAML(om.ExplicitComponent): tower_layer_materials : list of strings 1D array of the names of the materials of each layer modeled in the tower structure. - tower_layer_thickness : numpy array[n_layers_tow, n_height_tow-1], [m] + tower_layer_thickness : numpy array[n_layers_tow, n_height_tow], [m] 2D array of the thickness of the layers of the tower structure. The first dimension represents each layer, the second dimension represents each piecewise- constant entry of the tower sections. @@ -54,7 +56,7 @@ class DiscretizationYAML(om.ExplicitComponent): monopile_layer_materials : list of strings 1D array of the names of the materials of each layer modeled in the tower structure. - monopile_layer_thickness : numpy array[n_layers_mon, n_height_mon_minus], [m] + monopile_layer_thickness : numpy array[n_layers_mon, n_height_mon], [m] 2D array of the thickness of the layers of the tower structure. The first dimension represents each layer, the second dimension represents each piecewise- constant entry of the tower sections. @@ -527,8 +529,6 @@ class TowerMass(om.ExplicitComponent): Mass of monopile from bottom of suction pile through transition piece monopile_cost : float, [USD] Total monopile cost - monopile_length : float, [m] - Length of monopile from bottom of suction pile through transition piece """ @@ -560,7 +560,6 @@ def setup(self): self.add_output("tower_I_base", np.zeros(6), units="kg*m**2") self.add_output("monopile_mass", val=0.0, units="kg") self.add_output("monopile_cost", val=0.0, units="USD") - self.add_output("monopile_length", val=0.0, units="m") self.add_output("transition_piece_I", np.zeros(6), units="kg*m**2") self.add_output("gravity_foundation_I", np.zeros(6), units="kg*m**2") @@ -585,7 +584,6 @@ def compute(self, inputs, outputs): inputs["cylinder_cost"] * outputs["monopile_mass"] / m_cyl.sum() + inputs["transition_piece_cost"] ) outputs["monopile_mass"] += m_trans + m_grav - outputs["monopile_length"] = z_trans - z[0] outputs["tower_cost"] = outputs["structural_cost"] - outputs["monopile_cost"] outputs["tower_mass"] = outputs["structural_mass"] - outputs["monopile_mass"] @@ -764,6 +762,8 @@ class TowerPreFrame(om.ExplicitComponent): def initialize(self): self.options.declare("n_height") self.options.declare("monopile", default=False) + self.options.declare("soil_springs", default=False) + self.options.declare("gravity_foundation", default=False) def setup(self): n_height = self.options["n_height"] @@ -780,16 +780,18 @@ def setup(self): self.add_input("gravity_foundation_I", np.zeros(6), units="kg*m**2") self.add_input("gravity_foundation_mass", 0.0, units="kg") self.add_input("transition_piece_height", 0.0, units="m") + self.add_input("suctionpile_depth", 0.0, units="m") # point loads self.add_input("rna_F", np.zeros(3), units="N") self.add_input("rna_M", np.zeros(3), units="N*m") # Monopile handling - self.add_input("k_monopile", np.zeros(6), units="N/m") + self.add_input("z_soil", np.zeros(NPTS_SOIL), units="N/m") + self.add_input("k_soil", np.zeros((NPTS_SOIL, 6)), units="N/m") # spring reaction data. - nK = 1 + nK = 4 if self.options["monopile"] and not self.options["gravity_foundation"] else 1 self.add_output("kidx", np.zeros(nK, dtype=np.int_)) self.add_output("kx", np.zeros(nK), units="N/m") self.add_output("ky", np.zeros(nK), units="N/m") @@ -883,22 +885,44 @@ def compute(self, inputs, outputs): outputs["Mzz"] = np.array([inputs["rna_M"][2]]).flatten() # Prepare for reactions: rigid at tower base - outputs["kidx"] = np.array([0], dtype=np.int_) - if self.options["monopile"]: - kmono = inputs["k_monopile"] - outputs["kx"] = np.array([kmono[0]]) - outputs["ky"] = np.array([kmono[2]]) - outputs["kz"] = np.array([kmono[4]]) - outputs["ktx"] = np.array([kmono[1]]) - outputs["kty"] = np.array([kmono[3]]) - outputs["ktz"] = np.array([kmono[5]]) + if self.options["monopile"] and not self.options["gravity_foundation"]: + if self.options["soil_springs"]: + z_soil = inputs["z_soil"] + k_soil = inputs["k_soil"] + z_pile = z[z <= (z[0] + 1e-1 + np.abs(z_soil[0]))] + if z_pile.size != 4: + print(z) + print(z_soil) + print(z_pile) + raise ValueError("Please use only one section for submerged pile for now") + k_mono = np.zeros((z_pile.size, 6)) + for k in range(6): + k_mono[:, k] = np.interp(z_pile + np.abs(z_soil[0]), z_soil, k_soil[:, k]) + outputs["kidx"] = np.arange(len(z_pile), dtype=np.int_) + outputs["kx"] = np.array([k_mono[:, 0]]) + outputs["ky"] = np.array([k_mono[:, 2]]) + outputs["kz"] = np.zeros(k_mono.shape[0]) + outputs["kz"][0] = np.array([k_mono[0, 4]]) + outputs["ktx"] = np.array([k_mono[:, 1]]) + outputs["kty"] = np.array([k_mono[:, 3]]) + outputs["ktz"] = np.array([k_mono[:, 5]]) + + else: + z_pile = z[z <= (z[0] + 1e-1 + inputs["suctionpile_depth"])] + npile = z_pile.size + if npile != 4: + print(z) + print(z_pile) + print(inputs["suctionpile_depth"]) + raise ValueError("Please use only one section for submerged pile for now") + outputs["kidx"] = np.arange(npile, dtype=np.int_) + outputs["kx"] = outputs["ky"] = outputs["kz"] = RIGID * np.ones(npile) + outputs["ktx"] = outputs["kty"] = outputs["ktz"] = RIGID * np.ones(npile) + else: - outputs["kx"] = np.array([RIGID]) - outputs["ky"] = np.array([RIGID]) - outputs["kz"] = np.array([RIGID]) - outputs["ktx"] = np.array([RIGID]) - outputs["kty"] = np.array([RIGID]) - outputs["ktz"] = np.array([RIGID]) + outputs["kidx"] = np.array([0], dtype=np.int_) + outputs["kx"] = outputs["ky"] = outputs["kz"] = np.array([RIGID]) + outputs["ktx"] = outputs["kty"] = outputs["ktz"] = np.array([RIGID]) class TowerPostFrame(om.ExplicitComponent): @@ -929,8 +953,8 @@ class TowerPostFrame(om.ExplicitComponent): shear stress in tower elements hoop_stress : numpy array[nFull-1], [N/m**2] hoop stress in tower elements - top_deflection_in : float, [m] - Deflection of tower top in yaw-aligned +x direction + tower_deflection_in : numpy array[nFull], [m] + Deflection of tower nodes in yaw-aligned +x direction life : float fatigue life of tower freqs : numpy array[NFREQ], [Hz] @@ -958,6 +982,8 @@ class TowerPostFrame(om.ExplicitComponent): side_side_modes : numpy array[NFREQ2, 5] 6-degree polynomial coefficients of mode shapes in the tower side-side direction (without constant term) + tower_deflection : numpy array[nFull], [m] + Deflection of tower nodes in yaw-aligned +x direction top_deflection : float, [m] Deflection of tower top in yaw-aligned +x direction stress : numpy array[nFull-1] @@ -1010,7 +1036,7 @@ def setup(self): self.add_input("axial_stress", val=np.zeros(nFull - 1), units="N/m**2") self.add_input("shear_stress", val=np.zeros(nFull - 1), units="N/m**2") self.add_input("hoop_stress", val=np.zeros(nFull - 1), units="N/m**2") - self.add_input("top_deflection_in", 0.0, units="m") + self.add_input("tower_deflection_in", val=np.zeros(nFull), units="m") # safety factors # self.add_input('gamma_f', 1.35, desc='safety factor on loads') @@ -1071,6 +1097,9 @@ def setup(self): np.zeros(NFREQ2), desc="Frequencies associated with mode shapes in the tower side-side direction", ) + self.add_output( + "tower_deflection", np.zeros(nFull), units="m", desc="Deflection of tower top in yaw-aligned +x direction" + ) self.add_output("top_deflection", 0.0, units="m", desc="Deflection of tower top in yaw-aligned +x direction") self.add_output( "stress", @@ -1099,15 +1128,14 @@ def setup(self): ["axial_stress", "d_full", "hoop_stress", "shear_stress", "sigma_y_full", "t_full"], method="fd", ) - self.declare_partials("stress", ["axial_stress", "hoop_stress", "shear_stress", "sigma_y_full"], method="fd") - self.declare_partials("structural_frequencies", ["freqs"], method="fd") - self.declare_partials("fore_aft_freqs", ["x_mode_freqs"], method="fd") - self.declare_partials("side_side_freqs", ["y_mode_freqs"], method="fd") - self.declare_partials("fore_aft_modes", ["x_mode_shapes"], method="fd") - self.declare_partials("side_side_modes", ["y_mode_shapes"], method="fd") - self.declare_partials("top_deflection", ["top_deflection_in"], method="fd") - self.declare_partials("turbine_F", [], method="fd") - self.declare_partials("turbine_M", [], method="fd") + # self.declare_partials("stress", ["axial_stress", "hoop_stress", "shear_stress", "sigma_y_full"], method="fd") + # self.declare_partials("structural_frequencies", ["freqs"], method="fd") + # self.declare_partials("fore_aft_freqs", ["x_mode_freqs"], method="fd") + # self.declare_partials("side_side_freqs", ["y_mode_freqs"], method="fd") + # self.declare_partials("fore_aft_modes", ["x_mode_shapes"], method="fd") + # self.declare_partials("side_side_modes", ["y_mode_shapes"], method="fd") + # self.declare_partials("turbine_F", [], method="fd") + # self.declare_partials("turbine_M", [], method="fd") def compute(self, inputs, outputs): # Unpack some variables @@ -1133,7 +1161,8 @@ def compute(self, inputs, outputs): outputs["side_side_modes"] = inputs["y_mode_shapes"] # Tower top deflection - outputs["top_deflection"] = inputs["top_deflection_in"] + outputs["tower_deflection"] = inputs["tower_deflection_in"] + outputs["top_deflection"] = inputs["tower_deflection_in"][-1] # von mises stress outputs["stress"] = util_con.vonMisesStressUtilization( @@ -1265,7 +1294,6 @@ def setup(self): "transition_piece_I", "monopile_mass", "monopile_cost", - "monopile_length", "structural_mass", "structural_cost", ], @@ -1346,8 +1374,9 @@ def setup(self): self.set_input_defaults("yaw", 0.0, units="deg") self.set_input_defaults("E", np.zeros(n_height - 1), units="N/m**2") self.set_input_defaults("G", np.zeros(n_height - 1), units="N/m**2") - self.set_input_defaults("G_soil", 0.0, units="N/m**2") - self.set_input_defaults("nu_soil", 0.0) + if monopile and mod_opt["soil_springs"]: + self.set_input_defaults("G_soil", 0.0, units="N/m**2") + self.set_input_defaults("nu_soil", 0.0) self.set_input_defaults("sigma_y", np.zeros(n_height - 1), units="N/m**2") self.set_input_defaults("rna_mass", 0.0, units="kg") self.set_input_defaults("rna_cg", np.zeros(3), units="m") @@ -1356,12 +1385,13 @@ def setup(self): # Load baseline discretization self.add_subsystem("geom", TowerLeanSE(modeling_options=self.options["modeling_options"]), promotes=["*"]) - self.add_subsystem( - "soil", TowerSoil(), promotes=[("G", "G_soil"), ("nu", "nu_soil"), ("depth", "suctionpile_depth")] - ) - # Connections for geometry and mass - if monopile: + if monopile and mod_opt["soil_springs"]: + self.add_subsystem( + "soil", + TowerSoil(npts=NPTS_SOIL), + promotes=[("G", "G_soil"), ("nu", "nu_soil"), ("depth", "suctionpile_depth")], + ) self.connect("d_full", "soil.d0", src_indices=[0]) # Add in all Components that drive load cases @@ -1407,7 +1437,12 @@ def setup(self): self.add_subsystem( "pre" + lc, - TowerPreFrame(n_height=n_height, monopile=monopile), + TowerPreFrame( + n_height=n_height, + monopile=monopile, + soil_springs=mod_opt["soil_springs"], + gravity_foundation=mod_opt["gravity_foundation"], + ), promotes=[ "transition_piece_mass", "transition_piece_height", @@ -1415,6 +1450,7 @@ def setup(self): "gravity_foundation_mass", "gravity_foundation_I", "z_full", + "suctionpile_depth", ("mass", "rna_mass"), ("mrho", "rna_cg"), ("mI", "rna_I"), @@ -1424,7 +1460,7 @@ def setup(self): "tower" + lc, CylinderFrame3DD( npts=nFull, - nK=1, + nK=4 if monopile and not mod_opt["gravity_foundation"] else 1, nMass=3, nPL=1, frame3dd_opt=frame3dd_opt, @@ -1477,7 +1513,9 @@ def setup(self): self.connect("pre" + lc + ".Mxx", "tower" + lc + ".Mxx") self.connect("pre" + lc + ".Myy", "tower" + lc + ".Myy") self.connect("pre" + lc + ".Mzz", "tower" + lc + ".Mzz") - self.connect("soil.k", "pre" + lc + ".k_monopile") + if monopile and mod_opt["soil_springs"]: + self.connect("soil.z_k", "pre" + lc + ".z_soil") + self.connect("soil.k", "pre" + lc + ".k_soil") self.connect("tower" + lc + ".freqs", "post" + lc + ".freqs") self.connect("tower" + lc + ".x_mode_freqs", "post" + lc + ".x_mode_freqs") @@ -1490,7 +1528,7 @@ def setup(self): self.connect("tower" + lc + ".axial_stress", "post" + lc + ".axial_stress") self.connect("tower" + lc + ".shear_stress", "post" + lc + ".shear_stress") self.connect("tower" + lc + ".hoop_stress_euro", "post" + lc + ".hoop_stress") - self.connect("tower" + lc + ".top_deflection", "post" + lc + ".top_deflection_in") + self.connect("tower" + lc + ".cylinder_deflection", "post" + lc + ".tower_deflection_in") self.connect("wind" + lc + ".U", "windLoads" + lc + ".U") if monopile: From 9bb5d4b4c3fe48618eade16d99b7f5ea2fb10d3e Mon Sep 17 00:00:00 2001 From: pibo Date: Wed, 30 Dec 2020 09:20:23 -0700 Subject: [PATCH 6/8] uptilt vs uptilt_angle --- examples/02_control_opt/IEA-15-240-RWT.yaml | 2 +- examples/03_NREL5MW_OC3_spar/nrel5mw-spar_oc3.yaml | 2 +- examples/04_NREL5MW_OC4_semi/nrel5mw-semi_oc4.yaml | 2 +- examples/05_IEA-3.4-130-RWT/IEA-3p4-130-RWT.yaml | 2 +- examples/06_IEA-15-240-RWT/IEA-15-240-RWT.yaml | 2 +- examples/07_te_flaps/BAR10.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/02_control_opt/IEA-15-240-RWT.yaml b/examples/02_control_opt/IEA-15-240-RWT.yaml index 400a951e5..cec0fe2a9 100644 --- a/examples/02_control_opt/IEA-15-240-RWT.yaml +++ b/examples/02_control_opt/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/03_NREL5MW_OC3_spar/nrel5mw-spar_oc3.yaml b/examples/03_NREL5MW_OC3_spar/nrel5mw-spar_oc3.yaml index dda4e1387..3c7669ebb 100644 --- a/examples/03_NREL5MW_OC3_spar/nrel5mw-spar_oc3.yaml +++ b/examples/03_NREL5MW_OC3_spar/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/04_NREL5MW_OC4_semi/nrel5mw-semi_oc4.yaml b/examples/04_NREL5MW_OC4_semi/nrel5mw-semi_oc4.yaml index b7be1443d..07d0d3a57 100644 --- a/examples/04_NREL5MW_OC4_semi/nrel5mw-semi_oc4.yaml +++ b/examples/04_NREL5MW_OC4_semi/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/05_IEA-3.4-130-RWT/IEA-3p4-130-RWT.yaml b/examples/05_IEA-3.4-130-RWT/IEA-3p4-130-RWT.yaml index 33f798157..a76f9b3e2 100644 --- a/examples/05_IEA-3.4-130-RWT/IEA-3p4-130-RWT.yaml +++ b/examples/05_IEA-3.4-130-RWT/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/06_IEA-15-240-RWT/IEA-15-240-RWT.yaml b/examples/06_IEA-15-240-RWT/IEA-15-240-RWT.yaml index c77ca32ed..8dc25446c 100644 --- a/examples/06_IEA-15-240-RWT/IEA-15-240-RWT.yaml +++ b/examples/06_IEA-15-240-RWT/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/07_te_flaps/BAR10.yaml b/examples/07_te_flaps/BAR10.yaml index 8b094c454..812bc0ba7 100644 --- a/examples/07_te_flaps/BAR10.yaml +++ b/examples/07_te_flaps/BAR10.yaml @@ -415,7 +415,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 From b6f14a800393fbb01d1d2ca973478752c7fc771d Mon Sep 17 00:00:00 2001 From: pibo Date: Wed, 30 Dec 2020 11:10:05 -0700 Subject: [PATCH 7/8] no del for less than 60 seconds of simulations --- weis/aeroelasticse/openmdao_openfast.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/weis/aeroelasticse/openmdao_openfast.py b/weis/aeroelasticse/openmdao_openfast.py index 7ed994e95..d5881886b 100644 --- a/weis/aeroelasticse/openmdao_openfast.py +++ b/weis/aeroelasticse/openmdao_openfast.py @@ -1069,12 +1069,9 @@ def post_process(self, FAST_Output, case_list, dlc_list, inputs, discrete_inputs loads_analysis.DEL_info = [('RootMyb1', 10), ('RootMyb2', 10)] else: loads_analysis.DEL_info = [('RootMyb1', 10), ('RootMyb2', 10), ('RootMyb3', 10)] + loads_analysis.DEL_info += [('TwrBsMxt', 3), ('TwrBsMyt', 3), ('TwrBsMzt', 3)] else: print('WARNING: the measurement window of the OpenFAST simulations is shorter than 60 seconds. No DEL can be estimated reliably.') - loads_analysis.DEL_info = [('RootMyb1', 10), ('RootMyb2', 10), ('RootMyb3', 10)] - loads_analysis.DEL_info += [('TwrBsMxt', 3), ('TwrBsMyt', 3), ('TwrBsMzt', 3)] - else: - print('WARNING: the measurement window of the OpenFAST simulations is shorter than 60 seconds. No DEL can be estimated reliably.') # get summary stats sum_stats, extreme_table = loads_analysis.summary_stats(FAST_Output) From 5aab9d84744a1cc0a3caf2324b7cdf3d72aeede9 Mon Sep 17 00:00:00 2001 From: pibo Date: Wed, 30 Dec 2020 13:47:19 -0700 Subject: [PATCH 8/8] fix soil connections towerse --- weis/glue_code/glue_code.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/weis/glue_code/glue_code.py b/weis/glue_code/glue_code.py index 51d5cc1b7..85626dec1 100644 --- a/weis/glue_code/glue_code.py +++ b/weis/glue_code/glue_code.py @@ -571,9 +571,10 @@ def setup(self): if modeling_options['flags']['monopile']: self.connect("env.water_depth", "towerse_post.water_depth") self.connect('env.rho_water', 'towerse_post.rho_water') - self.connect('env.mu_water', 'towerse_post.mu_water') - self.connect('env.G_soil', 'towerse_post.G_soil') - self.connect('env.nu_soil', 'towerse_post.nu_soil') + self.connect('env.mu_water', 'towerse_post.mu_water') + if modeling_options["TowerSE"]["soil_springs"]: + self.connect('env.G_soil', 'towerse_post.G_soil') + self.connect('env.nu_soil', 'towerse_post.nu_soil') self.connect("env.Hsig_wave", "towerse_post.Hsig_wave") self.connect("env.Tsig_wave", "towerse_post.Tsig_wave") self.connect('monopile.diameter', 'towerse_post.monopile_outer_diameter_in')