def solve_nonlinear(self, params, unknowns, resids): unknowns['turbine_mass'] = params['rna_mass'] + params['tower_mass'] cg_rna = params['rna_cg'] + np.array([0.0, 0.0, params['hubH']]) cg_tower = np.array([0.0, 0.0, params['tower_center_of_mass']]) unknowns['turbine_center_of_mass'] = (params['rna_mass']*cg_rna + params['tower_mass']*cg_tower) / unknowns['turbine_mass'] R = cg_rna I_tower = assembleI(params['tower_I_base']) I_rna = assembleI(params['rna_I']) + params['rna_mass']*(np.dot(R, R)*np.eye(3) - np.outer(R, R)) unknowns['turbine_I_base'] = unassembleI(I_tower + I_rna)
def solve_nonlinear(self, params, unknowns, resids): # Unpack variables z_full = params['z_full'] # at section nodes z_param = params['z_param'] R_od = 0.5 * params['d_full'] # at section nodes twall = params['t_full'] # at section nodes t_bulk = params['bulkhead_thickness'] # at section nodes # Map bulkhead locations to finer computation grid Zf, Zp = np.meshgrid(z_full, z_param) idx = np.argmin(np.abs(Zf - Zp), axis=1) t_bulk_full = np.zeros(z_full.shape) t_bulk_full[idx] = t_bulk # Compute bulkhead volume at every section node # Assume bulkheads are same thickness as shell wall V_bulk = np.pi * (R_od - twall)**2 * t_bulk_full # Convert to mass with fudge factor for design features not captured in this simple approach m_bulk = params['bulkhead_mass_factor'] * params['rho'] * V_bulk # Compute moments of inertia at keel # Assume bulkheads are just simple thin discs with radius R_od-t_wall and mass already computed Izz = 0.5 * m_bulk * (R_od - twall)**2 Ixx = Iyy = 0.5 * Izz I_keel = np.zeros((3, 3)) dz = z_full - z_full[0] for k in xrange(m_bulk.size): R = np.array([0.0, 0.0, dz[k]]) Icg = assembleI([Ixx[k], Iyy[k], Izz[k], 0.0, 0.0, 0.0]) I_keel += Icg + m_bulk[k] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) # Store results unknowns['bulkhead_I_keel'] = unassembleI(I_keel) unknowns['bulkhead_mass'] = m_bulk
def runMAP(self, params, unknowns): """Writes MAP input file, executes, and then queries MAP to find maximum loading and displacement from vessel displacement around all 360 degrees INPUTS: ---------- params : dictionary of input parameters unknowns : dictionary of output parameters OUTPUTS : none (multiple unknown dictionary values set) """ # Unpack variables rhoWater = params['water_density'] waterDepth = params['water_depth'] fairleadDepth = params['fairlead'] Dmooring = params['mooring_diameter'] offset = params['max_offset'] heel = params['operational_heel'] gamma = params['gamma_f'] n_connect = int(params['number_of_mooring_connections']) n_lines = int(params['mooring_lines_per_connection']) ntotal = n_connect * n_lines # Write the mooring system input file for this design self.write_input_file(params) # 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 unknowns['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) unknowns['mooring_neutral_load'] = F_neutral unknowns['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) unknowns['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) unknowns['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 unknowns['max_offset_restoring_force'] = F_min # Check for good convergence if (plotMat[0, -1, -1] + fairleadDepth) > 1.0: unknowns['axial_unity'] = 1e30 else: unknowns['axial_unity'] = gamma * max_tension / self.min_break_load mymap.end()
def solve_nonlinear(self, params, unknowns, resids): # Unpack variables R_od, _ = nodal2sectional(params['d_full']) # at section nodes R_od *= 0.5 t_wall, _ = nodal2sectional(params['t_full']) # at section nodes z_full = params['z_full'] # at section nodes z_param = params['z_param'] z_section, _ = nodal2sectional(params['z_full']) h_section = np.diff(z_full) t_web = sectionalInterp(z_section, z_param, params['stiffener_web_thickness']) t_flange = sectionalInterp(z_section, z_param, params['stiffener_flange_thickness']) h_web = sectionalInterp(z_section, z_param, params['stiffener_web_height']) w_flange = sectionalInterp(z_section, z_param, params['stiffener_flange_width']) L_stiffener = sectionalInterp(z_section, z_param, params['stiffener_spacing']) rho = params['rho'] # Outer and inner radius of web by section R_wo = R_od - t_wall R_wi = R_wo - h_web # Outer and inner radius of flange by section R_fo = R_wi R_fi = R_fo - t_flange # Material volumes by section V_web = np.pi * (R_wo**2 - R_wi**2) * t_web V_flange = np.pi * (R_fo**2 - R_fi**2) * w_flange # Ring mass by volume by section # Include fudge factor for design features not captured in this simple approach m_web = params['ring_mass_factor'] * rho * V_web m_flange = params['ring_mass_factor'] * rho * V_flange m_ring = m_web + m_flange n_stiff = np.zeros(z_section.shape, dtype=np.int_) # Compute moments of inertia for stiffeners (lumped by section for simplicity) at keel I_web = I_tube(R_wi, R_wo, t_web, m_web) I_flange = I_tube(R_fi, R_fo, w_flange, m_flange) I_ring = I_web + I_flange I_keel = np.zeros((3, 3)) # Now march up the column, adding stiffeners at correct spacing until we are done z_stiff = [] isection = 0 epsilon = 1e-6 while True: if len(z_stiff) == 0: z_march = np.minimum(z_full[isection + 1], z_full[0] + 0.5 * L_stiffener[isection]) + epsilon else: z_march = np.minimum(z_full[isection + 1], z_stiff[-1] + L_stiffener[isection]) + epsilon if z_march >= z_full[-1]: break isection = np.searchsorted(z_full, z_march) - 1 if len(z_stiff) == 0: add_stiff = (z_march - z_full[0]) >= 0.5 * L_stiffener[isection] else: add_stiff = (z_march - z_stiff[-1]) >= L_stiffener[isection] if add_stiff: z_stiff.append(z_march) n_stiff[isection] += 1 R = np.array([0.0, 0.0, (z_march - z_full[0])]) Icg = assembleI(I_ring[isection, :]) I_keel += Icg + m_ring[isection] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) # Number of stiffener rings per section (height of section divided by spacing) unknowns['stiffener_mass'] = n_stiff * m_ring # Find total number of stiffeners in each original section npts_per = z_section.size / (z_param.size - 1) n_stiff_sec = np.zeros(z_param.size - 1) for k in range(npts_per): n_stiff_sec += n_stiff[k::npts_per] unknowns['number_of_stiffeners'] = n_stiff_sec # Store results unknowns['stiffener_I_keel'] = unassembleI(I_keel) # Create some constraints for reasonable stiffener designs for an optimizer unknowns['flange_spacing_ratio'] = w_flange / (0.5 * L_stiffener) unknowns['stiffener_radius_ratio'] = (h_web + t_flange + t_wall) / R_od
def compute_ballast_mass_cg(self, params, unknowns): """Computes permanent ballast mass and center of mass Assumes permanent ballast is located at bottom of spar (at the keel) From the user/optimizer input of ballast height, computes the mass based on varying radius of the spar INPUTS: ---------- params : dictionary of input parameters unknowns : dictionary of output parameters OUTPUTS: ---------- m_ballast : permanent ballast mass z_cg : center of mass along z-axis for the ballast z_ballast_var : z-position of where variable ballast starts ballast_mass in 'unknowns' dictionary set """ # Unpack variables R_od = 0.5 * params['d_full'] t_wall = params['t_full'] h_ballast = params['permanent_ballast_height'] rho_ballast = params['permanent_ballast_density'] rho_water = params['water_density'] z_nodes = params['z_full'] npts = R_od.size # Geometry of the spar in our coordinate system (z=0 at waterline) z_draft = z_nodes[0] # Fixed and total ballast mass and cg # Assume they are bottled in columns a the keel of the spar- first the permanent then the fixed zpts = np.linspace(z_draft, z_draft + h_ballast, npts) R_id = np.interp(zpts, z_nodes, R_od - t_wall) V_perm = np.pi * np.trapz(R_id**2, zpts) m_perm = rho_ballast * V_perm z_cg_perm = rho_ballast * np.pi * np.trapz( zpts * R_id**2, zpts) / m_perm if m_perm > 0.0 else 0.0 for k in xrange(z_nodes.size - 1): ind = np.logical_and(zpts >= z_nodes[k], zpts <= z_nodes[k + 1]) self.section_mass[k] += rho_ballast * np.pi * np.trapz( R_id[ind]**2, zpts[ind]) Ixx = Iyy = frustum.frustumIxx(R_id[:-1], R_id[1:], np.diff(zpts)) Izz = frustum.frustumIzz(R_id[:-1], R_id[1:], np.diff(zpts)) V_slice = frustum.frustumVol(R_id[:-1], R_id[1:], np.diff(zpts)) I_keel = np.zeros((3, 3)) dz = frustum.frustumCG(R_id[:-1], R_id[1:], np.diff(zpts)) + zpts[:-1] - z_draft for k in xrange(V_slice.size): R = np.array([0.0, 0.0, dz[k]]) Icg = assembleI([Ixx[k], Iyy[k], Izz[k], 0.0, 0.0, 0.0]) I_keel += Icg + V_slice[k] * (np.dot(R, R) * np.eye(3) - np.outer(R, R)) I_keel = rho_ballast * unassembleI(I_keel) # Water ballast will start at top of fixed ballast z_water_start = z_draft + h_ballast # Find height of water ballast numerically by finding the height that integrates to the mass we want # This step is completed in spar.py or semi.py because we must account for other substructure elements too zpts = np.linspace(z_water_start, z_nodes[-1], npts) R_id = np.interp(zpts, z_nodes, R_od - t_wall) m_water = rho_water * cumtrapz(np.pi * R_id**2, zpts) unknowns['variable_ballast_interp_mass'] = np.r_[ 0.0, m_water] #cumtrapz has length-1 unknowns['variable_ballast_interp_zpts'] = zpts # Save permanent ballast mass and variable height unknowns['ballast_mass'] = m_perm unknowns['ballast_I_keel'] = I_keel unknowns['ballast_z_cg'] = z_cg_perm return m_perm, z_cg_perm, I_keel
def compute_rigid_body_periods(self, params, unknowns): # Unpack variables ncolumn = int(params['number_of_offset_columns']) R_semi = params['radius_to_offset_column'] m_main = np.sum(params['main_mass']) m_column = np.sum(params['offset_mass']) m_tower = np.sum(params['tower_mass']) m_rna = params['rna_mass'] m_mooring = params['mooring_mass'] m_total = unknowns['total_mass'] m_water = np.maximum(0.0, unknowns['variable_ballast_mass']) m_a_main = params['main_added_mass'] m_a_column = params['offset_added_mass'] rhoWater = params['water_density'] V_system = params['total_displacement'] h_metacenter = unknowns['metacentric_height'] Awater_main = params['main_Awaterplane'] Awater_column = params['offset_Awaterplane'] I_main = params['main_moments_of_inertia'] I_column = params['offset_moments_of_inertia'] I_mooring = params['mooring_moments_of_inertia'] I_water = unknowns['variable_ballast_moments_of_inertia'] I_tower = params['tower_I_base'] I_rna = params['rna_I'] I_waterplane = unknowns['Iwaterplane_system'] z_cg_main = params['main_center_of_mass'] z_cb_main = params['main_center_of_buoyancy'] z_cg_column = params['offset_center_of_mass'] z_cb_column = params['offset_center_of_buoyancy'] z_cb = params['z_center_of_buoyancy'] z_cg_water = unknowns['variable_ballast_center_of_mass'] z_fairlead = params['fairlead'] * (-1) r_cg = unknowns['center_of_mass'] cg_rna = params['rna_cg'] z_tower = params['tower_z_full'] K_moor = np.diag(params['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)) unknowns['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] unknowns['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] unknowns['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) unknowns['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) unknowns['rigid_body_periods'] = 2 * np.pi * np.sqrt( (M_mat + A_mat) / (K_total + epsilon))
def solve_nonlinear(self, params, unknowns, resids): # Unpack variables for thickness and average radius at each can interface twall = params['t_full'] Rb = 0.5*params['d_full'][:-1] Rt = 0.5*params['d_full'][1:] zz = params['z_full'] H = np.diff(zz) rho = params['material_density'] coeff = params['outfitting_factor'] if coeff < 1.0: coeff += 1.0 # Total mass of cylinder V_shell = frustum.frustumShellVol(Rb, Rt, twall, H) unknowns['mass'] = coeff * rho * V_shell # Center of mass of each can/section cm_section = zz[:-1] + frustum.frustumShellCG(Rb, Rt, twall, H) unknowns['section_center_of_mass'] = cm_section # Center of mass of cylinder V_shell += eps unknowns['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 unknowns['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 = params['material_cost_rate'] #1.1 # USD / kg carbon steel plate k_f = params['labor_cost_rate'] #1.0 # USD / min labor k_p = params['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 unknowns['cost'] = K_m + K_o + K_p + K_f
def compute_natural_periods(self, params, unknowns): # Unpack variables ncolumn = int(params['number_of_auxiliary_columns']) R_semi = params['radius_to_auxiliary_column'] m_column = np.sum(params['auxiliary_column_mass']) m_struct = params['structural_mass'] m_water = unknowns['variable_ballast_mass'] m_a_base = params['base_column_added_mass'] m_a_column = params['auxiliary_column_added_mass'] rhoWater = params['water_density'] V_system = params['total_displacement'] h_metacenter = unknowns['metacentric_height'] Awater_base = params['base_column_Awaterplane'] Awater_column = params['auxiliary_column_Awaterplane'] I_base = params['base_column_moments_of_inertia'] I_column = params['auxiliary_column_moments_of_inertia'] z_cg_base = params['base_column_center_of_mass'] z_cb_base = params['base_column_center_of_buoyancy'] z_cg_column = params['auxiliary_column_center_of_mass'] z_cb_column = params['auxiliary_column_center_of_buoyancy'] K_moor = np.diag( params['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 M_mat[:3] = m_struct + np.maximum(m_water, 0.0) # Add up moments of intertia of all columns for other entries 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) ) dz_cg = z_cg_column - z_cg_base I_total = assembleI( I_base ) I_column = assembleI( I_column ) for k in xrange(ncolumn): R = np.array([radii_x[k], radii_y[k], dz_cg]) I_total += I_column + m_column*(np.dot(R, R)*np.eye(3) - np.outer(R, R)) M_mat[3:] = unassembleI( I_total )[:3] unknowns['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_base[:3] + ncolumn*m_a_column[:3] # Add up moments of inertia, move added mass moments from CofB to CofG dz_cgcb = z_cb_base - z_cg_base I_base = assembleI( np.r_[m_a_base[3:] , np.zeros(3)] ) R = np.array([0.0, 0.0, dz_cgcb]) I_total = I_base + m_a_base[0]*(np.dot(R, R)*np.eye(3) - np.outer(R, R)) # Add up added moments of intertia of all columns for other entries dz_cgcb = z_cb_column - z_cg_base I_column = assembleI( np.r_[m_a_column[3:], np.zeros(3)] ) for k in xrange(ncolumn): R = np.array([radii_x[k], radii_y[k], dz_cgcb]) 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] unknowns['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_base + ncolumn*Awater_column) K_hydro[3:5] = rhoWater * gravity * V_system * h_metacenter unknowns['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) unknowns['natural_periods'] = 2*np.pi * np.sqrt( (M_mat + A_mat) / (K_total + epsilon) )
def runMAP(self, params, unknowns): """Writes MAP input file, executes, and then queries MAP to find maximum loading and displacement from vessel displacement around all 360 degrees INPUTS: ---------- params : dictionary of input parameters unknowns : dictionary of output parameters OUTPUTS : none (multiple unknown dictionary values set) """ # Unpack variables rhoWater = params['water_density'] waterDepth = params['water_depth'] fairleadDepth = params['fairlead'] Dmooring = params['mooring_diameter'] offset = params['max_offset'] heel = params['operational_heel'] gamma = params['gamma_f'] n_connect = int(params['number_of_mooring_connections']) n_lines = int(params['mooring_lines_per_connection']) ntotal = n_connect * n_lines # Write the mooring system input file for this design self.write_input_file(params) # 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 unknowns['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) unknowns['mooring_neutral_load'] = F_neutral unknowns['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) unknowns['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) unknowns['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 unknowns['max_offset_restoring_force'] = F_min # Check for good convergence if (plotMat[0,-1,-1] + fairleadDepth) > 1.0: unknowns['axial_unity'] = 1e30 else: unknowns['axial_unity'] = gamma * max_tension / self.min_break_load mymap.end()
def solve_nonlinear(self, params, unknowns, resids): # Unpack variables for thickness and average radius at each can interface twall = params['t_full'] Rb = 0.5 * params['d_full'][:-1] Rt = 0.5 * params['d_full'][1:] zz = params['z_full'] H = np.diff(zz) rho = params['material_density'] coeff = params['outfitting_factor'] if coeff < 1.0: coeff += 1.0 # Total mass of cylinder V_shell = frustum.frustumShellVol(Rb, Rt, twall, H) unknowns['mass'] = coeff * rho * V_shell # Center of mass of each can/section cm_section = zz[:-1] + frustum.frustumShellCG(Rb, Rt, twall, H) unknowns['section_center_of_mass'] = cm_section # Center of mass of cylinder V_shell += eps unknowns['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 unknowns['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 = params['material_cost_rate'] #1.1 # USD / kg carbon steel plate k_f = params['labor_cost_rate'] #1.0 # USD / min labor k_p = params['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 unknowns['cost'] = K_m + K_o + K_p + K_f