def __init__(self, **kwargs): """Simulation interface. Used to apply loads on laminate and compute resulting strains/stresses. Workflow : - sim = Sim(laminate = Laminate(...)) - sim.apply_N([a,b,c] * ureg.GNperm) - sim.apply_M([d,e,f] * ureg.GN) - sim.solve() - sim.return_results() """ # if isinstance(laminate,Laminate): # self.laminate = laminate # else: # raise TypeError("Input argument must be of type 'composites.Laminate'") x = kwargs try: self.laminate = x['laminate'] except AttributeError as e: #['laminate'] does not contain a Laminate raise AttributeError("'laminate' must point to 'Laminate' object \n"+e.message) except KeyError: try: self.laminate = Laminate(x['layup'],x['materialID']) except KeyError: raise KeyError("Could not find keyword args 'laminate' or 'layup' and 'id'") self.laminate.compute_all() self.e0 = numpy.zeros((3,)) #Average strain from overall in-plane stress 'N' self.e_k = None self.solved = False self.loaded = False
names = ('max', 'quad', 'hash') for cols, name in zip(all_cols, names): R_min_data = find_R_mins(self.R_data, cols) the_min_idx = R_min_data['Lowest R'].idxmin() minimum_R[name] = tuple(R_min_data[['Lowest R', 'Mode']].iloc[the_min_idx]) return minimum_R if __name__ == '__main__': import numpy #Create a Simulation object composed of a laminate. sim = Sim(laminate=Laminate( '0_2/p25/0_2s', materialID=5, #5 core_thick=0.01)) #Define and apply load P = Q_(1000, 'N') b = Q_(0.11, 'm') L = Q_(0.51, 'm') M1 = -P * L / (4 * b) N1 = 0.5 * P / b M = Q_([M1.magnitude, 0, 0], M1.units) N = Q_([N1.magnitude, 0, 0], N1.units) #Apply load sim.apply_M(M) sim.apply_N(N) sim.solve() fail = FailureAnalysis(sim) R_data = fail.make_table()
class Sim(object): WorkflowError = WorkflowError def __init__(self, **kwargs): """Simulation interface. Used to apply loads on laminate and compute resulting strains/stresses. Workflow : - sim = Sim(laminate = Laminate(...)) - sim.apply_N([a,b,c] * ureg.GNperm) - sim.apply_M([d,e,f] * ureg.GN) - sim.solve() - sim.return_results() """ # if isinstance(laminate,Laminate): # self.laminate = laminate # else: # raise TypeError("Input argument must be of type 'composites.Laminate'") x = kwargs try: self.laminate = x['laminate'] except AttributeError as e: #['laminate'] does not contain a Laminate raise AttributeError("'laminate' must point to 'Laminate' object \n"+e.message) except KeyError: try: self.laminate = Laminate(x['layup'],x['materialID']) except KeyError: raise KeyError("Could not find keyword args 'laminate' or 'layup' and 'id'") self.laminate.compute_all() self.e0 = numpy.zeros((3,)) #Average strain from overall in-plane stress 'N' self.e_k = None self.solved = False self.loaded = False @ureg.wraps(None, (None,ureg.GNperm)) def apply_N(self,N_input,do_return = False): """Obtain off-axis strain from applied 3D average load N to laminate. Load N must be 3-dimensional AND use units [Force/length]. Converts to GN/m. """ try: N = numpy.array(N_input,dtype=float).reshape(3,) except ValueError: try: N = numpy.array([N_input,0,0],dtype=float).reshape(3,) except: # raise ValueError("Could not reshape %r to a 'float' 3D array" % N_input) raise self.e0 = self.laminate.a.dot(N) self.loaded = True if do_return: return self.e0 @ureg.wraps(None, (None,ureg.GN,None)) def apply_M(self,M_input,do_return = False): """Apply moment M to laminate. Moment M must be 3-dimensional AND use units [Force]. Converts to GN. """ try: M = numpy.array(M_input,dtype=float).reshape(3,) except ValueError: try: M = numpy.array([M_input,0,0],dtype=float).reshape(3,) except: raise ValueError("Could not reshape %r to a 'float' 3D array" % M_input) self.k = self.laminate.d.dot(M) self.e_k = numpy.empty((self.laminate.num_of_layers(),2,3), dtype = float) for layer in self.laminate.layers: z_bot,z_top = layer.get_z() index = layer.index self.e_k[index,:,:] = numpy.vstack((z_bot*self.k, z_top*self.k) ) self.loaded = True if do_return: return self.k,self.e_k def _compute_off_strain(self): # pos = ['bot','top']{ # self.off_strain = {} # for layer in self.laminate.layers: # index = layer.index # self.off_strain['%s_%s' % (index,pos[0])] = self.e0 + self.e_k[index*2] # self.off_strain['%s_%s' % (index,pos[1])] = self.e0 + self.e_k[index*2+1]} self.off_strain = numpy.empty((self.laminate.num_of_layers(),2,3), dtype = float) if self.e_k is None: self.off_strain[:,:,:] = self.e0 else: self.off_strain[:,:,:] = self.e_k + self.e0 def _compute_on_strain(self): self.on_strain = numpy.empty_like(self.off_strain) for layer in self.laminate.layers: on_strain_bot = transform_strain(self.off_strain[layer.index,0,:], 'off', layer.theta) on_strain_top = transform_strain(self.off_strain[layer.index,1,:], 'off', layer.theta) self.on_strain[layer.index,:,:] = numpy.vstack((on_strain_bot, on_strain_top )) def _compute_on_stress(self): Q_on = self.laminate.layers[0].Q_on trans = lambda x:numpy.vstack(x).T og_shape = numpy.shape(self.on_strain) self.on_stress = Q_on.dot(trans(self.on_strain)).T.reshape(og_shape) def _compute_off_stress(self): self.off_stress = numpy.empty_like(self.on_stress) for layer in self.laminate.layers: off_stress_bot = transform_stress(self.on_stress[layer.index,0,:], 'on', layer.theta) off_stress_top = transform_stress(self.on_stress[layer.index,1,:], 'on', layer.theta) self.off_stress[layer.index,:,:] = numpy.vstack((off_stress_bot, off_stress_top )) def solve(self): """Solve for the laminate strains/stresses due to applied N and M given by apply_N and apply_M """ if not self.loaded: raise WorkflowError("Nothing to solve. Apply loads before solving.") self._compute_off_strain() self._compute_on_strain() self._compute_on_stress() self._compute_off_stress() self.solved = True def get_stress(self): """Returns stress (with units) as ndarray of shape (n,2,3) where n is the number of plies in the laminate. Since only stress is used for failure theory, not necessary to create return methods for other quantities. """ if not self.solved: raise WorkflowError ("Must solve first. Apply loads and solve() first.") return self.on_stress def return_results(self, in_latex = True,precision = 4): """Return results as a pandas DataFrame object in_latex : Returns column and indices as latex strings (default True). """ if not self.solved: try: self.solve() except WorkflowError: raise WorkflowError("Loads must be applied first.") import numpy as np import pandas as pd import sympy off_strain = np.vstack(self.off_strain) on_strain = np.vstack(self.on_strain) on_stress = np.vstack(self.on_stress) off_stress = np.vstack(self.off_stress) stacks = np.hstack((off_strain,on_strain,on_stress)) # stacks = np.round(np.hstack((off_strain, # on_strain, # on_stress, # )), # decimals = precision # ) if in_latex: make_columns = lambda *args: [sympy.latex('$%s$'%arg) for arg in args] columns = make_columns('\epsilon_1','\epsilon_2','\epsilon_6', '\epsilon_x','\epsilon_y','\epsilon_s', '\sigma_x','\sigma_y','\sigma_s') rows = [ sympy.latex('%i (%i$^\circ$) - %s' % (layer.index+1,layer.theta,pos)) \ for layer in self.laminate.layers for pos in ['B','T']] df = pd.DataFrame(data = stacks ,columns=columns ,dtype = float ) df['Ply'] = rows rearanged_cols = ['Ply']+columns df = df[rearanged_cols] else: columns = ('epsilon_1','epsilon_2','epsilon_6', 'epsilon_x','epsilon_y','epsilon_s', 'sigma_x','sigma_y','sigma_s') plies = [ ('%i (%i) - %s' % (layer.index+1,layer.theta,pos) ) for layer in self.laminate.layers for pos in ['B','T'] ] df = pd.DataFrame(data = stacks ,columns=columns ,dtype = float ) df['Ply'] = plies df.set_index('Ply',inplace = True) return df
'epsilon_x','epsilon_y','epsilon_s', 'sigma_x','sigma_y','sigma_s') plies = [ ('%i (%i) - %s' % (layer.index+1,layer.theta,pos) ) for layer in self.laminate.layers for pos in ['B','T'] ] df = pd.DataFrame(data = stacks ,columns=columns ,dtype = float ) df['Ply'] = plies df.set_index('Ply',inplace = True) return df def make_test_sim(): layup = '0/90'; matID = 1 a_sim = Sim(layup = layup,materialID = matID) a_sim.apply_N([1,0,0]*ureg.Nperm) a_sim.solve() return a_sim if __name__ == '__main__': # sigma_on = transform_strain([0.0659,-0.0471,-0.0832],'off',30, do_debug = True) # print sigma_on layups = ['0/90/0/90s','0/45/0/-45s','0/45/0/90s','0/-45/0/45s','p45s'] for layup in layups: sim = Sim(laminate = Laminate(layup,1)) sim.apply_M([1,0,0]*ureg.GN) sim.solve() sim.return_stress()