def assembly_MB_eq_system(self, MB_beam, MB_tstep, ts, dt, Lambda, Lambda_dot, MBdict): self.lc_list = lagrangeconstraints.initialize_constraints(MBdict) self.num_LM_eq = lagrangeconstraints.define_num_LM_eq(self.lc_list) MB_M = np.zeros( (self.sys_size + self.num_LM_eq, self.sys_size + self.num_LM_eq), dtype=ct.c_double, order='F') MB_C = np.zeros( (self.sys_size + self.num_LM_eq, self.sys_size + self.num_LM_eq), dtype=ct.c_double, order='F') MB_K = np.zeros( (self.sys_size + self.num_LM_eq, self.sys_size + self.num_LM_eq), dtype=ct.c_double, order='F') MB_Asys = np.zeros( (self.sys_size + self.num_LM_eq, self.sys_size + self.num_LM_eq), dtype=ct.c_double, order='F') MB_Q = np.zeros((self.sys_size + self.num_LM_eq, ), dtype=ct.c_double, order='F') #ipdb.set_trace() first_dof = 0 last_dof = 0 # Loop through the different bodies for ibody in range(len(MB_beam)): # Initialize matrices M = None C = None K = None Q = None # Generate the matrices for each body if MB_beam[ibody].FoR_movement == 'prescribed': last_dof = first_dof + MB_beam[ibody].num_dof.value M, C, K, Q = xbeamlib.cbeam3_asbly_dynamic( MB_beam[ibody], MB_tstep[ibody], self.settings) elif MB_beam[ibody].FoR_movement == 'free': last_dof = first_dof + MB_beam[ibody].num_dof.value + 10 M, C, K, Q = xbeamlib.xbeam3_asbly_dynamic( MB_beam[ibody], MB_tstep[ibody], self.settings) ############### Assembly into the global matrices # Flexible and RBM contribution to Asys MB_M[first_dof:last_dof, first_dof:last_dof] = M.astype(dtype=ct.c_double, copy=True, order='F') MB_C[first_dof:last_dof, first_dof:last_dof] = C.astype(dtype=ct.c_double, copy=True, order='F') MB_K[first_dof:last_dof, first_dof:last_dof] = K.astype(dtype=ct.c_double, copy=True, order='F') #Q MB_Q[first_dof:last_dof] = Q first_dof = last_dof # Define the number of equations # Generate matrices associated to Lagrange multipliers LM_C, LM_K, LM_Q = lagrangeconstraints.generate_lagrange_matrix( self.lc_list, MB_beam, MB_tstep, ts, self.num_LM_eq, self.sys_size, dt, Lambda, Lambda_dot, "dynamic") # Include the matrices associated to Lagrange Multipliers MB_C += LM_C MB_K += LM_K MB_Q += LM_Q MB_Asys = MB_K + MB_C * self.gamma / (self.beta * dt) + MB_M / ( self.beta * dt * dt) return MB_Asys, MB_Q
def run(self): r""" Extracts the eigenvalues and eigenvectors of the clamped structure. If ``use_undamped_modes == True`` then the free vibration modes of the clamped structure are found solving: .. math:: \mathbf{M\,\ddot{\eta}} + \mathbf{K\,\eta} = 0 that flows down to solving the non-trivial solutions to: .. math:: (-\omega_n^2\,\mathbf{M} + \mathbf{K})\mathbf{\Phi} = 0 On the other hand, if the damped modes are chosen because the system has damping, the free vibration modes are found solving the equation of motion of the form: .. math:: \mathbf{M\,\ddot{\eta}} + \mathbf{C\,\dot{\eta}} + \mathbf{K\,\eta} = 0 which can be written in state space form, with the state vector :math:`\mathbf{x} = [\eta^T,\,\dot{\eta}^T]^T` as .. math:: \mathbf{\dot{x}} = \begin{bmatrix} 0 & \mathbf{I} \\ -\mathbf{M^{-1}K} & -\mathbf{M^{-1}C} \end{bmatrix} \mathbf{x} and therefore the mode shapes and frequencies correspond to the solution of the eigenvalue problem .. math:: \mathbf{A\,\Phi} = \mathbf{\Lambda\,\Phi}. From the eigenvalues, the following system characteristics are provided: * Natural Frequency: :math:`\omega_n = |\lambda|` * Damped natural frequency: :math:`\omega_d = \text{Im}(\lambda) = \omega_n \sqrt{1-\zeta^2}` * Damping ratio: :math:`\zeta = -\frac{\text{Re}(\lambda)}{\omega_n}` In addition to the above, the modal output dictionary includes the following: * ``M``: Tangent mass matrix * ``C``: Tangent damping matrix * ``K``: Tangent stiffness matrix * ``Ccut``: Modal damping matrix :math:`\mathbf{C}_m = \mathbf{\Phi}^T\mathbf{C}\mathbf{\Phi}` * ``Kin_damp``: Forces gain matrix (when damped): :math:`K_{in} = \mathbf{\Phi}_L^T \mathbf{M}^{-1}` * ``eigenvectors``: Right eigenvectors * ``eigenvectors_left``: Left eigenvectors given when the system is damped Returns: PreSharpy: updated data object with modal analysis as part of the last structural time step. """ # Number of degrees of freedom num_str_dof = self.data.structure.num_dof.value if self.rigid_body_motion: num_rigid_dof = 10 else: num_rigid_dof = 0 num_dof = num_str_dof + num_rigid_dof # if NumLambda # Initialize matrices FullMglobal = np.zeros((num_dof, num_dof), dtype=ct.c_double, order='F') FullKglobal = np.zeros((num_dof, num_dof), dtype=ct.c_double, order='F') FullCglobal = np.zeros((num_dof, num_dof), dtype=ct.c_double, order='F') if self.rigid_body_motion: # Settings for the assembly of the matrices # try: # full_matrix_settings = self.data.settings['StaticCoupled']['structural_solver_settings'] # full_matrix_settings['dt'] = ct.c_double(0.01) # Dummy: required but not used # full_matrix_settings['newmark_damp'] = ct.c_double(1e-2) # Dummy: required but not used # except KeyError: # full_matrix_settings = self.data.settings['DynamicCoupled']['structural_solver_settings'] import sharpy.solvers._basestructural as basestructuralsolver full_matrix_settings = basestructuralsolver._BaseStructural().settings_default settings.to_custom_types(full_matrix_settings, basestructuralsolver._BaseStructural().settings_types, full_matrix_settings) # Obtain the tangent mass, damping and stiffness matrices FullMglobal, FullCglobal, FullKglobal, FullQ = xbeamlib.xbeam3_asbly_dynamic(self.data.structure, self.data.structure.timestep_info[self.data.ts], full_matrix_settings) else: xbeamlib.cbeam3_solv_modal(self.data.structure, self.settings, self.data.ts, FullMglobal, FullCglobal, FullKglobal) # Print matrices if self.settings['print_matrices'].value: np.savetxt(self.folder + "Mglobal.dat", FullMglobal, fmt='%.12f', delimiter='\t', newline='\n') np.savetxt(self.folder + "Cglobal.dat", FullCglobal, fmt='%.12f', delimiter='\t', newline='\n') np.savetxt(self.folder + "Kglobal.dat", FullKglobal, fmt='%.12f', delimiter='\t', newline='\n') # Check if the damping matrix is zero (issue working) if self.settings['use_undamped_modes'].value: zero_FullCglobal = True for i,j in itertools.product(range(num_dof),range(num_dof)): if(np.absolute(FullCglobal[i, j]) > np.finfo(float).eps): zero_FullCglobal = False warnings.warn( 'Projecting a system with damping on undamped modal shapes') break # Check if the damping matrix is skew-symmetric # skewsymmetric_FullCglobal = True # for i in range(num_dof): # for j in range(i:num_dof): # if((i==j) and (np.absolute(FullCglobal[i, j]) > np.finfo(float).eps)): # skewsymmetric_FullCglobal = False # elif(np.absolute(FullCglobal[i, j] + FullCglobal[j, i]) > np.finfo(float).eps): # skewsymmetric_FullCglobal = False NumLambda = min(num_dof, self.settings['NumLambda'].value) if self.settings['use_undamped_modes'].value: # Solve for eigenvalues (with unit eigenvectors) eigenvalues,eigenvectors=np.linalg.eig( np.linalg.solve(FullMglobal,FullKglobal)) eigenvectors_left=None # Define vibration frequencies and damping freq_natural = np.sqrt(eigenvalues) order = np.argsort(freq_natural)[:NumLambda] freq_natural = freq_natural[order] #freq_damped = freq_natural eigenvalues = eigenvalues[order] eigenvectors = eigenvectors[:,order] damping = np.zeros((NumLambda,)) else: # State-space model Minv_neg = -np.linalg.inv(FullMglobal) A = np.zeros((2*num_dof, 2*num_dof), dtype=ct.c_double, order='F') A[:num_dof, num_dof:] = np.eye(num_dof) A[num_dof:, :num_dof] = np.dot(Minv_neg, FullKglobal) A[num_dof:, num_dof:] = np.dot(Minv_neg, FullCglobal) # Solve the eigenvalues problem eigenvalues, eigenvectors_left, eigenvectors = \ sc.linalg.eig(A,left=True,right=True) freq_natural = np.abs(eigenvalues) damping = np.zeros_like(freq_natural) iiflex = freq_natural > 1e-16*np.mean(freq_natural) # Pick only structural modes damping[iiflex] = -eigenvalues[iiflex].real/freq_natural[iiflex] freq_damped = freq_natural * np.sqrt(1-damping**2) # Order & downselect complex conj: # this algorithm assumes that complex conj eigenvalues appear consecutively # in eigenvalues. For symmetrical systems, this relies on the fact that: # - complex conj eigenvalues have the same absolute value (to machine # precision) # - couples of eigenvalues with multiplicity higher than 1, show larger # numerical difference order = np.argsort(freq_damped)[:2*NumLambda] freq_damped = freq_damped[order] freq_natural = freq_natural[order] eigenvalues = eigenvalues[order] include = np.ones((2*NumLambda,), dtype=np.bool) ii = 0 tol_rel = np.finfo(float).eps * freq_damped[ii] while ii < 2*NumLambda: # check complex if np.abs(eigenvalues[ii].imag) > 0.: if np.abs(eigenvalues[ii+1].real-eigenvalues[ii].real) > tol_rel or\ np.abs(eigenvalues[ii+1].imag+eigenvalues[ii].imag) > tol_rel: raise NameError('Complex conjugate expected but not found!') ii += 1 try: include[ii] = False except IndexError: pass ii += 1 freq_damped = freq_damped[include] eigenvalues = eigenvalues[include] if self.settings['continuous_eigenvalues']: if self.settings['dt'].value == 0.: raise ValueError('Cannot compute the continuous eigenvalues without a dt value') eigenvalues = np.log(eigenvalues)/self.settings['dt'].value order = order[include] damping = damping[order] eigenvectors = eigenvectors[:, order] eigenvectors_left = eigenvectors_left[:, order].conj() # Modify rigid body modes for them to be defined wrt the CG if self.settings['rigid_modes_cg']: if not eigenvectors_left: eigenvectors = self.free_free_modes(eigenvectors, FullMglobal) # Scaling eigenvectors, eigenvectors_left = self.scale_modes_unit_mass_matrix(eigenvectors, FullMglobal, eigenvectors_left) # Other terms required for state-space realisation # non-zero damping matrix # Modal damping matrix if self.settings['use_undamped_modes'] and not(zero_FullCglobal): Ccut = np.dot(eigenvectors.T, np.dot(FullCglobal, eigenvectors)) else: Ccut = None # forces gain matrix (nodal -> modal) if not self.settings['use_undamped_modes']: Kin_damp = np.dot(eigenvectors_left[num_dof:, :].T, -Minv_neg) else: Kin_damp = None # Plot eigenvalues using matplotlib if specified in settings if self.settings['plot_eigenvalues']: import matplotlib.pyplot as plt fig = plt.figure() plt.scatter(eigenvalues.real, eigenvalues.imag) plt.show() plt.savefig(self.folder + 'eigenvalues.png', transparent=True, bbox_inches='tight') # Write dat files if self.settings['write_dat'].value: if type(eigenvalues) == complex: np.savetxt(self.folder + "eigenvalues.dat", eigenvalues.view(float).reshape(-1, 2), fmt='%.12f', delimiter='\t', newline='\n') else: np.savetxt(self.folder + "eigenvalues.dat", eigenvalues.view(float), fmt='%.12f', delimiter='\t', newline='\n') np.savetxt(self.folder + "eigenvectors.dat", eigenvectors[:num_dof].real, fmt='%.12f', delimiter='\t', newline='\n') if not self.settings['use_undamped_modes'].value: np.savetxt(self.folder + 'frequencies.dat', freq_damped[:NumLambda], fmt='%e', delimiter='\t', newline='\n') else: np.savetxt(self.folder + 'frequencies.dat', freq_natural[:NumLambda], fmt='%e', delimiter='\t', newline='\n') np.savetxt(self.filename_damp, damping[:NumLambda], fmt='%e', delimiter='\t', newline='\n') # Write vtk if self.settings['write_modes_vtk'].value: try: self.data.aero aero_model = True except AttributeError: warnings.warn('No aerodynamic model found - unable to project the mode onto aerodynamic grid') aero_model = False if aero_model: modalutils.write_modes_vtk( self.data, eigenvectors[:num_dof], NumLambda, self.filename_shapes, self.settings['max_rotation_deg'], self.settings['max_displacement'], ts=self.settings['use_custom_timestep'].value) outdict = dict() if self.settings['use_undamped_modes']: outdict['modes'] = 'undamped' outdict['freq_natural'] = freq_natural if not zero_FullCglobal: outdict['warning'] =\ 'system with damping: mode shapes and natural frequencies do not account for damping!' else: outdict['modes'] = 'damped' outdict['freq_damped'] = freq_damped outdict['freq_natural'] = freq_natural outdict['damping'] = damping outdict['eigenvalues'] = eigenvalues outdict['eigenvectors'] = eigenvectors if Ccut is not None: outdict['Ccut'] = Ccut if Kin_damp is not None: outdict['Kin_damp'] = Kin_damp if not self.settings['use_undamped_modes']: outdict['eigenvectors_left'] = eigenvectors_left if self.settings['keep_linear_matrices'].value: outdict['M'] = FullMglobal outdict['C'] = FullCglobal outdict['K'] = FullKglobal self.data.structure.timestep_info[self.data.ts].modal = outdict if self.settings['print_info']: if self.settings['use_undamped_modes']: self.eigenvalue_table.print_evals(np.sqrt(eigenvalues[:NumLambda])*1j) else: self.eigenvalue_table.print_evals(eigenvalues[:NumLambda]) return self.data
def assembly_MB_eq_system(self, MB_beam, MB_tstep, ts, dt, Lambda, Lambda_dot, MBdict): """ This function generates the matrix and vector associated to the linear system to solve a structural iteration It usses a Newmark-beta scheme for time integration. Being M, C and K the mass, damping and stiffness matrices of the system: .. math:: MB_Asys = MB_K + MB_C \frac{\gamma}{\beta dt} + \frac{1}{\beta dt^2} MB_M Args: MB_beam (list(:class:`~sharpy.structure.models.beam.Beam`)): each entry represents a body MB_tstep (list(:class:`~sharpy.utils.datastructures.StructTimeStepInfo`)): each entry represents a body ts (int): Time step number dt(int): time step Lambda (np.ndarray): Lagrange Multipliers array Lambda_dot (np.ndarray): Time derivarive of ``Lambda`` MBdict (dict): Dictionary including the multibody information Returns: MB_Asys (np.ndarray): Matrix of the systems of equations MB_Q (np.ndarray): Vector of the systems of equations """ self.num_LM_eq = lagrangeconstraints.define_num_LM_eq(self.lc_list) MB_M = np.zeros( (self.sys_size + self.num_LM_eq, self.sys_size + self.num_LM_eq), dtype=ct.c_double, order='F') MB_C = np.zeros( (self.sys_size + self.num_LM_eq, self.sys_size + self.num_LM_eq), dtype=ct.c_double, order='F') MB_K = np.zeros( (self.sys_size + self.num_LM_eq, self.sys_size + self.num_LM_eq), dtype=ct.c_double, order='F') MB_Asys = np.zeros( (self.sys_size + self.num_LM_eq, self.sys_size + self.num_LM_eq), dtype=ct.c_double, order='F') MB_Q = np.zeros((self.sys_size + self.num_LM_eq, ), dtype=ct.c_double, order='F') first_dof = 0 last_dof = 0 # Loop through the different bodies for ibody in range(len(MB_beam)): # Initialize matrices M = None C = None K = None Q = None # Generate the matrices for each body if MB_beam[ibody].FoR_movement == 'prescribed': last_dof = first_dof + MB_beam[ibody].num_dof.value M, C, K, Q = xbeamlib.cbeam3_asbly_dynamic( MB_beam[ibody], MB_tstep[ibody], self.settings) elif MB_beam[ibody].FoR_movement == 'free': last_dof = first_dof + MB_beam[ibody].num_dof.value + 10 M, C, K, Q = xbeamlib.xbeam3_asbly_dynamic( MB_beam[ibody], MB_tstep[ibody], self.settings) ############### Assembly into the global matrices # Flexible and RBM contribution to Asys MB_M[first_dof:last_dof, first_dof:last_dof] = M.astype(dtype=ct.c_double, copy=True, order='F') MB_C[first_dof:last_dof, first_dof:last_dof] = C.astype(dtype=ct.c_double, copy=True, order='F') MB_K[first_dof:last_dof, first_dof:last_dof] = K.astype(dtype=ct.c_double, copy=True, order='F') #Q MB_Q[first_dof:last_dof] = Q first_dof = last_dof # Define the number of equations # Generate matrices associated to Lagrange multipliers LM_C, LM_K, LM_Q = lagrangeconstraints.generate_lagrange_matrix( self.lc_list, MB_beam, MB_tstep, ts, self.num_LM_eq, self.sys_size, dt, Lambda, Lambda_dot, "dynamic") # Include the matrices associated to Lagrange Multipliers MB_C += LM_C MB_K += LM_K MB_Q += LM_Q MB_Asys = MB_K + MB_C * self.gamma / (self.beta * dt) + MB_M / ( self.beta * dt * dt) return MB_Asys, MB_Q