def compute(self, inputs, outputs): A = inputs['A'] nodes = inputs['nodes'] mrho = inputs['mrho'] wwr = self.surface['wing_weight_ratio'] Aspars = inputs['Aspars'] #VMGM # Calculate the volume and weight of the structure element_spar_volumes = norm(nodes[1:, :] - nodes[:-1, :], axis=1) * Aspars element_skin_volumes = norm(nodes[1:, :] - nodes[:-1, :], axis=1) * (A - Aspars) # nodes[1:, :] - nodes[:-1, :] this is the delta array of the difference between the points element_mass = element_spar_volumes * mrho[0] * wwr + element_skin_volumes * mrho[1] * wwr weight = np.sum(element_mass) weight_spars = np.sum(element_spar_volumes * mrho[0] * wwr) #VMGM # If the tube is symmetric, double the computed weight if self.surface['symmetry']: weight *= 2. weight_spars *= 2. #VMGM if weight < 0 : print("negative weight") outputs['structural_mass'] = weight outputs['element_mass'] = element_mass outputs['spars_mass'] = weight_spars #VMGM
def compute(self, inputs, outputs): struct_weights = inputs['element_weights'] nodes = inputs['nodes'] element_lengths = np.ones(self.ny - 1, dtype=complex) for i in range(self.ny - 1): element_lengths[i] = norm(nodes[i + 1] - nodes[i]) # And we also need the deltas between consecutive nodes deltas = nodes[1:, :] - nodes[:-1, :] # Assume weight coincides with the elastic axis z_forces_for_each = struct_weights / 2. z_moments_for_each = struct_weights * element_lengths / 12. \ * (deltas[:, 0]**2 + deltas[:, 1]**2)**0.5 / element_lengths loads = np.zeros((self.ny, 6), dtype=complex) # Loads in z-direction loads[:-1, 2] += -z_forces_for_each loads[1:, 2] += -z_forces_for_each # Bending moments for consistency loads[:-1, 3] += -z_moments_for_each * deltas[:, 1] / element_lengths loads[1:, 3] += z_moments_for_each * deltas[:, 1] / element_lengths loads[:-1, 4] += -z_moments_for_each * deltas[:, 0] / element_lengths loads[1:, 4] += z_moments_for_each * deltas[:, 0] / element_lengths outputs['struct_weight_loads'] = loads
def compute(self, inputs, outputs): A = inputs['A'] nodes = inputs['nodes'] mrho = self.surface['mrho'] wwr = self.surface['wing_weight_ratio'] # Calculate the volume and weight of the structure element_volumes = norm(nodes[1:, :] - nodes[:-1, :], axis=1) * A # nodes[1:, :] - nodes[:-1, :] this is the delta array of the different between the points element_mass = element_volumes * mrho * wwr weight = np.sum(element_mass) # If the tube is symmetric, double the computed weight if self.surface['symmetry']: weight *= 2. outputs['structural_mass'] = weight outputs['element_mass'] = element_mass
def compute(self, inputs, outputs): dtype = float if self.under_complex_step: dtype = complex self.T = np.zeros((3, 3), dtype=dtype) self.x_gl = np.array([1, 0, 0], dtype=dtype) radius = inputs['radius'] disp = inputs['disp'] nodes = inputs['nodes'] T = self.T E = self.E G = self.G x_gl = self.x_gl num_elems = self.ny - 1 for ielem in range(num_elems): P0 = nodes[ielem, :] P1 = nodes[ielem + 1, :] L = norm(P1 - P0) x_loc = unit(P1 - P0) y_loc = unit(np.cross(x_loc, x_gl)) z_loc = unit(np.cross(x_loc, y_loc)) T[0, :] = x_loc T[1, :] = y_loc T[2, :] = z_loc u0x, u0y, u0z = T.dot(disp[ielem, :3]) r0x, r0y, r0z = T.dot(disp[ielem, 3:]) u1x, u1y, u1z = T.dot(disp[ielem + 1, :3]) r1x, r1y, r1z = T.dot(disp[ielem + 1, 3:]) tmp = np.sqrt((r1y - r0y)**2 + (r1z - r0z)**2) sxx0 = E * (u1x - u0x) / L + E * radius[ielem] / L * tmp sxx1 = E * (u0x - u1x) / L + E * radius[ielem] / L * tmp sxt = G * radius[ielem] * (r1x - r0x) / L outputs['vonmises'][ielem, 0] = np.sqrt(sxx0**2 + 3 * sxt**2) outputs['vonmises'][ielem, 1] = np.sqrt(sxx1**2 + 3 * sxt**2)
def compute(self, inputs, outputs): radius = inputs['radius'] disp = inputs['disp'] nodes = inputs['nodes'] T = self.T E = self.E G = self.G x_gl = self.x_gl if fortran_flag: vm = OAS_API.oas_api.calc_vonmises(nodes, radius, disp, E, G, x_gl) outputs['vonmises'] = vm else: num_elems = self.ny - 1 for ielem in range(self.ny - 1): P0 = nodes[ielem, :] P1 = nodes[ielem + 1, :] L = norm(P1 - P0) x_loc = unit(P1 - P0) y_loc = unit(np.cross(x_loc, x_gl)) z_loc = unit(np.cross(x_loc, y_loc)) T[0, :] = x_loc T[1, :] = y_loc T[2, :] = z_loc u0x, u0y, u0z = T.dot(disp[ielem, :3]) r0x, r0y, r0z = T.dot(disp[ielem, 3:]) u1x, u1y, u1z = T.dot(disp[ielem + 1, :3]) r1x, r1y, r1z = T.dot(disp[ielem + 1, 3:]) tmp = np.sqrt((r1y - r0y)**2 + (r1z - r0z)**2) sxx0 = E * (u1x - u0x) / L + E * radius[ielem] / L * tmp sxx1 = E * (u0x - u1x) / L + E * radius[ielem] / L * tmp sxt = G * radius[ielem] * (r1x - r0x) / L outputs['vonmises'][ielem, 0] = np.sqrt(sxx0**2 + 3 * sxt**2) outputs['vonmises'][ielem, 1] = np.sqrt(sxx1**2 + 3 * sxt**2)
def compute(self, inputs, outputs): dtype = float if self.under_complex_step: dtype = complex self.T = np.zeros((3, 3),dtype=dtype) self.x_gl = np.array([1, 0, 0],dtype=dtype) radius = inputs['radius'] disp = inputs['disp'] nodes = inputs['nodes'] T = self.T E = self.E G = self.G x_gl = self.x_gl num_elems = self.ny - 1 for ielem in range(num_elems): P0 = nodes[ielem, :] P1 = nodes[ielem+1, :] L = norm(P1 - P0) x_loc = unit(P1 - P0) y_loc = unit(np.cross(x_loc, x_gl)) z_loc = unit(np.cross(x_loc, y_loc)) T[0, :] = x_loc T[1, :] = y_loc T[2, :] = z_loc u0x, u0y, u0z = T.dot(disp[ielem, :3]) r0x, r0y, r0z = T.dot(disp[ielem, 3:]) u1x, u1y, u1z = T.dot(disp[ielem+1, :3]) r1x, r1y, r1z = T.dot(disp[ielem+1, 3:]) tmp = np.sqrt((r1y - r0y)**2 + (r1z - r0z)**2) sxx0 = E * (u1x - u0x) / L + E * radius[ielem] / L * tmp sxx1 = E * (u0x - u1x) / L + E * radius[ielem] / L * tmp sxt = G * radius[ielem] * (r1x - r0x) / L outputs['vonmises'][ielem, 0] = np.sqrt(sxx0**2 + 3 * sxt**2) outputs['vonmises'][ielem, 1] = np.sqrt(sxx1**2 + 3 * sxt**2)
def compute(self, inputs, outputs): A = inputs['A'] nodes = inputs['nodes'] mrho = self.surface['mrho'] wwr = self.surface['wing_weight_ratio'] lf = inputs['load_factor'] # Calculate the volume and weight of the structure element_volumes = norm(nodes[1:, :] - nodes[:-1, :], axis=1) * A # nodes[1:, :] - nodes[:-1, :] this is the delta array of the different between the points element_weights = element_volumes * mrho * 9.81 * wwr * lf weight = np.sum(element_weights) # If the tube is symmetric, double the computed weight and set the # y-location of the cg to 0, at the symmetry plane if self.surface['symmetry']: weight *= 2. #outputs['structural_weight'] = weight outputs['structural_weight'] = weight outputs['element_weights'] = element_weights
def compute(self, inputs, outputs): struct_weights = inputs['element_mass'] * inputs[ 'load_factor'] * grav_constant nodes = inputs['nodes'] element_lengths = norm(nodes[1:, :] - nodes[:-1, :], axis=1) # And we also need the deltas between consecutive nodes deltas = nodes[1:, :] - nodes[:-1, :] # save these slices cause I use them a lot del0 = deltas[:, 0] del1 = deltas[:, 1] # Assume weight coincides with the elastic axis z_forces_for_each = struct_weights / 2. z_moments_for_each = struct_weights / 12. \ * (del0**2 + del1**2)**0.5 loads = outputs['struct_weight_loads'] loads *= 0 # need to zero it out, since we're accumulating onto it # Why doesn't this trigger when running ../../tests/test_aerostruct_wingbox_+weight_analysis.py??? if self.under_complex_step: loads = np.zeros((self.ny, 6), dtype=complex) # Loads in z-direction loads[:-1, 2] += -z_forces_for_each loads[1:, 2] += -z_forces_for_each # Bending moments for consistency bm3 = z_moments_for_each * del1 / element_lengths loads[:-1, 3] += -bm3 loads[1:, 3] += bm3 bm4 = z_moments_for_each * del0 / element_lengths loads[:-1, 4] += -bm4 loads[1:, 4] += bm4 outputs['struct_weight_loads'] = loads
def compute(self, inputs, outputs): struct_weights = inputs['element_mass'] * inputs['load_factor'] * grav_constant nodes = inputs['nodes'] element_lengths = norm(nodes[1:, :] - nodes[:-1, :], axis=1) # And we also need the deltas between consecutive nodes deltas = nodes[1:, :] - nodes[:-1, :] # save these slices cause I use them alot del0 = deltas[: , 0] del1 = deltas[: , 1] # Assume weight coincides with the elastic axis z_forces_for_each = struct_weights / 2. z_moments_for_each = struct_weights / 12. \ * (del0**2 + del1**2)**0.5 loads = outputs['struct_weight_loads'] loads *= 0 # need to zero it out, since we're accumulating onto it # Why doesn't this trigger when running ../../tests/test_aerostruct_wingbox_+weight_analysis.py??? if self.under_complex_step: loads = np.zeros((self.ny, 6), dtype=complex) # Loads in z-direction loads[:-1, 2] += -z_forces_for_each loads[1:, 2] += -z_forces_for_each # Bending moments for consistency bm3 = z_moments_for_each * del1 / element_lengths loads[:-1, 3] += -bm3 loads[1:, 3] += bm3 bm4 = z_moments_for_each * del0 / element_lengths loads[:-1, 4] += -bm4 loads[1:, 4] += bm4 outputs['struct_weight_loads'] = loads
def compute(self, inputs, outputs): mesh = inputs['mesh'] vectors = mesh[-1, :, :] - mesh[0, :, :] streamwise_chords = np.sqrt(np.sum(vectors**2, axis=1)) streamwise_chords = 0.5 * streamwise_chords[: -1] + 0.5 * streamwise_chords[ 1:] # Chord lengths for the panel strips at the panel midpoint outputs['streamwise_chords'] = streamwise_chords.copy() fem_twists = np.zeros(streamwise_chords.shape) fem_chords = streamwise_chords.copy() surface = self.surface # Gets the shear center by looking at the four corners. # Assumes same spar thickness for front and rear spar. w = (surface['data_x_upper'][0] *(surface['data_y_upper'][0]-surface['data_y_lower'][0]) + \ surface['data_x_upper'][-1]*(surface['data_y_upper'][-1]-surface['data_y_lower'][-1])) / \ ( (surface['data_y_upper'][0]-surface['data_y_lower'][0]) + (surface['data_y_upper'][-1]-surface['data_y_lower'][-1])) # TODO: perhaps replace this or link with existing nodes computation nodes = (1 - w) * mesh[0, :, :] + w * mesh[-1, :, :] mesh_vectors = mesh[-1, :, :] - mesh[0, :, :] # Loop over spanwise elements for ielem in range(mesh.shape[1] - 1): # Obtain the element nodes P0 = nodes[ielem, :] P1 = nodes[ielem + 1, :] elem_vec = (P1 - P0) # vector along element temp_vec = elem_vec.copy() temp_vec[0] = 0. # vector along element without x component # This is used to get chord length normal to FEM element. # To be clear, this 3D angle sweep measure. # This is the projection to the wing orthogonal to the FEM direction. cos_theta_fe_sweep = norm(temp_vec) / norm(elem_vec) fem_chords[ielem] = fem_chords[ielem] * cos_theta_fe_sweep outputs['fem_chords'] = fem_chords # Loop over spanwise elements for ielem in range(mesh.shape[1] - 1): # The following is used to approximate the twist angle for the section normal to the FEM element mesh_vec_0 = mesh_vectors[ielem] temp_mesh_vectors_0 = mesh_vec_0.copy() temp_mesh_vectors_0[2] = 0. cos_twist_0 = norm(temp_mesh_vectors_0) / norm(mesh_vec_0) if cos_twist_0 > 1.: theta_0 = 0. # to prevent nan in case value for arccos is greater than 1 due to machine precision else: theta_0 = np.arccos(cos_twist_0) mesh_vec_1 = mesh_vectors[ielem + 1] temp_mesh_vectors_1 = mesh_vec_1.copy() temp_mesh_vectors_1[2] = 0. cos_twist_1 = norm(temp_mesh_vectors_1) / norm(mesh_vec_1) if cos_twist_1 > 1.: theta_1 = 0. # to prevent nan in case value for arccos is greater than 1 due to machine precision else: theta_1 = np.arccos(cos_twist_1) fem_twists[ielem] = ( theta_0 + theta_1) / 2 * streamwise_chords[ielem] / fem_chords[ielem] outputs['fem_twists'] = fem_twists
def compute(self, inputs, outputs): disp = inputs['disp'] nodes = inputs['nodes'] A_enc = inputs['A_enc'] Qy = inputs['Qz'] J = inputs['J'] htop = inputs['htop'] hbottom = inputs['hbottom'] hfront = inputs['hfront'] hrear = inputs['hrear'] spar_thickness = inputs['spar_thickness'] vonmises = outputs['vonmises'] # Only use complex type for these arrays if we're using cs to check derivs dtype = type(disp[0, 0]) T = np.zeros((3, 3), dtype=dtype) x_gl = np.array([1, 0, 0], dtype=dtype) E = self.E G = self.G num_elems = self.ny - 1 for ielem in range(num_elems): P0 = nodes[ielem, :] P1 = nodes[ielem + 1, :] L = norm(P1 - P0) x_loc = unit(P1 - P0) y_loc = unit(np.cross(x_loc, x_gl)) z_loc = unit(np.cross(x_loc, y_loc)) T[0, :] = x_loc T[1, :] = y_loc T[2, :] = z_loc u0x, u0y, u0z = T.dot(disp[ielem, :3]) r0x, r0y, r0z = T.dot(disp[ielem, 3:]) u1x, u1y, u1z = T.dot(disp[ielem + 1, :3]) r1x, r1y, r1z = T.dot(disp[ielem + 1, 3:]) # this is stress = modulus * strain; positive is tensile axial_stress = E * (u1x - u0x) / L # this is Torque / (2 * thickness_min * Area_enclosed) torsion_stress = G * J[ielem] / L * ( r1x - r0x) / 2 / spar_thickness[ielem] / A_enc[ielem] # this is moment * h / I top_bending_stress = E / (L**2) * ( 6 * u0y + 2 * r0z * L - 6 * u1y + 4 * r1z * L) * htop[ielem] # this is moment * h / I bottom_bending_stress = -E / (L**2) * ( 6 * u0y + 2 * r0z * L - 6 * u1y + 4 * r1z * L) * hbottom[ielem] # this is moment * h / I front_bending_stress = -E / (L**2) * ( -6 * u0z + 2 * r0y * L + 6 * u1z + 4 * r1y * L) * hfront[ielem] # this is moment * h / I rear_bending_stress = E / (L**2) * ( -6 * u0z + 2 * r0y * L + 6 * u1z + 4 * r1y * L) * hrear[ielem] # shear due to bending (VQ/It) note: the I used to get V cancels the other I vertical_shear = E / (L**3) * (-12 * u0y - 6 * r0z * L + 12 * u1y - 6 * r1z * L) * Qy[ielem] / ( 2 * spar_thickness[ielem]) # print("==========",ielem,"================") # print("vertical_shear", vertical_shear) # print("top",top_bending_stress) # print("bottom",bottom_bending_stress) # print("front",front_bending_stress) # print("rear",rear_bending_stress) # print("axial", axial_stress) # print("torsion", torsion_stress) # The 4 stress combinations: vonmises[ielem, 0] = np.sqrt( (top_bending_stress + rear_bending_stress + axial_stress)**2 + 3 * torsion_stress**2) / self.tssf vonmises[ielem, 1] = np.sqrt((bottom_bending_stress + front_bending_stress + axial_stress)**2 + 3 * torsion_stress**2) vonmises[ielem, 2] = np.sqrt((front_bending_stress + axial_stress)**2 + 3 * (torsion_stress - vertical_shear)**2) vonmises[ielem, 3] = np.sqrt( (rear_bending_stress + axial_stress)**2 + 3 * (torsion_stress + vertical_shear)**2) / self.tssf
def compute_partials(self, inputs, J): struct_weights = inputs['element_mass'] * inputs['load_factor'] * grav_constant nodes = inputs['nodes'] element_lengths = norm(nodes[1:, :] - nodes[:-1, :], axis=1) # And we also need the deltas between consecutive nodes deltas = nodes[1:, :] - nodes[:-1, :] # save these slices cause I use them alot del0 = deltas[: , 0] del1 = deltas[: , 1] # Assume weight coincides with the elastic axis z_moments_for_each = struct_weights / 12. \ * (del0**2 + del1**2)**0.5 dzf__dew = .5*inputs['load_factor'][0] dzf__dlf = inputs['element_mass']/2. dzm__dew = diags((del0**2 + del1**2)**0.5/12*inputs['load_factor']) dzm__dlf = (del0**2 + del1**2)**.5/12. * inputs['element_mass'] dbm3__dzm = diags(del1/element_lengths) dbm4__dzm = diags(del0/element_lengths) # need to convert to lil to re-order the data to match the original row/col indexing from setup dswl__dlf = (-self.dswl__dbm3*dbm3__dzm*coo_matrix(dzm__dlf).T +\ -self.dswl__dbm4*dbm4__dzm*coo_matrix(dzm__dlf).T +\ self.dswl__dzf*coo_matrix(dzf__dlf).T).tolil() J['struct_weight_loads', 'load_factor'] = dswl__dlf[self.dswl__dlf_row, self.dswl__dlf_col].toarray().flatten() * grav_constant dswl__dew = (-self.dswl__dbm4 * dbm4__dzm * dzm__dew +\ -self.dswl__dbm3 * dbm3__dzm * dzm__dew +\ self.dswl__dzf * dzf__dew).tolil() data = dswl__dew[self.dswl__dew_pattern.row, self.dswl__dew_pattern.col].toarray().flatten() J['struct_weight_loads', 'element_mass'] = data * grav_constant # dstruct_weight_loads__dnodes (this one is super complicated) # del__dnodes # note: del__dnodes matrix already created in setup, just need to set data raw_data = diags(1/element_lengths)*(nodes[1:, :] - nodes[:-1, :]) data = np.hstack((-raw_data,raw_data)).flatten() self.del__dnodes.data = data dzm_dnodes = diags(struct_weights/12*(del0**2 + del1**2)**-.5)*\ (diags(del0)*self.ddel0__dnodes + diags(del1)*self.ddel1__dnodes) dbm3_dnodes = diags(del1/element_lengths)*dzm_dnodes \ + diags(z_moments_for_each/element_lengths)*self.ddel1__dnodes \ - diags(z_moments_for_each*del1/element_lengths**2)*self.del__dnodes dbm4_dnodes = diags(del0/element_lengths)*dzm_dnodes \ + diags(z_moments_for_each/element_lengths)*self.ddel0__dnodes \ - diags(z_moments_for_each*del0/element_lengths**2)*self.del__dnodes # this is kind of dumb, but I need lil cause I have to re-index to preserve order # the coo column ordering doesn't seem to be deterministic # so I use the original row/col from the pattern as index arrays to # pull the data out in the correct order dswl__dnodes= (self.dswl__dbm3*dbm3_dnodes+self.dswl__dbm4*dbm4_dnodes).tolil() data = dswl__dnodes[self.dswl__dnodes_pattern.row, self.dswl__dnodes_pattern.col].toarray().flatten() J['struct_weight_loads', 'nodes'] = -data
def compute(self, inputs, outputs): disp = inputs['disp'] nodes = inputs['nodes'] A_enc = inputs['A_enc'] Qy = inputs['Qz'] Iz = inputs['Iz'] J = inputs['J'] htop = inputs['htop'] hbottom = inputs['hbottom'] hfront = inputs['hfront'] hrear = inputs['hrear'] spar_thickness = inputs['spar_thickness'] skin_thickness = inputs['skin_thickness'] vonmises = outputs['vonmises'] # Only use complex type for these arrays if we're using cs to check derivs dtype = type(disp[0, 0]) T = np.zeros((3, 3), dtype=dtype) x_gl = np.array([1, 0, 0], dtype=dtype) E = self.E G = self.G num_elems = self.ny - 1 for ielem in range(num_elems): P0 = nodes[ielem, :] P1 = nodes[ielem+1, :] L = norm(P1 - P0) x_loc = unit(P1 - P0) y_loc = unit(np.cross(x_loc, x_gl)) z_loc = unit(np.cross(x_loc, y_loc)) T[0, :] = x_loc T[1, :] = y_loc T[2, :] = z_loc u0x, u0y, u0z = T.dot(disp[ielem, :3]) r0x, r0y, r0z = T.dot(disp[ielem, 3:]) u1x, u1y, u1z = T.dot(disp[ielem+1, :3]) r1x, r1y, r1z = T.dot(disp[ielem+1, 3:]) axial_stress = E * (u1x - u0x) / L # this is stress = modulus * strain; positive is tensile torsion_stress = G * J[ielem] / L * (r1x - r0x) / 2 / spar_thickness[ielem] / A_enc[ielem] # this is Torque / (2 * thickness_min * Area_enclosed) top_bending_stress = E / (L**2) * (6 * u0y + 2 * r0z * L - 6 * u1y + 4 * r1z * L ) * htop[ielem] # this is moment * htop / I bottom_bending_stress = - E / (L**2) * (6 * u0y + 2 * r0z * L - 6 * u1y + 4 * r1z * L ) * hbottom[ielem] # this is moment * htop / I front_bending_stress = - E / (L**2) * (-6 * u0z + 2 * r0y * L + 6 * u1z + 4 * r1y * L ) * hfront[ielem] # this is moment * htop / I rear_bending_stress = E / (L**2) * (-6 * u0z + 2 * r0y * L + 6 * u1z + 4 * r1y * L ) * hrear[ielem] # this is moment * htop / I vertical_shear = E / (L**3) *(-12 * u0y - 6 * r0z * L + 12 * u1y - 6 * r1z * L ) * Qy[ielem] / (2 * spar_thickness[ielem]) # shear due to bending (VQ/It) note: the I used to get V cancels the other I # print("==========",ielem,"================") # print("vertical_shear", vertical_shear) # print("top",top_bending_stress) # print("bottom",bottom_bending_stress) # print("front",front_bending_stress) # print("rear",rear_bending_stress) # print("axial", axial_stress) # print("torsion", torsion_stress) vonmises[ielem, 0] = np.sqrt((top_bending_stress + rear_bending_stress + axial_stress)**2 + 3*torsion_stress**2) / self.tssf vonmises[ielem, 1] = np.sqrt((bottom_bending_stress + front_bending_stress + axial_stress)**2 + 3*torsion_stress**2) vonmises[ielem, 2] = np.sqrt((front_bending_stress + axial_stress)**2 + 3*(torsion_stress-vertical_shear)**2) vonmises[ielem, 3] = np.sqrt((rear_bending_stress + axial_stress)**2 + 3*(torsion_stress+vertical_shear)**2) / self.tssf
def compute_partials(self, inputs, partials): radius = inputs['radius'] disp = inputs['disp'] nodes = inputs['nodes'] T = self.T E = self.E G = self.G x_gl = self.x_gl num_elems = self.ny - 1 for ielem in range(num_elems): # Compute the coordinate delta between the two element end points P0 = nodes[ielem, :] P1 = nodes[ielem+1, :] dP = P1 - P0 # Compute the derivative of element length L = norm(dP) dLddP = norm_d(dP) # unit function converts a vector to a unit vector # calculate the transormation to the local element frame. # We use x_gl to provide a reference axis to reference x_loc = unit(dP) dxdP = unit_d(dP) y_loc = unit(np.cross(x_loc, x_gl)) dtmpdx, _ = cross_d(x_loc, x_gl) dydtmp = unit_d(np.cross(x_loc, x_gl)) dydP = dydtmp.dot(dtmpdx).dot(dxdP) z_loc = unit(np.cross(x_loc, y_loc)) dtmpdx, dtmpdy = cross_d(x_loc,y_loc) dzdtmp = unit_d(np.cross(x_loc, y_loc)) dzdP = dzdtmp.dot(dtmpdx).dot(dxdP) + dzdtmp.dot(dtmpdy).dot(dydP) T[0, :] = x_loc T[1, :] = y_loc T[2, :] = z_loc u0x = x_loc.dot(disp[ielem, :3]) r0x, r0y, r0z = T.dot(disp[ielem, 3:]) u1x = x_loc.dot(disp[ielem+1, :3]) r1x, r1y, r1z = T.dot(disp[ielem+1, 3:]) # #$$$$$$$$$$$$$$$$$$$$$$$$$$ # The derivatives of the above code wrt displacement all boil down to sections of the T matrix dxddisp = T[0,:] dyddisp = T[1,:] dzddisp = T[2,:] #The derivatives of the above code wrt T all boil down to sections of the #displacement vector du0dloc = disp[ielem, :3] dr0dloc = disp[ielem, 3:] du1dloc = disp[ielem+1, :3] dr1dloc = disp[ielem+1, 3:] #$$$$$$$$$$$$$$$$$$$$$$$$$$ # Original code # $$$$$$$$$$$$ tmp = np.sqrt((r1y - r0y)**2 + (r1z - r0z)**2) + 1e-50 #added eps to avoid 0 disp singularity sxx0 = E * (u1x - u0x) / L + E * radius[ielem] / L * tmp sxx1 = E * (u0x - u1x) / L + E * radius[ielem] / L * tmp sxt = G * radius[ielem] * (r1x - r0x) / L # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ dtmpdr0y = 1/tmp * (r1y - r0y)*-1 dtmpdr1y = 1/tmp * (r1y - r0y) dtmpdr0z = 1/tmp * (r1z - r0z)*-1 dtmpdr1z = 1/tmp * (r1z - r0z) # Combine all of the derivtives for tmp dtmpdDisp = np.zeros(12) dr0xdDisp = np.zeros(12) dr1xdDisp = np.zeros(12) #r0 term dr0xdDisp[3:6] = dxddisp dr1xdDisp[9:12] = dxddisp dtmpdDisp[3:6] = dtmpdr0y*dyddisp dtmpdDisp[3:6] += dtmpdr0z*dzddisp dtmpdDisp[9:12] = dtmpdr1y*dyddisp dtmpdDisp[9:12] += dtmpdr1z*dzddisp # x_loc, y_loc and z_loc terms # (dttmpx_loc is zeros, so don't compute with it) dtmpdy_loc = dtmpdr0y*dr0dloc + dtmpdr1y*dr1dloc dtmpdz_loc = dtmpdr0z*dr0dloc + dtmpdr1z*dr1dloc dtmpdP = dtmpdy_loc.dot(dydP) + dtmpdz_loc.dot(dzdP) dsxx0dtmp = E * radius[ielem] / L dsxx0du0x = -E / L dsxx0du1x = E / L dsxx0dL = -E * (u1x - u0x) / (L*L) - E * radius[ielem] / (L*L) * tmp dsxx1dtmp = E * radius[ielem] / L dsxx1du0x = E / L dsxx1du1x = -E / L dsxx1dL = -E * (u0x - u1x) / (L*L) - E * radius[ielem] / (L*L) * tmp dsxx0dP = dsxx0dtmp * dtmpdP + \ dsxx0du0x*du0dloc.dot(dxdP) + dsxx0du1x*du1dloc.dot(dxdP)+\ dsxx0dL*dLddP dsxx1dP = dsxx1dtmp * dtmpdP + \ dsxx1du0x*du0dloc.dot(dxdP)+dsxx1du1x*du1dloc.dot(dxdP)+\ dsxx1dL*dLddP # Combine sxx0 and sxx1 terms # Start with the tmp term dsxx0dDisp = dsxx0dtmp * dtmpdDisp dsxx1dDisp = dsxx1dtmp * dtmpdDisp # Now add the direct u dep dsxx0dDisp[0:3] = dsxx0du0x * dxddisp dsxx0dDisp[6:9] = dsxx0du1x * dxddisp dsxx1dDisp[0:3] = dsxx1du0x * dxddisp dsxx1dDisp[6:9] = dsxx1du1x * dxddisp # Combine sxt term dsxtdr0x = -G * radius[ielem] / L dsxtdr1x = G * radius[ielem] / L dsxtdL = - G * radius[ielem] * (r1x - r0x) / (L*L) dsxtdP = dsxtdr0x*(dr0dloc.dot(dxdP)) + dsxtdr1x*(dr1dloc.dot(dxdP)) + \ dsxtdL*dLddP #disp dsxtdDisp = dsxtdr0x * dr0xdDisp + dsxtdr1x * dr1xdDisp #radius derivatives dsxxdrad = E / L * tmp dsxtdrad = G * (r1x - r0x)/L fact = 1.0 / (np.sqrt(sxx0**2 + 3 * sxt**2)) dVm0dsxx0 = sxx0 * fact dVm0dsxt = 3 * sxt * fact fact = 1.0 / (np.sqrt(sxx1**2 + 3 * sxt**2)) dVm1dsxx1 = sxx1 * fact dVm1dsxt = 3 * sxt * fact ii = 2 * ielem partials['vonmises', 'radius'][ii] = dVm0dsxx0*dsxxdrad + dVm0dsxt*dsxtdrad partials['vonmises', 'radius'][ii+1] = dVm1dsxx1*dsxxdrad + dVm1dsxt*dsxtdrad ii = 24 * ielem partials['vonmises', 'disp'][ii:ii+12] = dVm0dsxx0*dsxx0dDisp + dVm0dsxt*dsxtdDisp partials['vonmises', 'disp'][ii+12:ii+24] = dVm1dsxx1*dsxx1dDisp + dVm1dsxt*dsxtdDisp # Compute terms for the nodes ii = 12 * ielem dVm0_dnode = dVm0dsxx0*dsxx0dP + dVm0dsxt*dsxtdP partials['vonmises', 'nodes'][ii:ii+3] = -dVm0_dnode partials['vonmises', 'nodes'][ii+3:ii+6] = dVm0_dnode dVM1_dnode = dVm1dsxx1*dsxx1dP + dVm1dsxt*dsxtdP partials['vonmises', 'nodes'][ii+6:ii+9] = -dVM1_dnode partials['vonmises', 'nodes'][ii+9:ii+12] = dVM1_dnode
def compute_partials(self, inputs, partials): radius = inputs['radius'] disp = inputs['disp'] nodes = inputs['nodes'] T = self.T E = self.E G = self.G x_gl = self.x_gl num_elems = self.ny - 1 for ielem in range(num_elems): # Compute the coordinate delta between the two element end points P0 = nodes[ielem, :] P1 = nodes[ielem + 1, :] dP = P1 - P0 # Compute the derivative of element length L = norm(dP) dLddP = norm_d(dP) # unit function converts a vector to a unit vector # calculate the transormation to the local element frame. # We use x_gl to provide a reference axis to reference x_loc = unit(dP) dxdP = unit_d(dP) y_loc = unit(np.cross(x_loc, x_gl)) dtmpdx, _ = cross_d(x_loc, x_gl) dydtmp = unit_d(np.cross(x_loc, x_gl)) dydP = dydtmp.dot(dtmpdx).dot(dxdP) z_loc = unit(np.cross(x_loc, y_loc)) dtmpdx, dtmpdy = cross_d(x_loc, y_loc) dzdtmp = unit_d(np.cross(x_loc, y_loc)) dzdP = dzdtmp.dot(dtmpdx).dot(dxdP) + dzdtmp.dot(dtmpdy).dot(dydP) T[0, :] = x_loc T[1, :] = y_loc T[2, :] = z_loc u0x = x_loc.dot(disp[ielem, :3]) r0x, r0y, r0z = T.dot(disp[ielem, 3:]) u1x = x_loc.dot(disp[ielem + 1, :3]) r1x, r1y, r1z = T.dot(disp[ielem + 1, 3:]) # #$$$$$$$$$$$$$$$$$$$$$$$$$$ # The derivatives of the above code wrt displacement all boil down to sections of the T matrix dxddisp = T[0, :] dyddisp = T[1, :] dzddisp = T[2, :] #The derivatives of the above code wrt T all boil down to sections of the #displacement vector du0dloc = disp[ielem, :3] dr0dloc = disp[ielem, 3:] du1dloc = disp[ielem + 1, :3] dr1dloc = disp[ielem + 1, 3:] #$$$$$$$$$$$$$$$$$$$$$$$$$$ # Original code # $$$$$$$$$$$$ tmp = np.sqrt( (r1y - r0y)**2 + (r1z - r0z)**2) + 1e-50 #added eps to avoid 0 disp singularity sxx0 = E * (u1x - u0x) / L + E * radius[ielem] / L * tmp sxx1 = E * (u0x - u1x) / L + E * radius[ielem] / L * tmp sxt = G * radius[ielem] * (r1x - r0x) / L # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ dtmpdr0y = 1 / tmp * (r1y - r0y) * -1 dtmpdr1y = 1 / tmp * (r1y - r0y) dtmpdr0z = 1 / tmp * (r1z - r0z) * -1 dtmpdr1z = 1 / tmp * (r1z - r0z) # Combine all of the derivtives for tmp dtmpdDisp = np.zeros(12) dr0xdDisp = np.zeros(12) dr1xdDisp = np.zeros(12) #r0 term dr0xdDisp[3:6] = dxddisp dr1xdDisp[9:12] = dxddisp dtmpdDisp[3:6] = dtmpdr0y * dyddisp dtmpdDisp[3:6] += dtmpdr0z * dzddisp dtmpdDisp[9:12] = dtmpdr1y * dyddisp dtmpdDisp[9:12] += dtmpdr1z * dzddisp # x_loc, y_loc and z_loc terms # (dttmpx_loc is zeros, so don't compute with it) dtmpdy_loc = dtmpdr0y * dr0dloc + dtmpdr1y * dr1dloc dtmpdz_loc = dtmpdr0z * dr0dloc + dtmpdr1z * dr1dloc dtmpdP = dtmpdy_loc.dot(dydP) + dtmpdz_loc.dot(dzdP) dsxx0dtmp = E * radius[ielem] / L dsxx0du0x = -E / L dsxx0du1x = E / L dsxx0dL = -E * (u1x - u0x) / (L * L) - E * radius[ielem] / (L * L) * tmp dsxx1dtmp = E * radius[ielem] / L dsxx1du0x = E / L dsxx1du1x = -E / L dsxx1dL = -E * (u0x - u1x) / (L * L) - E * radius[ielem] / (L * L) * tmp dsxx0dP = dsxx0dtmp * dtmpdP + \ dsxx0du0x*du0dloc.dot(dxdP) + dsxx0du1x*du1dloc.dot(dxdP)+\ dsxx0dL*dLddP dsxx1dP = dsxx1dtmp * dtmpdP + \ dsxx1du0x*du0dloc.dot(dxdP)+dsxx1du1x*du1dloc.dot(dxdP)+\ dsxx1dL*dLddP # Combine sxx0 and sxx1 terms # Start with the tmp term dsxx0dDisp = dsxx0dtmp * dtmpdDisp dsxx1dDisp = dsxx1dtmp * dtmpdDisp # Now add the direct u dep dsxx0dDisp[0:3] = dsxx0du0x * dxddisp dsxx0dDisp[6:9] = dsxx0du1x * dxddisp dsxx1dDisp[0:3] = dsxx1du0x * dxddisp dsxx1dDisp[6:9] = dsxx1du1x * dxddisp # Combine sxt term dsxtdr0x = -G * radius[ielem] / L dsxtdr1x = G * radius[ielem] / L dsxtdL = -G * radius[ielem] * (r1x - r0x) / (L * L) dsxtdP = dsxtdr0x*(dr0dloc.dot(dxdP)) + dsxtdr1x*(dr1dloc.dot(dxdP)) + \ dsxtdL*dLddP #disp dsxtdDisp = dsxtdr0x * dr0xdDisp + dsxtdr1x * dr1xdDisp #radius derivatives dsxxdrad = E / L * tmp dsxtdrad = G * (r1x - r0x) / L fact = 1.0 / (np.sqrt(sxx0**2 + 3 * sxt**2)) dVm0dsxx0 = sxx0 * fact dVm0dsxt = 3 * sxt * fact fact = 1.0 / (np.sqrt(sxx1**2 + 3 * sxt**2)) dVm1dsxx1 = sxx1 * fact dVm1dsxt = 3 * sxt * fact ii = 2 * ielem partials['vonmises', 'radius'][ii] = dVm0dsxx0 * dsxxdrad + dVm0dsxt * dsxtdrad partials['vonmises', 'radius'][ii + 1] = dVm1dsxx1 * dsxxdrad + dVm1dsxt * dsxtdrad ii = 24 * ielem partials[ 'vonmises', 'disp'][ii:ii + 12] = dVm0dsxx0 * dsxx0dDisp + dVm0dsxt * dsxtdDisp partials[ 'vonmises', 'disp'][ii + 12:ii + 24] = dVm1dsxx1 * dsxx1dDisp + dVm1dsxt * dsxtdDisp # Compute terms for the nodes ii = 12 * ielem dVm0_dnode = dVm0dsxx0 * dsxx0dP + dVm0dsxt * dsxtdP partials['vonmises', 'nodes'][ii:ii + 3] = -dVm0_dnode partials['vonmises', 'nodes'][ii + 3:ii + 6] = dVm0_dnode dVM1_dnode = dVm1dsxx1 * dsxx1dP + dVm1dsxt * dsxtdP partials['vonmises', 'nodes'][ii + 6:ii + 9] = -dVM1_dnode partials['vonmises', 'nodes'][ii + 9:ii + 12] = dVM1_dnode
def compute_partials(self, inputs, partials): radius = inputs['radius'] disp = inputs['disp'] nodes = inputs['nodes'] T = self.T E = self.E G = self.G x_gl = self.x_gl num_elems = self.ny - 1 for ielem in range(num_elems): # Compute the coordinate delta between the two element end points P0 = nodes[ielem, :] P1 = nodes[ielem+1, :] dP = P1 - P0 # and its derivatives ddPdP0 = -1.0*np.eye(3) ddPdP1 = 1.0*np.eye(3) # Compute the element length and its derivative L = norm(dP) dLddP = norm_d(dP) # unit function converts a vector to a unit vector # calculate the transormation to the local element frame. # We use x_gl to provide a reference axis to reference x_loc = unit(dP) dxdP = unit_d(dP) y_loc = unit(np.cross(x_loc, x_gl)) dtmpdx,dummy = cross_d(x_loc,x_gl) dydtmp = unit_d(np.cross(x_loc, x_gl)) dydP = dydtmp.dot(dtmpdx).dot(dxdP) z_loc = unit(np.cross(x_loc, y_loc)) dtmpdx,dtmpdy = cross_d(x_loc,y_loc) dzdtmp = unit_d(np.cross(x_loc, y_loc)) dzdP = dzdtmp.dot(dtmpdx).dot(dxdP)+dzdtmp.dot(dtmpdy).dot(dydP) T[0, :] = x_loc T[1, :] = y_loc T[2, :] = z_loc #$$$$$$$$$$$$$$$$$$$$$$$$$$ # Original code # $$$$$$$$$$$$ u0x, u0y, u0z = T.dot(disp[ielem, :3]) r0x, r0y, r0z = T.dot(disp[ielem, 3:]) u1x, u1y, u1z = T.dot(disp[ielem+1, :3]) r1x, r1y, r1z = T.dot(disp[ielem+1, 3:]) # #$$$$$$$$$$$$$$$$$$$$$$$$$$ # The derivatives of the above code wrt displacement all boil down to sections of the T matrix dxddisp = T[0,:] dyddisp = T[1,:] dzddisp = T[2,:] #The derivatives of the above code wrt T all boil down to sections of the #displacement vector # du0dT = disp[ielem, :3] # dr0dT = disp[ielem, 3:] # du1dT = disp[ielem+1, :3] # dr1dT = disp[ielem+1, 3:] du0dloc = disp[ielem, :3] dr0dloc = disp[ielem, 3:] du1dloc = disp[ielem+1, :3] dr1dloc = disp[ielem+1, 3:] #$$$$$$$$$$$$$$$$$$$$$$$$$$ # Original code # $$$$$$$$$$$$ tmp = np.sqrt((r1y - r0y)**2 + (r1z - r0z)**2) + 1e-50 #added eps to avoid 0 disp singularity sxx0 = E * (u1x - u0x) / L + E * radius[ielem] / L * tmp sxx1 = E * (u0x - u1x) / L + E * radius[ielem] / L * tmp sxt = G * radius[ielem] * (r1x - r0x) / L # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ dtmpdr0y = 1/tmp * (r1y - r0y)*-1 dtmpdr1y = 1/tmp * (r1y - r0y) dtmpdr0z = 1/tmp * (r1z - r0z)*-1 dtmpdr1z = 1/tmp * (r1z - r0z) # Combine all of the derivtives for tmp dtmpdDisp = np.zeros(2 * 6) dr0xdDisp = np.zeros(2*6) dr0ydDisp = np.zeros(2*6) dr0zdDisp = np.zeros(2*6) dr1xdDisp = np.zeros(2*6) dr1ydDisp = np.zeros(2*6) dr1zdDisp = np.zeros(2*6) idx1 = 3 idx2 = 6 + 3 #r0 term dr0xdDisp[idx1:idx1+3] = dxddisp dr0ydDisp[idx1:idx1+3] = dyddisp dr0zdDisp[idx1:idx1+3] = dzddisp dr1xdDisp[idx2:idx2+3] = dxddisp dr1ydDisp[idx2:idx2+3] = dyddisp dr1zdDisp[idx2:idx2+3] = dzddisp dtmpdDisp = (dtmpdr0y*dr0ydDisp+dtmpdr1y*dr1ydDisp+\ dtmpdr0z*dr0zdDisp+dtmpdr1z*dr1zdDisp) # x_loc, y_loc and z_loc terms dtmpdx_loc = np.array([0,0,0]) dtmpdy_loc = dtmpdr0y*dr0dloc + dtmpdr1y*dr1dloc dtmpdz_loc = dtmpdr0z*dr0dloc + dtmpdr1z*dr1dloc dtmpdP = dtmpdx_loc.dot(dxdP) + dtmpdy_loc.dot(dydP) + dtmpdz_loc.dot(dzdP) dsxx0dtmp = E * radius[ielem] / L dsxx0du0x = -E / L dsxx0du1x = E / L dsxx0dL = -E * (u1x - u0x) / (L*L) - E * radius[ielem] / (L*L) * tmp dsxx1dtmp = E * radius[ielem] / L dsxx1du0x = E / L dsxx1du1x = -E / L dsxx1dL = -E * (u0x - u1x) / (L*L) - E * radius[ielem] / (L*L) * tmp dsxx0dP = dsxx0dtmp * dtmpdP + \ dsxx0du0x*du0dloc.dot(dxdP) + dsxx0du1x*du1dloc.dot(dxdP)+\ dsxx0dL*dLddP dsxx1dP = dsxx1dtmp * dtmpdP + \ dsxx1du0x*du0dloc.dot(dxdP)+dsxx1du1x*du1dloc.dot(dxdP)+\ dsxx1dL*dLddP # Combine sxx0 and sxx1 terms idx1 = 0 idx2 = 6 # Start with the tmp term dsxx0dDisp = dsxx0dtmp * dtmpdDisp dsxx1dDisp = dsxx1dtmp * dtmpdDisp # Now add the direct u dep dsxx0dDisp[idx1:idx1+3] = dsxx0du0x * dxddisp dsxx0dDisp[idx2:idx2+3] = dsxx0du1x * dxddisp dsxx1dDisp[idx1:idx1+3] = dsxx1du0x * dxddisp dsxx1dDisp[idx2:idx2+3] = dsxx1du1x * dxddisp # Combine sxt term dsxtdDisp = np.zeros(2 * 6) idx1 = 3 idx2 = 6 + 3 dsxtdr0x = -G * radius[ielem] / L dsxtdr1x = G * radius[ielem] / L dsxtdL = - G * radius[ielem] * (r1x - r0x) / (L*L) dsxtdP = dsxtdr0x*(dr0dloc.dot(dxdP))+ dsxtdr1x*(dr1dloc.dot(dxdP))+\ dsxtdL*dLddP #disp dsxtdDisp= dsxtdr0x * dr0xdDisp + dsxtdr1x * dr1xdDisp #radius derivatives dsxx0drad = E / L * tmp dsxx1drad = E / L * tmp dsxtdrad = G * (r1x - r0x)/L dVm0dsxx0 = (sxx0)/(np.sqrt(sxx0**2 + 3 * sxt**2)) dVm0dsxt = (3*sxt)/(np.sqrt(sxx0**2 + 3 * sxt**2)) dVm1dsxx1 = (sxx1)/(np.sqrt(sxx1**2 + 3 * sxt**2)) dVm1dsxt = (3*sxt)/(np.sqrt(sxx1**2 + 3 * sxt**2)) idx = ielem*2 partials['vonmises','radius'][idx,ielem] = dVm0dsxx0*dsxx0drad+dVm0dsxt*dsxtdrad partials['vonmises','radius'][idx+1,ielem] = dVm1dsxx1*dsxx1drad+dVm1dsxt*dsxtdrad idx2 = ielem*6 partials['vonmises','disp'][idx,idx2:idx2+12] = (dVm0dsxx0*dsxx0dDisp+dVm0dsxt*dsxtdDisp ) partials['vonmises','disp'][idx+1,idx2:idx2+12] = (dVm1dsxx1*dsxx1dDisp+dVm1dsxt*dsxtdDisp ) # Compute terms for the nodes idx3 = ielem*3 partials['vonmises','nodes'][idx,idx3:idx3+3] = dVm0dsxx0*dsxx0dP.dot(ddPdP0)+dVm0dsxt*dsxtdP.dot(ddPdP0) partials['vonmises','nodes'][idx,idx3+3:idx3+6] = dVm0dsxx0*dsxx0dP.dot(ddPdP1)+dVm0dsxt*dsxtdP.dot(ddPdP1) partials['vonmises','nodes'][idx+1,idx3:idx3+3] = dVm1dsxx1*dsxx1dP.dot(ddPdP0)+dVm1dsxt*dsxtdP.dot(ddPdP0) partials['vonmises','nodes'][idx+1,idx3+3:idx3+6] = dVm1dsxx1*dsxx1dP.dot(ddPdP1)+dVm1dsxt*dsxtdP.dot(ddPdP1)
def compute(self, inputs, outputs): mesh = inputs['mesh'] vectors = mesh[-1, :, :] - mesh[0, :, :] streamwise_chords = np.sqrt(np.sum(vectors**2, axis=1)) streamwise_chords = 0.5 * streamwise_chords[:-1] + 0.5 * streamwise_chords[1:] # Chord lengths for the panel strips at the panel midpoint outputs['streamwise_chords'] = streamwise_chords.copy() fem_twists = np.zeros(streamwise_chords.shape, dtype=type(mesh[0, 0, 0])) fem_chords = streamwise_chords.copy() surface = self.surface # Gets the shear center by looking at the four corners. # Assumes same spar thickness for front and rear spar. w = (surface['data_x_upper'][0] *(surface['data_y_upper'][0]-surface['data_y_lower'][0]) + \ surface['data_x_upper'][-1]*(surface['data_y_upper'][-1]-surface['data_y_lower'][-1])) / \ ( (surface['data_y_upper'][0]-surface['data_y_lower'][0]) + (surface['data_y_upper'][-1]-surface['data_y_lower'][-1])) # TODO: perhaps replace this or link with existing nodes computation nodes = (1-w) * mesh[0, :, :] + w * mesh[-1, :, :] mesh_vectors = mesh[-1, :, :] - mesh[0, :, :] # Loop over spanwise elements for ielem in range(mesh.shape[1] - 1): # Obtain the element nodes P0 = nodes[ielem, :] P1 = nodes[ielem+1, :] elem_vec = (P1 - P0) # vector along element temp_vec = elem_vec.copy() temp_vec[0] = 0. # vector along element without x component # This is used to get chord length normal to FEM element. # To be clear, this 3D angle sweep measure. # This is the projection to the wing orthogonal to the FEM direction. cos_theta_fe_sweep = norm(temp_vec) / norm(elem_vec) fem_chords[ielem] = fem_chords[ielem] * cos_theta_fe_sweep outputs['fem_chords'] = fem_chords # Loop over spanwise elements for ielem in range(mesh.shape[1] - 1): # The following is used to approximate the twist angle for the section normal to the FEM element mesh_vec_0 = mesh_vectors[ielem] temp_mesh_vectors_0 = mesh_vec_0.copy() temp_mesh_vectors_0[2] = 0. cos_twist_0 = norm(temp_mesh_vectors_0) / norm(mesh_vec_0) if cos_twist_0 > 1.: theta_0 = 0. # to prevent nan in case value for arccos is greater than 1 due to machine precision else: theta_0 = np.arccos(cos_twist_0) mesh_vec_1 = mesh_vectors[ielem + 1] temp_mesh_vectors_1 = mesh_vec_1.copy() temp_mesh_vectors_1[2] = 0. cos_twist_1 = norm(temp_mesh_vectors_1) / norm(mesh_vec_1) if cos_twist_1 > 1.: theta_1 = 0. # to prevent nan in case value for arccos is greater than 1 due to machine precision else: theta_1 = np.arccos(cos_twist_1) fem_twists[ielem] = (theta_0 + theta_1) / 2 * streamwise_chords[ielem] / fem_chords[ielem] outputs['fem_twists'] = fem_twists
def compute_partials(self, inputs, J): struct_weights = inputs['element_mass'] * inputs[ 'load_factor'] * grav_constant nodes = inputs['nodes'] element_lengths = norm(nodes[1:, :] - nodes[:-1, :], axis=1) # And we also need the deltas between consecutive nodes deltas = nodes[1:, :] - nodes[:-1, :] # save these slices cause I use them alot del0 = deltas[:, 0] del1 = deltas[:, 1] # Assume weight coincides with the elastic axis z_moments_for_each = struct_weights / 12. \ * (del0**2 + del1**2)**0.5 dzf__dew = .5 * inputs['load_factor'][0] dzf__dlf = inputs['element_mass'] / 2. dzm__dew = diags((del0**2 + del1**2)**0.5 / 12 * inputs['load_factor']) dzm__dlf = (del0**2 + del1**2)**.5 / 12. * inputs['element_mass'] dbm3__dzm = diags(del1 / element_lengths) dbm4__dzm = diags(del0 / element_lengths) # need to convert to lil to re-order the data to match the original row/col indexing from setup dswl__dlf = (-self.dswl__dbm3*dbm3__dzm*coo_matrix(dzm__dlf).T +\ -self.dswl__dbm4*dbm4__dzm*coo_matrix(dzm__dlf).T +\ self.dswl__dzf*coo_matrix(dzf__dlf).T).tolil() J['struct_weight_loads', 'load_factor'] = dswl__dlf[ self.dswl__dlf_row, self.dswl__dlf_col].toarray().flatten() * grav_constant dswl__dew = (-self.dswl__dbm4 * dbm4__dzm * dzm__dew +\ -self.dswl__dbm3 * dbm3__dzm * dzm__dew +\ self.dswl__dzf * dzf__dew).tolil() data = dswl__dew[self.dswl__dew_pattern.row, self.dswl__dew_pattern.col].toarray().flatten() J['struct_weight_loads', 'element_mass'] = data * grav_constant # dstruct_weight_loads__dnodes (this one is super complicated) # del__dnodes # note: del__dnodes matrix already created in setup, just need to set data raw_data = diags(1 / element_lengths) * (nodes[1:, :] - nodes[:-1, :]) data = np.hstack((-raw_data, raw_data)).flatten() self.del__dnodes.data = data dzm_dnodes = diags(struct_weights/12*(del0**2 + del1**2)**-.5)*\ (diags(del0)*self.ddel0__dnodes + diags(del1)*self.ddel1__dnodes) dbm3_dnodes = diags(del1/element_lengths)*dzm_dnodes \ + diags(z_moments_for_each/element_lengths)*self.ddel1__dnodes \ - diags(z_moments_for_each*del1/element_lengths**2)*self.del__dnodes dbm4_dnodes = diags(del0/element_lengths)*dzm_dnodes \ + diags(z_moments_for_each/element_lengths)*self.ddel0__dnodes \ - diags(z_moments_for_each*del0/element_lengths**2)*self.del__dnodes # this is kind of dumb, but I need lil cause I have to re-index to preserve order # the coo column ordering doesn't seem to be deterministic # so I use the original row/col from the pattern as index arrays to # pull the data out in the correct order dswl__dnodes = (self.dswl__dbm3 * dbm3_dnodes + self.dswl__dbm4 * dbm4_dnodes).tolil() data = dswl__dnodes[self.dswl__dnodes_pattern.row, self.dswl__dnodes_pattern.col].toarray().flatten() J['struct_weight_loads', 'nodes'] = -data
def compute_partials(self, inputs, partials): radius = inputs['radius'] disp = inputs['disp'] nodes = inputs['nodes'] T = self.T E = self.E G = self.G x_gl = self.x_gl num_elems = self.ny - 1 for ielem in range(num_elems): # Compute the coordinate delta between the two element end points P0 = nodes[ielem, :] P1 = nodes[ielem + 1, :] dP = P1 - P0 # and its derivatives ddPdP0 = -1.0 * np.eye(3) ddPdP1 = 1.0 * np.eye(3) # Compute the element length and its derivative L = norm(dP) dLddP = norm_d(dP) # unit function converts a vector to a unit vector # calculate the transormation to the local element frame. # We use x_gl to provide a reference axis to reference x_loc = unit(dP) dxdP = unit_d(dP) y_loc = unit(np.cross(x_loc, x_gl)) dtmpdx, dummy = cross_d(x_loc, x_gl) dydtmp = unit_d(np.cross(x_loc, x_gl)) dydP = dydtmp.dot(dtmpdx).dot(dxdP) z_loc = unit(np.cross(x_loc, y_loc)) dtmpdx, dtmpdy = cross_d(x_loc, y_loc) dzdtmp = unit_d(np.cross(x_loc, y_loc)) dzdP = dzdtmp.dot(dtmpdx).dot(dxdP) + dzdtmp.dot(dtmpdy).dot(dydP) T[0, :] = x_loc T[1, :] = y_loc T[2, :] = z_loc #$$$$$$$$$$$$$$$$$$$$$$$$$$ # Original code # $$$$$$$$$$$$ u0x, u0y, u0z = T.dot(disp[ielem, :3]) r0x, r0y, r0z = T.dot(disp[ielem, 3:]) u1x, u1y, u1z = T.dot(disp[ielem + 1, :3]) r1x, r1y, r1z = T.dot(disp[ielem + 1, 3:]) # #$$$$$$$$$$$$$$$$$$$$$$$$$$ # The derivatives of the above code wrt displacement all boil down to sections of the T matrix dxddisp = T[0, :] dyddisp = T[1, :] dzddisp = T[2, :] #The derivatives of the above code wrt T all boil down to sections of the #displacement vector # du0dT = disp[ielem, :3] # dr0dT = disp[ielem, 3:] # du1dT = disp[ielem+1, :3] # dr1dT = disp[ielem+1, 3:] du0dloc = disp[ielem, :3] dr0dloc = disp[ielem, 3:] du1dloc = disp[ielem + 1, :3] dr1dloc = disp[ielem + 1, 3:] #$$$$$$$$$$$$$$$$$$$$$$$$$$ # Original code # $$$$$$$$$$$$ tmp = np.sqrt((r1y - r0y)**2 + (r1z - r0z)**2) sxx0 = E * (u1x - u0x) / L + E * radius[ielem] / L * tmp sxx1 = E * (u0x - u1x) / L + E * radius[ielem] / L * tmp sxt = G * radius[ielem] * (r1x - r0x) / L # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ dtmpdr0y = 1 / tmp * (r1y - r0y) * -1 dtmpdr1y = 1 / tmp * (r1y - r0y) dtmpdr0z = 1 / tmp * (r1z - r0z) * -1 dtmpdr1z = 1 / tmp * (r1z - r0z) # Combine all of the derivtives for tmp dtmpdDisp = np.zeros(2 * 6) dr0xdDisp = np.zeros(2 * 6) dr0ydDisp = np.zeros(2 * 6) dr0zdDisp = np.zeros(2 * 6) dr1xdDisp = np.zeros(2 * 6) dr1ydDisp = np.zeros(2 * 6) dr1zdDisp = np.zeros(2 * 6) idx1 = 3 idx2 = 6 + 3 #r0 term dr0xdDisp[idx1:idx1 + 3] = dxddisp dr0ydDisp[idx1:idx1 + 3] = dyddisp dr0zdDisp[idx1:idx1 + 3] = dzddisp dr1xdDisp[idx2:idx2 + 3] = dxddisp dr1ydDisp[idx2:idx2 + 3] = dyddisp dr1zdDisp[idx2:idx2 + 3] = dzddisp dtmpdDisp = (dtmpdr0y*dr0ydDisp+dtmpdr1y*dr1ydDisp+\ dtmpdr0z*dr0zdDisp+dtmpdr1z*dr1zdDisp) # x_loc, y_loc and z_loc terms dtmpdx_loc = np.array([0, 0, 0]) dtmpdy_loc = dtmpdr0y * dr0dloc + dtmpdr1y * dr1dloc dtmpdz_loc = dtmpdr0z * dr0dloc + dtmpdr1z * dr1dloc dtmpdP = dtmpdx_loc.dot(dxdP) + dtmpdy_loc.dot( dydP) + dtmpdz_loc.dot(dzdP) dsxx0dtmp = E * radius[ielem] / L dsxx0du0x = -E / L dsxx0du1x = E / L dsxx0dL = -E * (u1x - u0x) / (L * L) - E * radius[ielem] / (L * L) * tmp dsxx1dtmp = E * radius[ielem] / L dsxx1du0x = E / L dsxx1du1x = -E / L dsxx1dL = -E * (u0x - u1x) / (L * L) - E * radius[ielem] / (L * L) * tmp dsxx0dP = dsxx0dtmp * dtmpdP + \ dsxx0du0x*du0dloc.dot(dxdP) + dsxx0du1x*du1dloc.dot(dxdP)+\ dsxx0dL*dLddP dsxx1dP = dsxx1dtmp * dtmpdP + \ dsxx1du0x*du0dloc.dot(dxdP)+dsxx1du1x*du1dloc.dot(dxdP)+\ dsxx1dL*dLddP # Combine sxx0 and sxx1 terms idx1 = 0 idx2 = 6 # Start with the tmp term dsxx0dDisp = dsxx0dtmp * dtmpdDisp dsxx1dDisp = dsxx1dtmp * dtmpdDisp # Now add the direct u dep dsxx0dDisp[idx1:idx1 + 3] = dsxx0du0x * dxddisp dsxx0dDisp[idx2:idx2 + 3] = dsxx0du1x * dxddisp dsxx1dDisp[idx1:idx1 + 3] = dsxx1du0x * dxddisp dsxx1dDisp[idx2:idx2 + 3] = dsxx1du1x * dxddisp # Combine sxt term dsxtdDisp = np.zeros(2 * 6) idx1 = 3 idx2 = 6 + 3 dsxtdr0x = -G * radius[ielem] / L dsxtdr1x = G * radius[ielem] / L dsxtdL = -G * radius[ielem] * (r1x - r0x) / (L * L) dsxtdP = dsxtdr0x*(dr0dloc.dot(dxdP))+ dsxtdr1x*(dr1dloc.dot(dxdP))+\ dsxtdL*dLddP #disp dsxtdDisp = dsxtdr0x * dr0xdDisp + dsxtdr1x * dr1xdDisp #radius derivatives dsxx0drad = E / L * tmp dsxx1drad = E / L * tmp dsxtdrad = G * (r1x - r0x) / L dVm0dsxx0 = (sxx0) / (np.sqrt(sxx0**2 + 3 * sxt**2)) dVm0dsxt = (3 * sxt) / (np.sqrt(sxx0**2 + 3 * sxt**2)) dVm1dsxx1 = (sxx1) / (np.sqrt(sxx1**2 + 3 * sxt**2)) dVm1dsxt = (3 * sxt) / (np.sqrt(sxx1**2 + 3 * sxt**2)) idx = ielem * 2 partials['vonmises', 'radius'][ idx, ielem] = dVm0dsxx0 * dsxx0drad + dVm0dsxt * dsxtdrad partials['vonmises', 'radius'][ idx + 1, ielem] = dVm1dsxx1 * dsxx1drad + dVm1dsxt * dsxtdrad idx2 = ielem * 6 partials['vonmises','disp'][idx,idx2:idx2+12] = partials['vonmises','disp'][idx,idx2:idx2+12]+\ (dVm0dsxx0*dsxx0dDisp+dVm0dsxt*dsxtdDisp ) partials['vonmises','disp'][idx+1,idx2:idx2+12] = partials['vonmises','disp'][idx+1,idx2:idx2+12]+\ (dVm1dsxx1*dsxx1dDisp+dVm1dsxt*dsxtdDisp ) # Compute terms for the nodes idx3 = ielem * 3 partials['vonmises', 'nodes'][idx, idx3:idx3 + 3] = partials[ 'vonmises', 'nodes'][idx, idx3:idx3 + 3] + dVm0dsxx0 * dsxx0dP.dot( ddPdP0) + dVm0dsxt * dsxtdP.dot(ddPdP0) partials['vonmises', 'nodes'][idx, idx3 + 3:idx3 + 6] = partials[ 'vonmises', 'nodes'][idx, idx3 + 3:idx3 + 6] + dVm0dsxx0 * dsxx0dP.dot( ddPdP1) + dVm0dsxt * dsxtdP.dot(ddPdP1) partials['vonmises', 'nodes'][idx + 1, idx3:idx3 + 3] = partials[ 'vonmises', 'nodes'][idx + 1, idx3:idx3 + 3] + dVm1dsxx1 * dsxx1dP.dot( ddPdP0) + dVm1dsxt * dsxtdP.dot(ddPdP0) partials['vonmises', 'nodes'][idx + 1, idx3 + 3:idx3 + 6] = partials['vonmises', 'nodes'][ idx + 1, idx3 + 3:idx3 + 6] + dVm1dsxx1 * dsxx1dP.dot( ddPdP1) + dVm1dsxt * dsxtdP.dot(ddPdP1)