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 testRotateI(self): I = np.arange(6) + 1 th = np.deg2rad(45) Irot = util.rotateI(I, th, axis="z") npt.assert_almost_equal( Irot, np.array([-2.5, 5.5, 3, -0.5, -0.5 * np.sqrt(2), 5.5 * np.sqrt(2)]))
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, 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 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"]