def compute(self, inputs, outputs): # Unpack inputs m_pitch = float(inputs["pitch_mass"]) m_hub = float(inputs["hub_mass"]) m_spin = float(inputs["spinner_mass"]) cm_hub = float(inputs["hub_cm"]) cm_spin = float(inputs["spinner_cm"]) # Mass and cost totals m_total = m_pitch + m_hub + m_spin c_total = inputs["pitch_cost"] + inputs["hub_cost"] + inputs[ "spinner_cost"] # CofM total cm_total = ((m_pitch + m_hub) * cm_hub + m_spin * cm_spin) / m_total # I total I_total = util.assembleI(np.zeros(6)) r = np.array([cm_hub - cm_total, 0.0, 0.0]) I_total += util.assembleI(inputs["hub_I"]) + m_hub * ( np.dot(r, r) * np.eye(3) - np.outer(r, r)) I_total += util.assembleI(inputs["pitch_I"]) + m_pitch * ( np.dot(r, r) * np.eye(3) - np.outer(r, r)) r = np.array([cm_spin - cm_total, 0.0, 0.0]) I_total += util.assembleI(inputs["spinner_I"]) + m_spin * ( np.dot(r, r) * np.eye(3) - np.outer(r, r)) # Outputs outputs["hub_system_mass"] = m_total outputs["hub_system_cost"] = c_total outputs["hub_system_cm"] = cm_total outputs["hub_system_I"] = util.unassembleI(I_total)
def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): Cup = -1.0 if discrete_inputs["upwind"] else 1.0 tilt = float(np.deg2rad(inputs["tilt"])) rotor_mass = inputs["blades_mass"] + inputs["hub_system_mass"] nac_mass = inputs["nacelle_mass"] # rna mass outputs["rotor_mass"] = rotor_mass outputs["rna_mass"] = rotor_mass + nac_mass # rna cm shaft0 = inputs["shaft_start"] hub_cm = inputs["hub_system_cm"] L_drive = inputs["L_drive"] hub_cm = shaft0 + (L_drive + hub_cm) * np.array( [Cup * np.cos(tilt), 0.0, np.sin(tilt)]) outputs["rna_cm"] = (rotor_mass * hub_cm + nac_mass * inputs["nacelle_cm"]) / outputs["rna_mass"] # rna I hub_I = util.assembleI( util.rotateI(inputs["hub_system_I"], -Cup * tilt, axis="y")) blades_I = util.assembleI( util.rotateI(inputs["blades_I"], -Cup * tilt, axis="y")) nac_I_TT = util.assembleI(inputs["nacelle_I_TT"]) rotor_I = blades_I + hub_I R = hub_cm rotor_I_TT = rotor_I + rotor_mass * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) outputs["rna_I_TT"] = util.unassembleI(rotor_I_TT + nac_I_TT)
def compute(self, inputs, outputs): outputs['turbine_mass'] = inputs['rna_mass'] + inputs['tower_mass'] cg_rna = inputs['rna_cg'] + np.array([0.0, 0.0, inputs['hub_height']]) cg_tower = np.array([0.0, 0.0, inputs['tower_center_of_mass']]) outputs['turbine_center_of_mass'] = (inputs['rna_mass']*cg_rna + inputs['tower_mass']*cg_tower) / outputs['turbine_mass'] R = cg_rna I_tower = assembleI(inputs['tower_I_base']) I_rna = assembleI(inputs['rna_I']) + inputs['rna_mass']*(np.dot(R, R)*np.eye(3) - np.outer(R, R)) outputs['turbine_I_base'] = unassembleI(I_tower + I_rna)
def compute(self, inputs, outputs): outputs["turbine_mass"] = inputs["rna_mass"] + inputs["tower_mass"] + inputs["monopile_mass"] cg_rna = inputs["rna_cg"] + np.r_[0.0, 0.0, inputs["hub_height"]] cg_tower = np.r_[0.0, 0.0, inputs["tower_center_of_mass"]] outputs["turbine_center_of_mass"] = (inputs["rna_mass"] * cg_rna + inputs["tower_mass"] * cg_tower) / outputs[ "turbine_mass" ] R = cg_rna I_tower = util.assembleI(inputs["tower_I_base"]) I_rna = util.assembleI(inputs["rna_I"]) + inputs["rna_mass"] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) outputs["turbine_I_base"] = util.unassembleI(I_tower + I_rna)
def compute_rigid_body_periods(self, inputs, outputs): # Unpack variables ncolumn = int(inputs['number_of_offset_columns']) R_semi = inputs['radius_to_offset_column'] m_main = np.sum(inputs['main_mass']) m_column = np.sum(inputs['offset_mass']) m_tower = np.sum(inputs['tower_mass']) m_rna = inputs['rna_mass'] m_mooring = inputs['mooring_mass'] m_total = outputs['total_mass'] m_water = np.maximum(0.0, outputs['variable_ballast_mass']) m_a_main = inputs['main_added_mass'] m_a_column = inputs['offset_added_mass'] rhoWater = inputs['water_density'] V_system = inputs['total_displacement'] h_metacenter = outputs['metacentric_height'] Awater_main = inputs['main_Awaterplane'] Awater_column = inputs['offset_Awaterplane'] I_main = inputs['main_moments_of_inertia'] I_column = inputs['offset_moments_of_inertia'] I_mooring = inputs['mooring_moments_of_inertia'] I_water = outputs['variable_ballast_moments_of_inertia'] I_tower = inputs['tower_I_base'] I_rna = inputs['rna_I'] I_waterplane = outputs['Iwaterplane_system'] z_cg_main = float(inputs['main_center_of_mass']) z_cb_main = float(inputs['main_center_of_buoyancy']) z_cg_column = float(inputs['offset_center_of_mass']) z_cb_column = float(inputs['offset_center_of_buoyancy']) z_cb = float(inputs['z_center_of_buoyancy']) z_cg_water = float(outputs['variable_ballast_center_of_mass']) z_fairlead = float(inputs['fairlead'] * (-1)) r_cg = outputs['center_of_mass'] cg_rna = inputs['rna_cg'] z_tower = inputs['tower_z_full'] K_moor = np.diag(inputs['mooring_stiffness']) # Number of degrees of freedom nDOF = 6 # Compute elements on mass matrix diagonal M_mat = np.zeros((nDOF, )) # Surge, sway, heave just use normal inertia (without mooring according to Senu) M_mat[:3] = m_total + m_water - m_mooring # Add in moments of inertia of primary column I_total = assembleI(np.zeros(6)) I_main = assembleI(I_main) R = np.array([0.0, 0.0, z_cg_main]) - r_cg I_total += I_main + m_main * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) # Add up moments of intertia of other columns radii_x = R_semi * np.cos(np.linspace(0, 2 * np.pi, ncolumn + 1)) radii_y = R_semi * np.sin(np.linspace(0, 2 * np.pi, ncolumn + 1)) I_column = assembleI(I_column) for k in range(ncolumn): R = np.array([radii_x[k], radii_y[k], z_cg_column]) - r_cg I_total += I_column + m_column * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) # Add in variable ballast R = np.array([0.0, 0.0, z_cg_water]) - r_cg I_total += assembleI(I_water) + m_water * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) # Save what we have so far as m_substructure & I_substructure and move to its own CM m_subs = m_main + ncolumn * m_column + m_water z_cg_subs = (m_main * z_cg_main + ncolumn * m_column * z_cg_column + m_water * z_cg_water) / m_subs R = r_cg - np.array([0.0, 0.0, z_cg_subs]) I_substructure = I_total + m_subs * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) outputs['substructure_moments_of_inertia'] = unassembleI(I_total) # Now go back to the total # Add in mooring system- Not needed according to Senu #R = np.array([0.0, 0.0, z_fairlead]) - r_cg #I_total += assembleI(I_mooring) + m_mooring*(np.dot(R, R)*np.eye(3) - np.outer(R, R)) # Add in tower R = np.array([0.0, 0.0, z_tower[0]]) - r_cg I_total += assembleI(I_tower) + m_tower * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) # Add in RNA R = np.array([0.0, 0.0, z_tower[-1]]) + cg_rna - r_cg I_total += assembleI(I_rna) + m_rna * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) # Stuff moments of inertia into mass matrix M_mat[3:] = unassembleI(I_total)[:3] outputs['mass_matrix'] = M_mat # Add up all added mass entries in a similar way A_mat = np.zeros((nDOF, )) # Surge, sway, heave just use normal inertia A_mat[:3] = m_a_main[:3] + ncolumn * m_a_column[:3] # Add up moments of inertia, move added mass moments from CofB to CofG I_main = assembleI(np.r_[m_a_main[3:], np.zeros(3)]) R = np.array([0.0, 0.0, z_cb_main]) - r_cg I_total = I_main + m_a_main[0] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) # Add up added moments of intertia of all columns for other entries I_column = assembleI(np.r_[m_a_column[3:], np.zeros(3)]) for k in range(ncolumn): R = np.array([radii_x[k], radii_y[k], z_cb_column]) - r_cg I_total += I_column + m_a_column[0] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) A_mat[3:] = unassembleI(I_total)[:3] outputs['added_mass_matrix'] = A_mat # Hydrostatic stiffness has contributions in heave (K33) and roll/pitch (K44/55) # See DNV-RP-H103: Modeling and Analyis of Marine Operations K_hydro = np.zeros((nDOF, )) K_hydro[2] = rhoWater * gravity * (Awater_main + ncolumn * Awater_column) K_hydro[ 3: 5] = rhoWater * gravity * V_system * h_metacenter # FAST eqns: (I_waterplane + V_system * z_cb) outputs['hydrostatic_stiffness'] = K_hydro # Now compute all six natural periods at once epsilon = 1e-6 # Avoids numerical issues K_total = np.maximum(K_hydro + K_moor, 0.0) outputs['rigid_body_periods'] = 2 * np.pi * np.sqrt( (M_mat + A_mat) / (K_total + epsilon))
def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Unpack inputs L_12 = float(inputs["L_12"]) L_h1 = float(inputs["L_h1"]) L_generator = float(inputs["L_generator"]) L_overhang = float(inputs["overhang"]) H_drive = float(inputs["drive_height"]) tilt = float(np.deg2rad(inputs["tilt"])) D_access = float(inputs["access_diameter"]) D_nose = inputs["nose_diameter"] D_lss = inputs["lss_diameter"] D_top = float(inputs["D_top"]) D_hub = float(inputs["hub_diameter"]) t_nose = inputs["nose_wall_thickness"] t_lss = inputs["lss_wall_thickness"] t_bed = inputs["bedplate_wall_thickness"] upwind = discrete_inputs["upwind"] lss_rho = float(inputs["lss_rho"]) bedplate_rho = float(inputs["bedplate_rho"]) # ------- Discretization ---------------- L_grs = 0.5 * L_h1 L_gsn = L_generator - L_grs - L_12 L_2n = 2.0 * L_gsn # Length of lss and nose L_lss = L_12 + L_h1 L_nose = L_12 + L_2n outputs["L_lss"] = L_lss outputs["L_nose"] = L_nose # Total length from bedplate to hub flange ds = 0.5 * np.ones(2) s_drive = np.cumsum(np.r_[0.0, L_2n * ds, L_12 * ds, L_h1 * ds]) L_drive = s_drive[-1] outputs["L_drive"] = L_drive # From Overhang input (dist from center of tower measured in yaw-aligned # c.s.-parallel to ground), compute bedplate length and height L_bedplate = L_overhang - (L_drive + 0.5 * D_hub) * np.cos(tilt) constr_Ldrive = L_bedplate - 0.5 * D_top # Should be > 0 if constr_Ldrive < 0: L_bedplate = 0.5 * D_top H_bedplate = H_drive - (L_drive + 0.5 * D_hub) * np.sin( tilt) # Keep eccentricity under control outputs["L_bedplate"] = L_bedplate outputs["H_bedplate"] = H_bedplate # Discretize the drivetrain from bedplate to hub s_mb1 = s_drive[4] s_mb2 = s_drive[2] s_rotor = s_drive[-2] s_stator = s_drive[1] s_nose = s_drive[:5] s_lss = s_drive[2:] # Store outputs # outputs['s_drive'] = np.sort(s_drive) outputs["s_rotor"] = s_rotor outputs["s_stator"] = s_stator outputs["s_nose"] = s_nose outputs["s_lss"] = s_lss outputs["s_generator"] = 0.5 * (s_rotor + s_stator) outputs["s_mb1"] = s_mb1 outputs["s_mb2"] = s_mb2 # ------------------------------------ # ------------ Bedplate geometry and coordinates ------------- # Define reference/centroidal axis # Origin currently set like standard ellipse eqns, but will shift below to being at tower top # The end point of 90 deg isn't exactly right for non-zero tilt, but leaving that for later n_points = 12 if upwind: rad = np.linspace(0.0, 0.5 * np.pi, n_points) else: rad = np.linspace(np.pi, 0.5 * np.pi, n_points) # Make sure we have the right number of bedplate thickness points t_bed = np.interp(rad, np.linspace(rad[0], rad[-1], len(t_bed)), t_bed) # Centerline x_c = L_bedplate * np.cos(rad) z_c = H_bedplate * np.sin(rad) # Points on the outermost ellipse x_outer = (L_bedplate + 0.5 * D_top) * np.cos(rad) z_outer = (H_bedplate + 0.5 * D_nose[0]) * np.sin(rad) # Points on the innermost ellipse x_inner = (L_bedplate - 0.5 * D_top) * np.cos(rad) z_inner = (H_bedplate - 0.5 * D_nose[0]) * np.sin(rad) # Cross-sectional properties D_bed = np.sqrt((z_outer - z_inner)**2 + (x_outer - x_inner)**2) r_bed_o = 0.5 * D_bed r_bed_i = r_bed_o - t_bed A_bed = np.pi * (r_bed_o**2 - r_bed_i**2) # This finds the central angle (rad2) given the parametric angle (rad) rad2 = np.arctan(L_bedplate / H_bedplate * np.tan(rad)) # arc length from eccentricity of the centroidal ellipse using incomplete elliptic integral of the second kind if L_bedplate >= H_bedplate: ecc = np.sqrt(1 - (H_bedplate / L_bedplate)**2) arc = L_bedplate * np.diff(ellipeinc(rad2, ecc)) else: ecc = np.sqrt(1 - (L_bedplate / H_bedplate)**2) arc = H_bedplate * np.diff(ellipeinc(rad2, ecc)) # Mass and MoI properties x_c_sec = util.nodal2sectional(x_c)[0] z_c_sec = util.nodal2sectional(z_c)[0] # R_c_sec = np.sqrt( x_c_sec**2 + z_c_sec**2 ) # unnecesary mass = util.nodal2sectional(A_bed)[0] * arc * bedplate_rho mass_tot = mass.sum() cm = np.array([np.sum(mass * x_c_sec), 0.0, np.sum(mass * z_c_sec)]) / mass_tot # For I, could do integral over sectional I, rotate axes by rad2, and then parallel axis theorem # we simplify by assuming lumped point mass. TODO: Find a good way to check this? Torus shell? I_bed = util.assembleI(np.zeros(6)) for k in range(len(mass)): r_bed_o_k = 0.5 * (r_bed_o[k] + r_bed_o[k + 1]) r_bed_i_k = 0.5 * (r_bed_i[k] + r_bed_i[k + 1]) I_sec = mass[k] * np.array([ 0.5 * (r_bed_o_k**2 + r_bed_i_k**2), (1.0 / 12.0) * (3 * (r_bed_o_k**2 + r_bed_i_k**2) + arc[k]**2), (1.0 / 12.0) * (3 * (r_bed_o_k**2 + r_bed_i_k**2) + arc[k]**2), ]) I_sec_rot = util.rotateI(I_sec, 0.5 * np.pi - rad2[k], axis="y") R_k = np.array([x_c_sec[k] - x_c[0], 0.0, z_c_sec[k]]) I_bed += util.assembleI(I_sec_rot) + mass[k] * ( np.dot(R_k, R_k) * np.eye(3) - np.outer(R_k, R_k)) # Now shift origin to be at tower top cm[0] -= x_c[0] x_inner -= x_c[0] x_outer -= x_c[0] x_c -= x_c[0] outputs["bedplate_mass"] = mass_tot outputs["bedplate_cm"] = cm outputs["bedplate_I"] = util.unassembleI(I_bed) # Geometry outputs outputs["x_bedplate"] = x_c outputs["z_bedplate"] = z_c outputs["x_bedplate_inner"] = x_inner outputs["z_bedplate_inner"] = z_inner outputs["x_bedplate_outer"] = x_outer outputs["z_bedplate_outer"] = z_outer outputs["D_bedplate"] = D_bed outputs["t_bedplate"] = t_bed # ------------------------------------ # ------- Constraints ---------------- outputs["constr_access"] = np.c_[D_lss - 2 * t_lss - D_nose - 0.25 * D_access, D_nose - 2 * t_nose - D_access] outputs["constr_length"] = constr_Ldrive # Should be > 0 outputs["constr_height"] = H_bedplate # Should be > 0 outputs["constr_ecc"] = L_bedplate - H_bedplate # Should be > 0 # ------------------------------------ # ------- Nose, lss, and bearing properties ---------------- # Now is a good time to set bearing diameters outputs["D_bearing1"] = D_lss[-1] - t_lss[-1] - D_nose[0] outputs["D_bearing2"] = D_lss[-1] - t_lss[-1] - D_nose[-1] # Compute center of mass based on area m_nose, cm_nose, I_nose = rod_prop(s_nose, D_nose, t_nose, bedplate_rho) outputs["nose_mass"] = m_nose outputs["nose_cm"] = cm_nose outputs["nose_I"] = I_nose m_lss, cm_lss, I_lss = rod_prop(s_lss, D_lss, t_lss, lss_rho) outputs["lss_mass"] = m_lss outputs["lss_cm"] = cm_lss outputs["lss_I"] = I_lss
def compute(self, inputs, outputs): # Unpack variables for thickness and average radius at each can interface twall = inputs["t_full"] Rb = 0.5 * inputs["d_full"][:-1] Rt = 0.5 * inputs["d_full"][1:] zz = inputs["z_full"] H = np.diff(zz) rho = inputs["rho"] coeff = inputs["outfitting_factor"] coeff = coeff + np.where(coeff < 1.0, 1.0, 0.0) # Total mass of cylinder V_shell = frustum.frustumShellVol(Rb, Rt, twall, H) mass = outputs["mass"] = coeff * rho * V_shell # Center of mass of each can/section cm_section = zz[:-1] + frustum.frustumShellCG(Rb, Rt, twall, H) outputs["section_center_of_mass"] = cm_section # Center of mass of cylinder V_shell += eps outputs["center_of_mass"] = np.dot(V_shell, cm_section) / V_shell.sum() # Moments of inertia Izz_section = coeff * rho * frustum.frustumShellIzz(Rb, Rt, twall, H) Ixx_section = Iyy_section = coeff * rho * frustum.frustumShellIxx(Rb, Rt, twall, H) # Sum up each cylinder section using parallel axis theorem I_base = np.zeros((3, 3)) for k in range(Izz_section.size): R = np.array([0.0, 0.0, cm_section[k] - zz[0]]) Icg = util.assembleI([Ixx_section[k], Iyy_section[k], Izz_section[k], 0.0, 0.0, 0.0]) I_base += Icg + mass[k] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) outputs["I_base"] = util.unassembleI(I_base) # Compute costs based on "Optimum Design of Steel Structures" by Farkas and Jarmai # All dimensions for correlations based on mm, not meters. R_ave = 0.5 * (Rb + Rt) taper = np.minimum(Rb / Rt, Rt / Rb) nsec = twall.size mshell = rho * V_shell mshell_tot = np.sum(rho * V_shell) k_m = inputs["material_cost_rate"] # 1.1 # USD / kg carbon steel plate k_f = inputs["labor_cost_rate"] # 1.0 # USD / min labor k_p = inputs["painting_cost_rate"] # USD / m^2 painting k_e = 0.064 # Industrial electricity rate $/kWh https://www.eia.gov/electricity/monthly/epm_table_grapher.php?t=epmt_5_6_a e_f = 15.9 # Electricity usage kWh/kg for steel e_fo = 26.9 # Electricity usage kWh/kg for stainless steel # Cost Step 1) Cutting flat plates for taper using plasma cutter cutLengths = 2.0 * np.sqrt((Rt - Rb) ** 2.0 + H ** 2.0) # Factor of 2 for both sides # Cost Step 2) Rolling plates # Cost Step 3) Welding rolled plates into shells (set difficulty factor based on tapering with logistic function) theta_F = 4.0 - 3.0 / (1 + np.exp(-5.0 * (taper - 0.75))) # Cost Step 4) Circumferential welds to join cans together theta_A = 2.0 # Labor-based expenses K_f = k_f * ( manufacture.steel_cutting_plasma_time(cutLengths, twall) + manufacture.steel_rolling_time(theta_F, R_ave, twall) + manufacture.steel_butt_welding_time(theta_A, nsec, mshell_tot, cutLengths, twall) + manufacture.steel_butt_welding_time(theta_A, nsec, mshell_tot, 2 * np.pi * Rb[1:], twall[1:]) ) # Cost step 5) Painting- outside and inside theta_p = 2 K_p = k_p * theta_p * 2 * (2 * np.pi * R_ave * H).sum() # Cost step 6) Outfitting with electricity usage K_o = np.sum(1.5 * k_m * (coeff - 1.0) * mshell) # Material cost with waste fraction, but without outfitting, K_m = 1.21 * np.sum(k_m * mshell) # Electricity usage K_e = np.sum(k_e * (e_f * mshell + e_fo * (coeff - 1.0) * mshell)) # Assemble all costs for now tempSum = K_m + K_e + K_o + K_p + K_f # Capital cost share from BLS MFP by NAICS K_c = 0.118 * tempSum / (1.0 - 0.118) outputs["cost"] = tempSum + K_c
def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): Cup = -1.0 if discrete_inputs["upwind"] else 1.0 tilt = float(np.deg2rad(inputs["tilt"])) components = [ "mb1", "mb2", "lss", "hss", "brake", "gearbox", "generator_rotor", "generator_stator", "generator", "hvac", "nose", "bedplate", "platform", "cover", ] if discrete_inputs["uptower"]: components.extend(["transformer", "converter"]) # Mass and CofM summaries first because will need them for I later m_nac = 0.0 cm_nac = np.zeros(3) shaft0 = np.zeros(3) shaft0[-1] += inputs["constr_height"] if self.options["direct_drive"]: shaft0[0] += inputs["x_bedplate"][-1] outputs["shaft_start"] = shaft0 for k in components: if k in ["generator_rotor", "generator_stator"]: continue m_i = inputs[k + "_mass"] cm_i = inputs[k + "_cm"] # If cm is (x,y,z) then it is already in tower-top c.s. If it is a scalar, it is in distance from tower and we have to convert if len(cm_i) == 1: cm_i = shaft0 + cm_i * np.array( [Cup * np.cos(tilt), 0.0, np.sin(tilt)]) m_nac += m_i cm_nac += m_i * cm_i # Complete CofM calculation cm_nac /= m_nac # Now find total I about nacelle CofM I_nac = np.zeros(6) m_list = np.zeros((len(components) + 3, )) cm_list = np.zeros((len(components) + 3, 3)) I_cm_list = np.zeros((len(components) + 3, 6)) I_TT_list = np.zeros((len(components) + 3, 6)) for ic, c in enumerate(components): m_i = inputs[c + "_mass"] cm_i = inputs["generator_cm"] if c.find( "generator") >= 0 else inputs[c + "_cm"] I_i = inputs[c + "_I"] # Rotate MofI if in hub c.s. if len(cm_i) == 1: cm_i = shaft0 + cm_i * np.array( [Cup * np.cos(tilt), 0.0, np.sin(tilt)]) I_i = util.rotateI(I_i, -Cup * tilt, axis="y") else: I_i = np.r_[I_i, np.zeros(3)] r = cm_i - cm_nac if not c in ["generator_rotor", "generator_stator"]: I_add = util.assembleI(I_i) + m_i * (np.dot(r, r) * np.eye(3) - np.outer(r, r)) I_add = util.unassembleI(I_add) I_nac += I_add # Record mass, cm, and I for output table m_list[ic] = m_i cm_list[ic, :] = cm_i I_TT_list[ic, :] = util.unassembleI( util.assembleI(I_i) + m_i * (np.dot(cm_i, cm_i) * np.eye(3) - np.outer(cm_i, cm_i))) I_i = inputs[c + "_I"] I_cm_list[ic, :] = I_i if I_i.size == 6 else np.r_[I_i, np.zeros(3)] outputs["above_yaw_mass"] = copy.copy(m_nac) outputs["above_yaw_cm"] = R = cm_nac.copy() outputs["above_yaw_I"] = I_nac.copy() parallel_axis = m_nac * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) outputs["above_yaw_I_TT"] = util.unassembleI( util.assembleI(I_nac) + parallel_axis) m_nac += inputs["yaw_mass"] cm_nac = (outputs["above_yaw_mass"] * outputs["above_yaw_cm"] + inputs["yaw_cm"] * inputs["yaw_mass"]) / m_nac r = inputs["yaw_cm"] - cm_nac I_add = util.assembleI( np.r_[inputs["yaw_I"], np.zeros(3)]) + inputs["yaw_mass"] * ( np.dot(r, r) * np.eye(3) - np.outer(r, r)) I_add = util.unassembleI(I_add) I_nac += I_add outputs["nacelle_mass"] = m_nac outputs["nacelle_cm"] = cm_nac outputs["nacelle_I"] = I_nac # Find nacelle MoI about tower top R = cm_nac parallel_axis = m_nac * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) outputs["nacelle_I_TT"] = util.unassembleI( util.assembleI(I_nac) + parallel_axis) # Store other misc outputs outputs["other_mass"] = (inputs["hvac_mass"] + inputs["platform_mass"] + inputs["cover_mass"] + inputs["yaw_mass"] + inputs["converter_mass"] + inputs["transformer_mass"]) outputs["mean_bearing_mass"] = 0.5 * (inputs["mb1_mass"] + inputs["mb2_mass"]) outputs["total_bedplate_mass"] = inputs["nose_mass"] + inputs[ "bedplate_mass"] # Wrap up nacelle mass table components.append("Above_yaw") m_list[-3] = outputs["above_yaw_mass"] cm_list[-3, :] = outputs["above_yaw_cm"] I_cm_list[-3, :] = outputs["above_yaw_I"] I_TT_list[-3, :] = outputs["above_yaw_I_TT"] components.append("yaw") m_list[-2] = inputs["yaw_mass"] cm_list[-2, :] = inputs["yaw_cm"] I_cm_list[-2, :] = I_TT_list[-2, :] = np.r_[inputs["yaw_I"], np.zeros(3)] components.append("nacelle") m_list[-1] = m_nac cm_list[-1, :] = cm_nac I_cm_list[-1, :] = I_nac I_TT_list[-1, :] = outputs["nacelle_I_TT"] self._mass_table = pd.DataFrame() self._mass_table["Component"] = components self._mass_table["Mass"] = m_list self._mass_table["CoM_TT_x"] = cm_list[:, 0] self._mass_table["CoM_TT_y"] = cm_list[:, 1] self._mass_table["CoM_TT_z"] = cm_list[:, 2] self._mass_table["MoI_CoM_xx"] = I_cm_list[:, 0] self._mass_table["MoI_CoM_yy"] = I_cm_list[:, 1] self._mass_table["MoI_CoM_zz"] = I_cm_list[:, 2] self._mass_table["MoI_CoM_xy"] = I_cm_list[:, 3] self._mass_table["MoI_CoM_xz"] = I_cm_list[:, 4] self._mass_table["MoI_CoM_yz"] = I_cm_list[:, 5] self._mass_table["MoI_TT_xx"] = I_TT_list[:, 0] self._mass_table["MoI_TT_yy"] = I_TT_list[:, 1] self._mass_table["MoI_TT_zz"] = I_TT_list[:, 2] self._mass_table["MoI_TT_xy"] = I_TT_list[:, 3] self._mass_table["MoI_TT_xz"] = I_TT_list[:, 4] self._mass_table["MoI_TT_yz"] = I_TT_list[:, 5] self._mass_table.set_index("Component", inplace=True)
def runMAP(self, inputs, discrete_inputs, outputs): """Writes MAP input file, executes, and then queries MAP to find maximum loading and displacement from vessel displacement around all 360 degrees INPUTS: ---------- inputs : dictionary of input parameters outputs : dictionary of output parameters OUTPUTS : none (multiple unknown dictionary values set) """ # Unpack variables rhoWater = inputs['water_density'] waterDepth = inputs['water_depth'] fairleadDepth = inputs['fairlead'] Dmooring = inputs['mooring_diameter'] offset = inputs['max_offset'] heel = inputs['operational_heel'] gamma = inputs['gamma_f'] n_connect = int(inputs['number_of_mooring_connections']) n_lines = int(inputs['mooring_lines_per_connection']) ntotal = n_connect * n_lines # Write the mooring system input file for this design self.write_input_file(inputs, discrete_inputs) # Initiate MAP++ for this design mymap = pyMAP() #mymap.ierr = 0 mymap.map_set_sea_depth(waterDepth) mymap.map_set_gravity(gravity) mymap.map_set_sea_density(rhoWater) mymap.read_list_input(self.finput) mymap.init() # Get the stiffness matrix at neutral position mymap.displace_vessel(0, 0, 0, 0, 0, 0) mymap.update_states(0.0, 0) K = mymap.linear(1e-4) # Input finite difference epsilon outputs['mooring_stiffness'] = np.array(K) mymap.displace_vessel(0, 0, 0, 0, 0, 0) mymap.update_states(0.0, 0) # Get the vertical load on the structure and plotting data F_neutral = np.zeros((NLINES_MAX, 3)) plotMat = np.zeros((NLINES_MAX, NPTS_PLOT, 3)) nptsMOI = 100 xyzpts = np.zeros((ntotal, nptsMOI, 3)) # For MOI calculation for k in range(ntotal): (F_neutral[k, 0], F_neutral[k, 1], F_neutral[k, 2]) = mymap.get_fairlead_force_3d(k) plotMat[k, :, 0] = mymap.plot_x(k, NPTS_PLOT) plotMat[k, :, 1] = mymap.plot_y(k, NPTS_PLOT) plotMat[k, :, 2] = mymap.plot_z(k, NPTS_PLOT) xyzpts[k, :, 0] = mymap.plot_x(k, nptsMOI) xyzpts[k, :, 1] = mymap.plot_y(k, nptsMOI) xyzpts[k, :, 2] = mymap.plot_z(k, nptsMOI) if self.tlpFlag: # Seems to be a bug in the plot arrays from MAP++ for plotting output with taut lines plotMat[k, :, 2] = np.linspace(-fairleadDepth, -waterDepth, NPTS_PLOT) xyzpts[k, :, 2] = np.linspace(-fairleadDepth, -waterDepth, nptsMOI) outputs['mooring_neutral_load'] = F_neutral outputs['mooring_plot_matrix'] = plotMat # Fine line segment length, ds = sqrt(dx^2 + dy^2 + dz^2) xyzpts_dx = np.gradient(xyzpts[:, :, 0], axis=1) xyzpts_dy = np.gradient(xyzpts[:, :, 1], axis=1) xyzpts_dz = np.gradient(xyzpts[:, :, 2], axis=1) xyzpts_ds = np.sqrt(xyzpts_dx**2 + xyzpts_dy**2 + xyzpts_dz**2) # Initialize inertia tensor integrands in https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor # Taking MOI relative to body centerline at fairlead depth r0 = np.array([0.0, 0.0, -fairleadDepth]) R = np.zeros((ntotal, nptsMOI, 6)) for ii in range(nptsMOI): for k in range(ntotal): r = xyzpts[k, ii, :] - r0 R[k, ii, :] = unassembleI( np.dot(r, r) * np.eye(3) - np.outer(r, r)) Imat = self.wet_mass_per_length * np.trapz( R, x=xyzpts_ds[:, :, np.newaxis], axis=1) outputs['mooring_moments_of_inertia'] = np.abs(Imat.sum(axis=0)) # Get the restoring moment at maximum angle of heel # Since we don't know the substucture CG, have to just get the forces of the lines now and do the cross product later # We also want to allow for arbitraty wind direction and yaw of rotor relative to mooring lines, so we will compare # pitch and roll forces as extremes # TODO: This still isgn't quite the same as clocking the mooring lines in different directions, # which is what we want to do, but that requires multiple input files and solutions Fh = np.zeros((NLINES_MAX, 3)) mymap.displace_vessel(0, 0, 0, 0, heel, 0) mymap.update_states(0.0, 0) for k in range(ntotal): Fh[k][0], Fh[k][1], Fh[k][2] = mymap.get_fairlead_force_3d(k) outputs['operational_heel_restoring_force'] = Fh # Get angles by which to find the weakest line dangle = 2.0 angles = np.deg2rad(np.arange(0.0, 360.0, dangle)) nangles = len(angles) # Get restoring force at weakest line at maximum allowable offset # Will global minimum always be along mooring angle? max_tension = 0.0 max_angle = None min_angle = None F_max_tension = None F_min = np.inf T = np.zeros((NLINES_MAX, )) F = np.zeros((NLINES_MAX, )) # Loop around all angles to find weakest point for a in angles: # Unit vector and offset in x-y components idir = np.array([np.cos(a), np.sin(a)]) surge = offset * idir[0] sway = offset * idir[1] # Get restoring force of offset at this angle mymap.displace_vessel(surge, sway, 0, 0, 0, 0) # 0s for z, angles mymap.update_states(0.0, 0) for k in range(ntotal): # Force in x-y-z coordinates fx, fy, fz = mymap.get_fairlead_force_3d(k) T[k] = np.sqrt(fx * fx + fy * fy + fz * fz) # Total restoring force F[k] = np.dot([fx, fy], idir) # Check if this is the weakest direction (highest tension) tempMax = T.max() if tempMax > max_tension: max_tension = tempMax F_max_tension = F.sum() max_angle = a if F.sum() < F_min: F_min = F.sum() min_angle = a # Store the weakest restoring force when the vessel is offset the maximum amount outputs['max_offset_restoring_force'] = F_min # Check for good convergence if (plotMat[0, -1, -1] + fairleadDepth) > 1.0: outputs['axial_unity'] = 1e30 else: outputs['axial_unity'] = gamma * max_tension / self.min_break_load mymap.end()
def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): Cup = -1.0 if discrete_inputs["upwind"] else 1.0 tilt = float(np.deg2rad(inputs["tilt"])) components = [ "mb1", "mb2", "lss", "hss", "brake", "gearbox", "generator", "hvac", "nose", "bedplate", "platform", "cover", ] if discrete_inputs["uptower"]: components.extend(["transformer", "converter"]) # Mass and CofM summaries first because will need them for I later m_nac = 0.0 cm_nac = np.zeros(3) for k in components: m_i = inputs[k + "_mass"] cm_i = inputs[k + "_cm"] # If cm is (x,y,z) then it is already in tower-top c.s. If it is a scalar, it is in distance from tower and we have to convert if len(cm_i) == 1: cm_i = cm_i * np.array([Cup * np.cos(tilt), 0.0, np.sin(tilt)]) m_nac += m_i cm_nac += m_i * cm_i # Complete CofM calculation cm_nac /= m_nac # Now find total I about nacelle CofM I_nac = np.zeros(6) m_list = np.zeros((len(components) + 1, )) cm_list = np.zeros((len(components) + 1, 3)) I_list = np.zeros((len(components) + 1, 6)) for ic, c in enumerate(components): m_i = inputs[c + "_mass"] cm_i = inputs[c + "_cm"] I_i = inputs[c + "_I"] # Rotate MofI if in hub c.s. if len(cm_i) == 1: cm_i = cm_i * np.array([Cup * np.cos(tilt), 0.0, np.sin(tilt)]) I_i = util.rotateI(I_i, -Cup * tilt, axis="y") else: I_i = np.r_[I_i, np.zeros(3)] r = cm_i - cm_nac I_add = util.assembleI(I_i) + m_i * (np.dot(r, r) * np.eye(3) - np.outer(r, r)) I_add = util.unassembleI(I_add) I_nac += I_add m_list[ic] = m_i cm_list[ic, :] = cm_i I_list[ic, :] = I_add outputs["above_yaw_mass"] = copy.copy(m_nac) outputs["above_yaw_cm"] = copy.copy(cm_nac) outputs["above_yaw_I"] = copy.copy(I_nac) components.append("yaw") m_nac += inputs["yaw_mass"] cm_nac = (outputs["above_yaw_mass"] * outputs["above_yaw_cm"] + inputs["yaw_cm"] * inputs["yaw_mass"]) / m_nac r = inputs["yaw_cm"] - cm_nac I_add = util.assembleI( np.r_[inputs["yaw_I"], np.zeros(3)]) + inputs["yaw_mass"] * ( np.dot(r, r) * np.eye(3) - np.outer(r, r)) I_add = util.unassembleI(I_add) I_nac += I_add # Wrap up nacelle mass table m_list[-1] = inputs["yaw_mass"] cm_list[-1, :] = inputs["yaw_cm"] I_list[-1, :] = I_add self._mass_table = pd.DataFrame() self._mass_table["Component"] = components self._mass_table["Mass"] = m_list self._mass_table["CoM_x"] = cm_list[:, 0] self._mass_table["CoM_y"] = cm_list[:, 1] self._mass_table["CoM_z"] = cm_list[:, 2] self._mass_table["MoI_xx"] = I_list[:, 0] self._mass_table["MoI_yy"] = I_list[:, 1] self._mass_table["MoI_zz"] = I_list[:, 2] self._mass_table["MoI_xy"] = I_list[:, 3] self._mass_table["MoI_xz"] = I_list[:, 4] self._mass_table["MoI_yz"] = I_list[:, 5] self._mass_table.set_index("Component") self._mass_table.loc["Total"] = self._mass_table.sum() outputs["nacelle_mass"] = m_nac outputs["nacelle_cm"] = cm_nac outputs["nacelle_I"] = I_nac outputs["other_mass"] = (inputs["hvac_mass"] + inputs["platform_mass"] + inputs["cover_mass"] + inputs["yaw_mass"] + inputs["converter_mass"] + inputs["transformer_mass"]) outputs["mean_bearing_mass"] = 0.5 * (inputs["mb1_mass"] + inputs["mb2_mass"]) outputs["total_bedplate_mass"] = inputs["nose_mass"] + inputs[ "bedplate_mass"]
def compute(self, inputs, outputs): # Unpack variables for thickness and average radius at each can interface twall = inputs['t_full'] Rb = 0.5 * inputs['d_full'][:-1] Rt = 0.5 * inputs['d_full'][1:] zz = inputs['z_full'] H = np.diff(zz) rho = inputs['material_density'] coeff = inputs['outfitting_factor'] if coeff < 1.0: coeff += 1.0 # Total mass of cylinder V_shell = frustum.frustumShellVol(Rb, Rt, twall, H) outputs['mass'] = coeff * rho * V_shell # Center of mass of each can/section cm_section = zz[:-1] + frustum.frustumShellCG(Rb, Rt, twall, H) outputs['section_center_of_mass'] = cm_section # Center of mass of cylinder V_shell += eps outputs['center_of_mass'] = np.dot(V_shell, cm_section) / V_shell.sum() # Moments of inertia Izz_section = frustum.frustumShellIzz(Rb, Rt, twall, H) Ixx_section = Iyy_section = frustum.frustumShellIxx(Rb, Rt, twall, H) # Sum up each cylinder section using parallel axis theorem I_base = np.zeros((3, 3)) for k in range(Izz_section.size): R = np.array([0.0, 0.0, cm_section[k]]) Icg = assembleI([ Ixx_section[k], Iyy_section[k], Izz_section[k], 0.0, 0.0, 0.0 ]) I_base += Icg + V_shell[k] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) # All of the mass and volume terms need to be multiplied by density I_base *= coeff * rho outputs['I_base'] = unassembleI(I_base) # Compute costs based on "Optimum Design of Steel Structures" by Farkas and Jarmai # All dimensions for correlations based on mm, not meters. R_ave = 0.5 * (Rb + Rt) taper = np.minimum(Rb / Rt, Rt / Rb) nsec = twall.size mplate = rho * V_shell.sum() k_m = inputs['material_cost_rate'] #1.1 # USD / kg carbon steel plate k_f = inputs['labor_cost_rate'] #1.0 # USD / min labor k_p = inputs['painting_cost_rate'] #USD / m^2 painting # Cost Step 1) Cutting flat plates for taper using plasma cutter cutLengths = 2.0 * np.sqrt( (Rt - Rb)**2.0 + H**2.0) # Factor of 2 for both sides # Cost Step 2) Rolling plates # Cost Step 3) Welding rolled plates into shells (set difficulty factor based on tapering with logistic function) theta_F = 4.0 - 3.0 / (1 + np.exp(-5.0 * (taper - 0.75))) # Cost Step 4) Circumferential welds to join cans together theta_A = 2.0 # Labor-based expenses K_f = k_f * (manufacture.steel_cutting_plasma_time(cutLengths, twall) + manufacture.steel_rolling_time(theta_F, R_ave, twall) + manufacture.steel_butt_welding_time( theta_A, nsec, mplate, cutLengths, twall) + manufacture.steel_butt_welding_time( theta_A, nsec, mplate, 2 * np.pi * Rb[1:], twall[1:])) # Cost step 5) Painting- outside and inside theta_p = 2 K_p = k_p * theta_p * 2 * (2 * np.pi * R_ave * H).sum() # Cost step 6) Outfitting K_o = 1.5 * k_m * (coeff - 1.0) * mplate # Material cost, without outfitting K_m = k_m * mplate # Assemble all costs outputs['cost'] = K_m + K_o + K_p + K_f
def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): opt = self.options['modeling_options'] # Frequencies to calculate outputs['frequency_range'] = np.array(opt['Level1']['frequencies']) # Tower layer sections outputs['turbine_tower_t'] = inputs['tower_layer_thickness'].sum( axis=0) outputs['turbine_tower_rho_shell'] = inputs['tower_rho'].mean() k_tow_tor = inputs['tower_torsional_stiffness'] / inputs[ 'tower_section_height'] outputs['turbine_yaw_stiffness'] = 1.0 / np.sum(1.0 / k_tow_tor) # Tower drag Re = float(inputs['rho_air'] ) * inputs['tower_U'] * inputs['turbine_tower_d'] / float( inputs['mu_air']) cd, _ = cylinderDrag(Re) outputs['turbine_tower_Cd'] = cd # Move tower-top MoI to hub height m_rna = float(inputs['turbine_mRNA']) I_rna = util.assembleI(inputs['rna_I_TT']) # Vector from WISDEM tower-top c.s. to raft tower center-line at hub height c.s. r = np.r_[0.0, 0.0, float(inputs['drive_height'])] outputs['turbine_xCG_RNA'] = (inputs['rna_cm'] - r)[0] I_rna_raft = util.unassembleI( I_rna + m_rna * (np.dot(r, r) * np.eye(3) - np.outer(r, r))) outputs['turbine_IxRNA'] = I_rna_raft[0] outputs['turbine_IrRNA'] = 0.5 * (I_rna_raft[1] + I_rna_raft[2]) # Floating member data structure transfer n_member = len(opt["floating"]["members"]["name"]) var_height = inputs['member_variable_height'] for k in range(n_member): discrete_outputs[f"platform_member{k+1}_potMod"] = opt["Level1"][ "model_potential"][k] # Member thickness outputs[f"platform_member{k+1}_t"] = inputs[ f"member{k}:layer_thickness"].sum(axis=0) outputs[f"platform_member{k+1}_rho_shell"] = inputs[ f"member{k}:rho"].mean() # Ring stiffener discretization conversion if float(inputs[f"member{k}:ring_stiffener_spacing"]) > 0.0: outputs[f"platform_member{k+1}_ring_spacing"] = ( inputs[f"member{k}:ring_stiffener_spacing"] / inputs[f"member{k}:height"]) h_web = inputs[f"member{k}:ring_stiffener_web_height"] t_web = inputs[f"member{k}:ring_stiffener_web_thickness"] t_flange = inputs[f"member{k}:ring_stiffener_flange_thickness"] w_flange = inputs[f"member{k}:ring_stiffener_flange_width"] outputs[f"platform_member{k+1}_ring_h"] = h_web + t_flange outputs[f"platform_member{k+1}_ring_t"] = ( t_web * h_web + w_flange * t_flange) / (h_web + t_flange) # Ballast discretization conversion s_grid = inputs[f"platform_member{k+1}_stations"] s_ballast = inputs[f"member{k}:ballast_grid"] rho_ballast = inputs[f"member{k}:ballast_density"] h_ballast = inputs[f"member{k}:ballast_height"] l_fill = np.zeros(s_grid.size - 1) rho_fill = np.zeros(s_grid.size - 1) for ii in range(s_ballast.shape[0]): iball = np.where(s_ballast[ii, 0] >= s_grid)[0][0] rho_fill[iball] = rho_ballast[ii] l_fill[iball] = h_ballast[ ii] if rho_ballast[ii] < 1100.0 else var_height[k] outputs[f"platform_member{k+1}_l_fill"] = l_fill outputs[f"platform_member{k+1}_rho_fill"] = rho_fill # Mooring for k in range(opt['mooring']['n_nodes']): discrete_outputs[f'mooring_point{k+1}_name'] = opt['mooring'][ 'node_names'][k] discrete_outputs[f'mooring_point{k+1}_type'] = opt['mooring'][ 'node_type'][k] outputs[f'mooring_point{k+1}_location'] = inputs['mooring_nodes'][ k, :] for k in range(opt['mooring']['n_lines']): discrete_outputs[f'mooring_line{k+1}_endA'] = opt['mooring'][ "node1"][k] discrete_outputs[f'mooring_line{k+1}_endB'] = opt['mooring'][ "node2"][k] discrete_outputs[f'mooring_line{k+1}_type'] = opt['mooring'][ "line_type"][k] outputs[f'mooring_line{k+1}_length'] = inputs[ 'unstretched_length'][k] for k in range(opt['mooring']['n_line_types']): discrete_outputs[f'mooring_line_type{k+1}_name'] = opt['mooring'][ "line_type_name"][k] outputs[f'mooring_line_type{k+1}_cost'] = inputs['line_cost_rate'][ k] for var in [ 'diameter', 'mass_density', 'stiffness', 'breaking_load', 'transverse_added_mass', 'tangential_added_mass', 'transverse_drag', 'tangential_drag' ]: outputs[f'mooring_line_type{k+1}_{var}'] = inputs[ f'line_{var}'][k]
def set_element_props(self, inputs, outputs): # Load in number of members opt = self.options["options"] n_member = opt["floating"]["members"]["n_members"] # Initialize running lists across all members elem_D = np.array([]) elem_t = np.array([]) elem_A = np.array([]) elem_Asx = np.array([]) elem_Asy = np.array([]) elem_Ixx = np.array([]) elem_Iyy = np.array([]) elem_Izz = np.array([]) elem_rho = np.array([]) elem_E = np.array([]) elem_G = np.array([]) mass = 0.0 cost = 0.0 volume = 0.0 Awater = 0.0 Iwater = 0.0 m_added = np.zeros(6) cg_plat = np.zeros(3) cb_plat = np.zeros(3) # Append all member data for k in range(n_member): n = np.where(inputs[f"member{k}:section_A"] == NULL)[0][0] elem_D = np.append(elem_D, inputs[f"member{k}:section_D"][:n]) elem_t = np.append(elem_t, inputs[f"member{k}:section_t"][:n]) elem_A = np.append(elem_A, inputs[f"member{k}:section_A"][:n]) elem_Asx = np.append(elem_Asx, inputs[f"member{k}:section_Asx"][:n]) elem_Asy = np.append(elem_Asy, inputs[f"member{k}:section_Asy"][:n]) elem_Ixx = np.append(elem_Ixx, inputs[f"member{k}:section_Ixx"][:n]) elem_Iyy = np.append(elem_Iyy, inputs[f"member{k}:section_Iyy"][:n]) elem_Izz = np.append(elem_Izz, inputs[f"member{k}:section_Izz"][:n]) elem_rho = np.append(elem_rho, inputs[f"member{k}:section_rho"][:n]) elem_E = np.append(elem_E, inputs[f"member{k}:section_E"][:n]) elem_G = np.append(elem_G, inputs[f"member{k}:section_G"][:n]) # Mass, volume, cost tallies imass = inputs[f"member{k}:total_mass"] ivol = inputs[f"member{k}:displacement"] mass += imass volume += ivol cost += inputs[f"member{k}:total_cost"] Awater += inputs[f"member{k}:Awater"] Iwater += inputs[f"member{k}:Iwater"] m_added += inputs[f"member{k}:added_mass"] # Center of mass / buoyancy tallies cg_plat += imass * inputs[f"member{k}:center_of_mass"] cb_plat += ivol * inputs[f"member{k}:center_of_buoyancy"] # Finalize outputs cg_plat /= mass cb_plat /= volume # With CG known, loop back through to compute platform I unit_z = np.array([0.0, 0.0, 1.0]) I_total = np.zeros((3, 3)) for k in range(n_member): xyz_k = inputs[f"member{k}:nodes_xyz"] inodes = np.where(xyz_k[:, 0] == NULL)[0][0] xyz_k = xyz_k[:inodes, :] imass = inputs[f"member{k}:total_mass"] cg_k = inputs[f"member{k}:center_of_mass"] R = cg_plat - cg_k # Figure out angle to make member parallel to global c.s. vec_k = xyz_k[-1, :] - xyz_k[0, :] T = util.rotate_align_vectors(vec_k, unit_z) # Rotate member inertia tensor I_k = util.assembleI(inputs[f"member{k}:I_total"]) I_k2 = T * np.asmatrix(I_k) * T.T # Now do parallel axis theorem I_total += np.array(I_k2) + imass * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) # Store outputs nelem = elem_A.size outputs["platform_elem_D"] = NULL * np.ones(NELEM_MAX) outputs["platform_elem_t"] = NULL * np.ones(NELEM_MAX) outputs["platform_elem_A"] = NULL * np.ones(NELEM_MAX) outputs["platform_elem_Asx"] = NULL * np.ones(NELEM_MAX) outputs["platform_elem_Asy"] = NULL * np.ones(NELEM_MAX) outputs["platform_elem_Ixx"] = NULL * np.ones(NELEM_MAX) outputs["platform_elem_Iyy"] = NULL * np.ones(NELEM_MAX) outputs["platform_elem_Izz"] = NULL * np.ones(NELEM_MAX) outputs["platform_elem_rho"] = NULL * np.ones(NELEM_MAX) outputs["platform_elem_E"] = NULL * np.ones(NELEM_MAX) outputs["platform_elem_G"] = NULL * np.ones(NELEM_MAX) outputs["platform_elem_D"][:nelem] = elem_D outputs["platform_elem_t"][:nelem] = elem_t outputs["platform_elem_A"][:nelem] = elem_A outputs["platform_elem_Asx"][:nelem] = elem_Asx outputs["platform_elem_Asy"][:nelem] = elem_Asy outputs["platform_elem_Ixx"][:nelem] = elem_Ixx outputs["platform_elem_Iyy"][:nelem] = elem_Iyy outputs["platform_elem_Izz"][:nelem] = elem_Izz outputs["platform_elem_rho"][:nelem] = elem_rho outputs["platform_elem_E"][:nelem] = elem_E outputs["platform_elem_G"][:nelem] = elem_G outputs["platform_mass"] = mass outputs["platform_cost"] = cost outputs["platform_displacement"] = volume outputs["platform_center_of_mass"] = cg_plat outputs["platform_center_of_buoyancy"] = cb_plat outputs["platform_I_total"] = util.unassembleI(I_total) outputs["platform_Awater"] = Awater outputs["platform_Iwater"] = Iwater outputs["platform_added_mass"] = m_added