diff --git a/WISDEM/docs/inputs/analysis_schema.rst b/WISDEM/docs/inputs/analysis_schema.rst index c24d6e499..3d00e8c91 100644 --- a/WISDEM/docs/inputs/analysis_schema.rst +++ b/WISDEM/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/WISDEM/examples/02_reference_turbines/IEA-15-240-RWT.yaml b/WISDEM/examples/02_reference_turbines/IEA-15-240-RWT.yaml index c77ca32ed..8dc25446c 100644 --- a/WISDEM/examples/02_reference_turbines/IEA-15-240-RWT.yaml +++ b/WISDEM/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/WISDEM/examples/02_reference_turbines/IEA-3p4-130-RWT.yaml b/WISDEM/examples/02_reference_turbines/IEA-3p4-130-RWT.yaml index d7a29e6fb..70da00927 100644 --- a/WISDEM/examples/02_reference_turbines/IEA-3p4-130-RWT.yaml +++ b/WISDEM/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/WISDEM/examples/02_reference_turbines/analysis_options.yaml b/WISDEM/examples/02_reference_turbines/analysis_options.yaml index 5771047e6..15f4d929f 100644 --- a/WISDEM/examples/02_reference_turbines/analysis_options.yaml +++ b/WISDEM/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/WISDEM/examples/02_reference_turbines/nrel5mw.yaml b/WISDEM/examples/02_reference_turbines/nrel5mw.yaml index 99ecc38cb..57e969f3c 100644 --- a/WISDEM/examples/02_reference_turbines/nrel5mw.yaml +++ b/WISDEM/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/WISDEM/examples/03_blade/blade.yaml b/WISDEM/examples/03_blade/blade.yaml index da9221490..dc8562238 100644 --- a/WISDEM/examples/03_blade/blade.yaml +++ b/WISDEM/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/WISDEM/examples/05_tower_monopile/modeling_options.yaml b/WISDEM/examples/05_tower_monopile/modeling_options.yaml index ed204991c..a29fa85a4 100644 --- a/WISDEM/examples/05_tower_monopile/modeling_options.yaml +++ b/WISDEM/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/WISDEM/examples/05_tower_monopile/monopile_direct.py b/WISDEM/examples/05_tower_monopile/monopile_direct.py index 4bc5bb2f9..1cd8cb696 100644 --- a/WISDEM/examples/05_tower_monopile/monopile_direct.py +++ b/WISDEM/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/WISDEM/examples/05_tower_monopile/tower_direct.py b/WISDEM/examples/05_tower_monopile/tower_direct.py index 91613a83b..8d4626196 100644 --- a/WISDEM/examples/05_tower_monopile/tower_direct.py +++ b/WISDEM/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/WISDEM/examples/09_weis_floating/nrel5mw-semi_oc4.yaml b/WISDEM/examples/09_weis_floating/nrel5mw-semi_oc4.yaml index b7be1443d..07d0d3a57 100644 --- a/WISDEM/examples/09_weis_floating/nrel5mw-semi_oc4.yaml +++ b/WISDEM/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/WISDEM/examples/09_weis_floating/nrel5mw-spar_oc3.yaml b/WISDEM/examples/09_weis_floating/nrel5mw-spar_oc3.yaml index dda4e1387..3c7669ebb 100644 --- a/WISDEM/examples/09_weis_floating/nrel5mw-spar_oc3.yaml +++ b/WISDEM/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/wisdem/ccblade/ccblade_component.py b/WISDEM/wisdem/ccblade/ccblade_component.py index bb2689fee..47a7b3afe 100644 --- a/WISDEM/wisdem/ccblade/ccblade_component.py +++ b/WISDEM/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/wisdem/commonse/environment.py b/WISDEM/wisdem/commonse/environment.py index 11c0d5d3a..8c1272ab8 100644 --- a/WISDEM/wisdem/commonse/environment.py +++ b/WISDEM/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/wisdem/commonse/fileIO.py b/WISDEM/wisdem/commonse/fileIO.py index 5159940da..060f26640 100644 --- a/WISDEM/wisdem/commonse/fileIO.py +++ b/WISDEM/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/wisdem/commonse/vertical_cylinder.py b/WISDEM/wisdem/commonse/vertical_cylinder.py index 6d786670b..22f2bbd52 100644 --- a/WISDEM/wisdem/commonse/vertical_cylinder.py +++ b/WISDEM/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/wisdem/drivetrainse/drive_components.py b/WISDEM/wisdem/drivetrainse/drive_components.py index 5a21e4515..c76f91bda 100644 --- a/WISDEM/wisdem/drivetrainse/drive_components.py +++ b/WISDEM/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/wisdem/drivetrainse/generator.py b/WISDEM/wisdem/drivetrainse/generator.py index 0a3676197..2c28f97b3 100644 --- a/WISDEM/wisdem/drivetrainse/generator.py +++ b/WISDEM/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/wisdem/drivetrainse/generator_models.py b/WISDEM/wisdem/drivetrainse/generator_models.py index e709b3802..e2c9f50f8 100644 --- a/WISDEM/wisdem/drivetrainse/generator_models.py +++ b/WISDEM/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/wisdem/glue_code/gc_LoadInputs.py b/WISDEM/wisdem/glue_code/gc_LoadInputs.py index e2980a5f5..5a45ef3aa 100644 --- a/WISDEM/wisdem/glue_code/gc_LoadInputs.py +++ b/WISDEM/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/wisdem/glue_code/gc_PoseOptimization.py b/WISDEM/wisdem/glue_code/gc_PoseOptimization.py index ad180581e..52046bbb6 100644 --- a/WISDEM/wisdem/glue_code/gc_PoseOptimization.py +++ b/WISDEM/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/wisdem/glue_code/gc_WT_DataStruc.py b/WISDEM/wisdem/glue_code/gc_WT_DataStruc.py index b00709634..05ff582c4 100644 --- a/WISDEM/wisdem/glue_code/gc_WT_DataStruc.py +++ b/WISDEM/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/wisdem/glue_code/gc_WT_InitModel.py b/WISDEM/wisdem/glue_code/gc_WT_InitModel.py index a6c5be4f7..eb500fc0e 100644 --- a/WISDEM/wisdem/glue_code/gc_WT_InitModel.py +++ b/WISDEM/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/wisdem/glue_code/glue_code.py b/WISDEM/wisdem/glue_code/glue_code.py index d13fc7da0..58eeaf127 100644 --- a/WISDEM/wisdem/glue_code/glue_code.py +++ b/WISDEM/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/wisdem/glue_code/runWISDEM.py b/WISDEM/wisdem/glue_code/runWISDEM.py index 52013bd12..d350d0119 100644 --- a/WISDEM/wisdem/glue_code/runWISDEM.py +++ b/WISDEM/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/wisdem/inputs/analysis_schema.yaml b/WISDEM/wisdem/inputs/analysis_schema.yaml index 646997818..513415dd1 100644 --- a/WISDEM/wisdem/inputs/analysis_schema.yaml +++ b/WISDEM/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/wisdem/inputs/geometry_schema.yaml b/WISDEM/wisdem/inputs/geometry_schema.yaml index 2be97c1c8..31cb0546e 100644 --- a/WISDEM/wisdem/inputs/geometry_schema.yaml +++ b/WISDEM/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/wisdem/inputs/modeling_schema.yaml b/WISDEM/wisdem/inputs/modeling_schema.yaml index 33335c965..c0dc898b2 100644 --- a/WISDEM/wisdem/inputs/modeling_schema.yaml +++ b/WISDEM/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/wisdem/landbosse/landbosse_omdao/OpenMDAODataframeCache.py b/WISDEM/wisdem/landbosse/landbosse_omdao/OpenMDAODataframeCache.py index e8450822b..e2c6a347c 100644 --- a/WISDEM/wisdem/landbosse/landbosse_omdao/OpenMDAODataframeCache.py +++ b/WISDEM/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/wisdem/landbosse/landbosse_omdao/landbosse.py b/WISDEM/wisdem/landbosse/landbosse_omdao/landbosse.py index fdde535cb..3b164395c 100644 --- a/WISDEM/wisdem/landbosse/landbosse_omdao/landbosse.py +++ b/WISDEM/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/wisdem/landbosse/model/CollectionCost.py b/WISDEM/wisdem/landbosse/model/CollectionCost.py index a8cae16b7..8e0ed3823 100644 --- a/WISDEM/wisdem/landbosse/model/CollectionCost.py +++ b/WISDEM/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/wisdem/landbosse/model/DevelopmentCost.py b/WISDEM/wisdem/landbosse/model/DevelopmentCost.py index ba84dd707..acd048e3b 100644 --- a/WISDEM/wisdem/landbosse/model/DevelopmentCost.py +++ b/WISDEM/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/wisdem/landbosse/model/ErectionCost.py b/WISDEM/wisdem/landbosse/model/ErectionCost.py index a74bce8e1..ccd5c1efc 100644 --- a/WISDEM/wisdem/landbosse/model/ErectionCost.py +++ b/WISDEM/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/wisdem/landbosse/model/FoundationCost.py b/WISDEM/wisdem/landbosse/model/FoundationCost.py index a5b2ac7ea..2583ea4a4 100644 --- a/WISDEM/wisdem/landbosse/model/FoundationCost.py +++ b/WISDEM/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/wisdem/landbosse/model/GridConnectionCost.py b/WISDEM/wisdem/landbosse/model/GridConnectionCost.py index 72cdbe1b3..65579efeb 100644 --- a/WISDEM/wisdem/landbosse/model/GridConnectionCost.py +++ b/WISDEM/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/wisdem/landbosse/model/ManagementCost.py b/WISDEM/wisdem/landbosse/model/ManagementCost.py index 075eaa232..6a6ae364d 100644 --- a/WISDEM/wisdem/landbosse/model/ManagementCost.py +++ b/WISDEM/wisdem/landbosse/model/ManagementCost.py @@ -1,7 +1,7 @@ import math import traceback + import pytest -import traceback class ManagementCost: diff --git a/WISDEM/wisdem/landbosse/model/Manager.py b/WISDEM/wisdem/landbosse/model/Manager.py index 525a59bce..ac0d902df 100644 --- a/WISDEM/wisdem/landbosse/model/Manager.py +++ b/WISDEM/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/wisdem/landbosse/model/SitePreparationCost.py b/WISDEM/wisdem/landbosse/model/SitePreparationCost.py index 1ae20479e..ffa392338 100644 --- a/WISDEM/wisdem/landbosse/model/SitePreparationCost.py +++ b/WISDEM/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/wisdem/landbosse/model/SubstationCost.py b/WISDEM/wisdem/landbosse/model/SubstationCost.py index 14e161855..478fcc02d 100644 --- a/WISDEM/wisdem/landbosse/model/SubstationCost.py +++ b/WISDEM/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/wisdem/orbit/_version.py b/WISDEM/wisdem/orbit/_version.py index 4d4385b3d..24d24ba1c 100644 --- a/WISDEM/wisdem/orbit/_version.py +++ b/WISDEM/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/wisdem/orbit/api/wisdem.py b/WISDEM/wisdem/orbit/api/wisdem.py index 5f1941a1e..2241bb157 100644 --- a/WISDEM/wisdem/orbit/api/wisdem.py +++ b/WISDEM/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/wisdem/orbit/core/_defaults.py b/WISDEM/wisdem/orbit/core/_defaults.py deleted file mode 100644 index 40ddd6e87..000000000 --- a/WISDEM/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/wisdem/orbit/core/components.py b/WISDEM/wisdem/orbit/core/components.py index 5f90b298b..997a5a5fd 100644 --- a/WISDEM/wisdem/orbit/core/components.py +++ b/WISDEM/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/wisdem/orbit/core/library.py b/WISDEM/wisdem/orbit/core/library.py index 6381312e7..6dd3febdb 100644 --- a/WISDEM/wisdem/orbit/core/library.py +++ b/WISDEM/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/wisdem/orbit/core/logic/__init__.py b/WISDEM/wisdem/orbit/core/logic/__init__.py index 340d9403f..156444016 100644 --- a/WISDEM/wisdem/orbit/core/logic/__init__.py +++ b/WISDEM/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/wisdem/orbit/core/logic/vessel_logic.py b/WISDEM/wisdem/orbit/core/logic/vessel_logic.py index 5615d7780..c320b48cb 100644 --- a/WISDEM/wisdem/orbit/core/logic/vessel_logic.py +++ b/WISDEM/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/wisdem/orbit/core/vessel.py b/WISDEM/wisdem/orbit/core/vessel.py index 95b3b939c..e3534d80a 100644 --- a/WISDEM/wisdem/orbit/core/vessel.py +++ b/WISDEM/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/wisdem/orbit/manager.py b/WISDEM/wisdem/orbit/manager.py index e4c07865e..7ea4cb2b4 100644 --- a/WISDEM/wisdem/orbit/manager.py +++ b/WISDEM/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/wisdem/orbit/phases/base.py b/WISDEM/wisdem/orbit/phases/base.py index e49f9d1ce..051314450 100644 --- a/WISDEM/wisdem/orbit/phases/base.py +++ b/WISDEM/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/wisdem/orbit/phases/design/__init__.py b/WISDEM/wisdem/orbit/phases/design/__init__.py index c7d85801e..d234df4c7 100644 --- a/WISDEM/wisdem/orbit/phases/design/__init__.py +++ b/WISDEM/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/wisdem/orbit/phases/design/_cables.py b/WISDEM/wisdem/orbit/phases/design/_cables.py index 23f6aa517..73e3d10da 100644 --- a/WISDEM/wisdem/orbit/phases/design/_cables.py +++ b/WISDEM/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/wisdem/orbit/phases/design/array_system_design.py b/WISDEM/wisdem/orbit/phases/design/array_system_design.py index 33321127d..bad4308da 100644 --- a/WISDEM/wisdem/orbit/phases/design/array_system_design.py +++ b/WISDEM/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/wisdem/orbit/phases/design/export_system_design.py b/WISDEM/wisdem/orbit/phases/design/export_system_design.py index 484788d18..d62f8288c 100644 --- a/WISDEM/wisdem/orbit/phases/design/export_system_design.py +++ b/WISDEM/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/wisdem/orbit/phases/design/monopile_design.py b/WISDEM/wisdem/orbit/phases/design/monopile_design.py index 474c7b03a..e2fdc6c1b 100644 --- a/WISDEM/wisdem/orbit/phases/design/monopile_design.py +++ b/WISDEM/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/wisdem/orbit/phases/design/mooring_system_design.py b/WISDEM/wisdem/orbit/phases/design/mooring_system_design.py index 53dc2f5a9..a0b0f381c 100644 --- a/WISDEM/wisdem/orbit/phases/design/mooring_system_design.py +++ b/WISDEM/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/wisdem/orbit/phases/design/oss_design.py b/WISDEM/wisdem/orbit/phases/design/oss_design.py index 9a04631df..8b17c0fd0 100644 --- a/WISDEM/wisdem/orbit/phases/design/oss_design.py +++ b/WISDEM/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/wisdem/orbit/phases/design/project_development.py b/WISDEM/wisdem/orbit/phases/design/project_development.py deleted file mode 100644 index f79ae7172..000000000 --- a/WISDEM/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/wisdem/orbit/phases/design/scour_protection_design.py b/WISDEM/wisdem/orbit/phases/design/scour_protection_design.py index 60d01df25..dd707ca8f 100644 --- a/WISDEM/wisdem/orbit/phases/design/scour_protection_design.py +++ b/WISDEM/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/wisdem/orbit/phases/design/semi_submersible_design.py b/WISDEM/wisdem/orbit/phases/design/semi_submersible_design.py index 29a14b552..1cbf3eac2 100644 --- a/WISDEM/wisdem/orbit/phases/design/semi_submersible_design.py +++ b/WISDEM/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/wisdem/orbit/phases/design/spar_design.py b/WISDEM/wisdem/orbit/phases/design/spar_design.py index 50b2b01f7..36e26aafd 100644 --- a/WISDEM/wisdem/orbit/phases/design/spar_design.py +++ b/WISDEM/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/wisdem/orbit/phases/install/cable_install/array.py b/WISDEM/wisdem/orbit/phases/install/cable_install/array.py index c75b20f0a..c4fbe8c2e 100644 --- a/WISDEM/wisdem/orbit/phases/install/cable_install/array.py +++ b/WISDEM/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/wisdem/orbit/phases/install/cable_install/common.py b/WISDEM/wisdem/orbit/phases/install/cable_install/common.py index c6ca15aff..bf3dd2fb6 100644 --- a/WISDEM/wisdem/orbit/phases/install/cable_install/common.py +++ b/WISDEM/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/wisdem/orbit/phases/install/cable_install/export.py b/WISDEM/wisdem/orbit/phases/install/cable_install/export.py index b6a1f9195..c1339e1bd 100644 --- a/WISDEM/wisdem/orbit/phases/install/cable_install/export.py +++ b/WISDEM/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/wisdem/orbit/phases/install/install_phase.py b/WISDEM/wisdem/orbit/phases/install/install_phase.py index 281c5f80d..a022e204d 100644 --- a/WISDEM/wisdem/orbit/phases/install/install_phase.py +++ b/WISDEM/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/wisdem/orbit/phases/install/monopile_install/common.py b/WISDEM/wisdem/orbit/phases/install/monopile_install/common.py index c6aefea2f..85c2b62dd 100644 --- a/WISDEM/wisdem/orbit/phases/install/monopile_install/common.py +++ b/WISDEM/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/wisdem/orbit/phases/install/monopile_install/standard.py b/WISDEM/wisdem/orbit/phases/install/monopile_install/standard.py index be83f8803..e0092b382 100644 --- a/WISDEM/wisdem/orbit/phases/install/monopile_install/standard.py +++ b/WISDEM/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/wisdem/orbit/phases/install/mooring_install/mooring.py b/WISDEM/wisdem/orbit/phases/install/mooring_install/mooring.py index 0a7e51466..c8767add9 100644 --- a/WISDEM/wisdem/orbit/phases/install/mooring_install/mooring.py +++ b/WISDEM/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/wisdem/orbit/phases/install/oss_install/common.py b/WISDEM/wisdem/orbit/phases/install/oss_install/common.py index c0246d309..9c27abb26 100644 --- a/WISDEM/wisdem/orbit/phases/install/oss_install/common.py +++ b/WISDEM/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/wisdem/orbit/phases/install/oss_install/standard.py b/WISDEM/wisdem/orbit/phases/install/oss_install/standard.py index 9a383e331..c8a9e9cdb 100644 --- a/WISDEM/wisdem/orbit/phases/install/oss_install/standard.py +++ b/WISDEM/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/wisdem/orbit/phases/install/quayside_assembly_tow/gravity_base.py b/WISDEM/wisdem/orbit/phases/install/quayside_assembly_tow/gravity_base.py index ad3a16fa6..9d6438e16 100644 --- a/WISDEM/wisdem/orbit/phases/install/quayside_assembly_tow/gravity_base.py +++ b/WISDEM/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/wisdem/orbit/phases/install/quayside_assembly_tow/moored.py b/WISDEM/wisdem/orbit/phases/install/quayside_assembly_tow/moored.py index c090a797b..ad952cfef 100644 --- a/WISDEM/wisdem/orbit/phases/install/quayside_assembly_tow/moored.py +++ b/WISDEM/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/wisdem/orbit/phases/install/scour_protection_install/standard.py b/WISDEM/wisdem/orbit/phases/install/scour_protection_install/standard.py index 611254304..b1a6ac41c 100644 --- a/WISDEM/wisdem/orbit/phases/install/scour_protection_install/standard.py +++ b/WISDEM/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/wisdem/orbit/phases/install/turbine_install/standard.py b/WISDEM/wisdem/orbit/phases/install/turbine_install/standard.py index 412955651..140c75cdb 100644 --- a/WISDEM/wisdem/orbit/phases/install/turbine_install/standard.py +++ b/WISDEM/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/wisdem/rotorse/rotor_elasticity.py b/WISDEM/wisdem/rotorse/rotor_elasticity.py index 3b0abf94d..80c140833 100644 --- a/WISDEM/wisdem/rotorse/rotor_elasticity.py +++ b/WISDEM/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/wisdem/rotorse/rotor_power.py b/WISDEM/wisdem/rotorse/rotor_power.py index 1f76bc169..5498b9afe 100644 --- a/WISDEM/wisdem/rotorse/rotor_power.py +++ b/WISDEM/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/wisdem/test/test_drivetrainse/test_components.py b/WISDEM/wisdem/test/test_drivetrainse/test_components.py index 3673a6f1e..809d53153 100644 --- a/WISDEM/wisdem/test/test_drivetrainse/test_components.py +++ b/WISDEM/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/wisdem/test/test_drivetrainse/test_drivetrainse.py b/WISDEM/wisdem/test/test_drivetrainse/test_drivetrainse.py index d76abb1b6..10dd773b6 100644 --- a/WISDEM/wisdem/test/test_drivetrainse/test_drivetrainse.py +++ b/WISDEM/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/wisdem/test/test_drivetrainse/test_generator_driver.py b/WISDEM/wisdem/test/test_drivetrainse/test_generator_driver.py new file mode 100644 index 000000000..b4e8c2f92 --- /dev/null +++ b/WISDEM/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/wisdem/test/test_drivetrainse/test_generator_models.py b/WISDEM/wisdem/test/test_drivetrainse/test_generator_models.py index 406a87930..3ae179342 100644 --- a/WISDEM/wisdem/test/test_drivetrainse/test_generator_models.py +++ b/WISDEM/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/wisdem/test/test_gluecode/test_gluecode.py b/WISDEM/wisdem/test/test_gluecode/test_gluecode.py index 126a7c421..8278be00a 100644 --- a/WISDEM/wisdem/test/test_gluecode/test_gluecode.py +++ b/WISDEM/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/wisdem/test/test_landbosse/test_landbosse.py b/WISDEM/wisdem/test/test_landbosse/test_landbosse.py index ca3906cf9..0a8d96620 100644 --- a/WISDEM/wisdem/test/test_landbosse/test_landbosse.py +++ b/WISDEM/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/wisdem/test/test_orbit/conftest.py b/WISDEM/wisdem/test/test_orbit/conftest.py index 6c301c89e..e5faed6fa 100644 --- a/WISDEM/wisdem/test/test_orbit/conftest.py +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/__init__.py b/WISDEM/wisdem/test/test_orbit/data/library/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/WISDEM/wisdem/test/test_orbit/data/library/cables/__init__.py b/WISDEM/wisdem/test/test_orbit/data/library/cables/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/WISDEM/wisdem/test/test_orbit/data/library/defaults/__init__.py b/WISDEM/wisdem/test/test_orbit/data/library/defaults/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/WISDEM/wisdem/test/test_orbit/data/library/project/__init__.py b/WISDEM/wisdem/test/test_orbit/data/library/project/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/WISDEM/wisdem/test/test_orbit/data/library/project/config/__init__.py b/WISDEM/wisdem/test/test_orbit/data/library/project/config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/WISDEM/wisdem/test/test_orbit/data/library/project/config/array_cable_install.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/array_cable_install.yaml index 3e61d45d2..c5c8282e4 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/project/config/array_cable_install.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/complete_floating_project.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/complete_floating_project.yaml new file mode 100644 index 000000000..45fbc3ecf --- /dev/null +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/export_cable_install.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/export_cable_install.yaml index 4fe13a012..beccf930b 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/project/config/export_cable_install.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/floating_oss_install.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/floating_oss_install.yaml new file mode 100644 index 000000000..d9dab2b15 --- /dev/null +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/floating_turbine_install_feeder.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/floating_turbine_install_feeder.yaml new file mode 100644 index 000000000..5780036ab --- /dev/null +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/moored_install.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/moored_install.yaml index 90b0b5ffc..47cd8bb0a 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/project/config/moored_install.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/moored_install_no_supply.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/moored_install_no_supply.yaml index 7a0782a25..249abd857 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/project/config/moored_install_no_supply.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/mooring_system_install.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/mooring_system_install.yaml index a9e34a609..7d32e74b8 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/project/config/mooring_system_install.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/multi_wtiv_mono_install.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/multi_wtiv_mono_install.yaml index 7812e2e0f..74f22bfde 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/project/config/multi_wtiv_mono_install.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/oss_install.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/oss_install.yaml index 7c6f1945b..a7b00dd02 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/project/config/oss_install.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/oss_multi_feeder_substation_install.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/oss_multi_feeder_substation_install.yaml index 6f325b41c..6167b0b99 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/project/config/oss_multi_feeder_substation_install.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/project_manager.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/project_manager.yaml index 47187b966..46ce05e3b 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/project/config/project_manager.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/scour_protection_install.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/scour_protection_install.yaml index fd23223ce..f4600895d 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/project/config/scour_protection_install.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/project/config/single_wtiv_mono_install.yaml b/WISDEM/wisdem/test/test_orbit/data/library/project/config/single_wtiv_mono_install.yaml index 68adaccf0..9e4c65ff8 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/project/config/single_wtiv_mono_install.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/turbines/__init__.py b/WISDEM/wisdem/test/test_orbit/data/library/turbines/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/WISDEM/wisdem/test/test_orbit/data/library/vessels/__init__.py b/WISDEM/wisdem/test/test_orbit/data/library/vessels/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_cable_lay_vessel.yaml b/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_cable_lay_vessel.yaml index c9df9a726..73bfb1a82 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_cable_lay_vessel.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/vessels/test_feeder.yaml b/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_feeder.yaml index 807db0420..c2785b028 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_feeder.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/vessels/test_floating_barge.yaml b/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_floating_barge.yaml new file mode 100644 index 000000000..1fcd8ef8f --- /dev/null +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/vessels/test_floating_heavy_lift_vessel.yaml b/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_floating_heavy_lift_vessel.yaml new file mode 100644 index 000000000..44b9f7e9b --- /dev/null +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/vessels/test_heavy_lift_vessel.yaml b/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_heavy_lift_vessel.yaml index a63057d9f..9eddae7c6 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_heavy_lift_vessel.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/vessels/test_phase_specific_wtiv.yaml b/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_phase_specific_wtiv.yaml index 8ae383127..b382722cf 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_phase_specific_wtiv.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/vessels/test_scour_protection_vessel.yaml b/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_scour_protection_vessel.yaml index 83d0b7947..bb2fe5cfb 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_scour_protection_vessel.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/vessels/test_towing_vessel.yaml b/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_towing_vessel.yaml index b1de857d4..5aa4bf588 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_towing_vessel.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/vessels/test_wtiv.yaml b/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_wtiv.yaml index 899be7a7b..41b70505c 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_wtiv.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/data/library/vessels/test_wtiv_mobilize.yaml b/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_wtiv_mobilize.yaml index 58034bb90..bc6b8bff4 100644 --- a/WISDEM/wisdem/test/test_orbit/data/library/vessels/test_wtiv_mobilize.yaml +++ b/WISDEM/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/wisdem/test/test_orbit/phases/design/test_array_system_design.py b/WISDEM/wisdem/test/test_orbit/phases/design/test_array_system_design.py index 9da26db35..d2974b4a3 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/design/test_array_system_design.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/design/test_cable.py b/WISDEM/wisdem/test/test_orbit/phases/design/test_cable.py index 0f07d7a39..fa42c5e0d 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/design/test_cable.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/design/test_export_system_design.py b/WISDEM/wisdem/test/test_orbit/phases/design/test_export_system_design.py index a41570e69..b0994f8d1 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/design/test_export_system_design.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/design/test_mooring_system_design.py b/WISDEM/wisdem/test/test_orbit/phases/design/test_mooring_system_design.py index 0e1ed270c..51ca9b229 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/design/test_mooring_system_design.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/design/test_oss_design.py b/WISDEM/wisdem/test/test_orbit/phases/design/test_oss_design.py index 867078d78..e3c4199e5 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/design/test_oss_design.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/design/test_project_development.py b/WISDEM/wisdem/test/test_orbit/phases/design/test_project_development.py deleted file mode 100644 index 267be2f57..000000000 --- a/WISDEM/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/wisdem/test/test_orbit/phases/design/test_scour_protection_design.py b/WISDEM/wisdem/test/test_orbit/phases/design/test_scour_protection_design.py index c22d07b2e..042075b38 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/design/test_scour_protection_design.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/design/test_semisubmersible_design.py b/WISDEM/wisdem/test/test_orbit/phases/design/test_semisubmersible_design.py index 39dc3b78f..e5f7ac826 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/design/test_semisubmersible_design.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/design/test_spar_design.py b/WISDEM/wisdem/test/test_orbit/phases/design/test_spar_design.py index 888cf6839..2e39dfdef 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/design/test_spar_design.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/install/cable_install/test_array_install.py b/WISDEM/wisdem/test/test_orbit/phases/install/cable_install/test_array_install.py index 6705e1772..324828bdb 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/install/cable_install/test_array_install.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/install/cable_install/test_export_install.py b/WISDEM/wisdem/test/test_orbit/phases/install/cable_install/test_export_install.py index 8d3b2ab56..8fd7b18a9 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/install/cable_install/test_export_install.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_install.py b/WISDEM/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_install.py index 043891848..bcd45085a 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_install.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_tasks.py b/WISDEM/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_tasks.py index 7218e0501..f6e0f1474 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/install/monopile_install/test_monopile_tasks.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/install/mooring_install/test_mooring_install.py b/WISDEM/wisdem/test/test_orbit/phases/install/mooring_install/test_mooring_install.py index ee0e2fb27..20c77da9a 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/install/mooring_install/test_mooring_install.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/install/oss_install/test_oss_install.py b/WISDEM/wisdem/test/test_orbit/phases/install/oss_install/test_oss_install.py index 70446a667..4e130be98 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/install/oss_install/test_oss_install.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_common.py b/WISDEM/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_common.py index 3da75c077..c5a5dbdfa 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_common.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_gravity_based.py b/WISDEM/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_gravity_based.py index 7b00f7cc4..4c62cd54a 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_gravity_based.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_moored.py b/WISDEM/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_moored.py index 9bc79fe84..d424f67ae 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/install/quayside_assembly_tow/test_moored.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/install/scour_protection_install/test_scour_protection.py b/WISDEM/wisdem/test/test_orbit/phases/install/scour_protection_install/test_scour_protection.py index 72bef4d3b..d822b7d9c 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/install/scour_protection_install/test_scour_protection.py +++ b/WISDEM/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/wisdem/test/test_orbit/phases/install/turbine_install/test_turbine_install.py b/WISDEM/wisdem/test/test_orbit/phases/install/turbine_install/test_turbine_install.py index bf1130ea0..9d3770079 100644 --- a/WISDEM/wisdem/test/test_orbit/phases/install/turbine_install/test_turbine_install.py +++ b/WISDEM/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/wisdem/test/test_orbit/test_design_install_phase_interactions.py b/WISDEM/wisdem/test/test_orbit/test_design_install_phase_interactions.py index ff4223350..d2469efa1 100644 --- a/WISDEM/wisdem/test/test_orbit/test_design_install_phase_interactions.py +++ b/WISDEM/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/wisdem/test/test_orbit/test_project_manager.py b/WISDEM/wisdem/test/test_orbit/test_project_manager.py index 8b3c4c52d..33b2671d0 100644 --- a/WISDEM/wisdem/test/test_orbit/test_project_manager.py +++ b/WISDEM/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/wisdem/test/test_rotorse/test_rotor_power.py b/WISDEM/wisdem/test/test_rotorse/test_rotor_power.py index dd24255c3..bd6cb7fec 100644 --- a/WISDEM/wisdem/test/test_rotorse/test_rotor_power.py +++ b/WISDEM/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/wisdem/test/test_towerse/test_tower.py b/WISDEM/wisdem/test/test_towerse/test_tower.py index a2332163e..2b1728902 100644 --- a/WISDEM/wisdem/test/test_towerse/test_tower.py +++ b/WISDEM/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/wisdem/towerse/tower.py b/WISDEM/wisdem/towerse/tower.py index 79c5d4ec9..7175bd048 100644 --- a/WISDEM/wisdem/towerse/tower.py +++ b/WISDEM/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: 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 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) diff --git a/weis/glue_code/gc_LoadInputs.py b/weis/glue_code/gc_LoadInputs.py index 56a1eec1b..178448540 100644 --- a/weis/glue_code/gc_LoadInputs.py +++ b/weis/glue_code/gc_LoadInputs.py @@ -75,19 +75,20 @@ 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 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): 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() 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') 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 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]