class MATS2DMicroplaneDamage(MATSXDMicroplaneDamage, MATS2DEval): implements(IMATSEval) # number of spatial dimensions # n_dim = Constant(2) # number of components of engineering tensor representation # n_eng = Constant(3) # planar constraint stress_state = Enum("plane_strain", "plane_stress") # Specify the class to use for directional dependence mfn_class = Class(MFnPolar) # get the normal vectors of the microplanes _MPN = Property(depends_on='n_mp') @cached_property def _get__MPN(self): return array([[cos(alpha), sin(alpha)] for alpha in self.alpha_list]) # get the weights of the microplanes _MPW = Property(depends_on='n_mp') @cached_property def _get__MPW(self): return ones(self.n_mp) / self.n_mp * 2 elasticity_tensors = Property(depends_on='E, nu, stress_state') @cached_property def _get_elasticity_tensors(self): ''' Intialize the fourth order elasticity tensor for 3D or 2D plane strain or 2D plane stress ''' # ---------------------------------------------------------------------------- # Lame constants calculated from E and nu # ---------------------------------------------------------------------------- E = self.E nu = self.nu # first Lame paramter la = E * nu / ((1 + nu) * (1 - 2 * nu)) # second Lame parameter (shear modulus) mu = E / (2 + 2 * nu) # ----------------------------------------------------------------------------------------------------- # Get the fourth order elasticity and compliance tensors for the 3D-case # ----------------------------------------------------------------------------------------------------- # The following lines correspond to the tensorial expression: # (using numpy functionality in order to avoid the loop): # # D4_e_3D = zeros((3,3,3,3),dtype=float) # C4_e_3D = zeros((3,3,3,3),dtype=float) # delta = identity(3) # for i in range(0,3): # for j in range(0,3): # for k in range(0,3): # for l in range(0,3): # # elasticity tensor (cf. Jir/Baz Inelastic analysis of structures Eq.D25): # D4_e_3D[i,j,k,l] = la * delta[i,j] * delta[k,l] + \ # mu * ( delta[i,k] * delta[j,l] + delta[i,l] * delta[j,k] ) # # elastic compliance tensor (cf. Simo, Computational Inelasticity, Eq.(2.7.16) AND (2.1.16)): # C4_e_3D[i,j,k,l] = (1+nu)/(2*E) * \ # ( delta[i,k] * delta[j,l] + delta[i,l]* delta[j,k] ) - \ # nu / E * delta[i,j] * delta[k,l] # # NOTE: swapaxes returns a reference not a copy! # (the index notation always refers to the initial indexing (i=0,j=1,k=2,l=3)) delta = identity(3) delta_ijkl = outer(delta, delta).reshape(3, 3, 3, 3) delta_ikjl = delta_ijkl.swapaxes(1, 2) delta_iljk = delta_ikjl.swapaxes(2, 3) D4_e_3D = la * delta_ijkl + mu * (delta_ikjl + delta_iljk) C4_e_3D = -nu / E * delta_ijkl + \ (1 + nu) / (2 * E) * (delta_ikjl + delta_iljk) # ----------------------------------------------------------------------------------------------------- # Get the fourth order elasticity and compliance tensors for the 2D-case # ----------------------------------------------------------------------------------------------------- # 1. step: Get the (6x6)-elasticity and compliance matrices # for the 3D-case: D2_e_3D = map3d_tns4_to_tns2(D4_e_3D) C2_e_3D = map3d_tns4_to_tns2(C4_e_3D) # 2. step: Get the (3x3)-elasticity and compliance matrices # for the 2D-cases plane stress and plane strain: D2_e_2D_plane_stress = get_D_plane_stress(D2_e_3D) D2_e_2D_plane_strain = get_D_plane_strain(D2_e_3D) C2_e_2D_plane_stress = get_C_plane_stress(C2_e_3D) C2_e_2D_plane_strain = get_C_plane_strain(C2_e_3D) if self.stress_state == 'plane_stress': D2_e = D2_e_2D_plane_stress if self.stress_state == 'plane_strain': D2_e = D2_e_2D_plane_strain # 3. step: Get the fourth order elasticity and compliance tensors # for the 2D-cases plane stress and plane strain (D4.shape = (2,2,2,2)) D4_e_2D_plane_stress = map2d_tns2_to_tns4(D2_e_2D_plane_stress) D4_e_2D_plane_strain = map2d_tns2_to_tns4(D2_e_2D_plane_strain) C4_e_2D_plane_stress = map2d_tns2_to_tns4(C2_e_2D_plane_stress) C4_e_2D_plane_strain = map2d_tns2_to_tns4(C2_e_2D_plane_strain) # ----------------------------------------------------------------------------------------------------- # assign the fourth order elasticity and compliance tensors as return values # ----------------------------------------------------------------------------------------------------- if self.stress_state == 'plane_stress': # print 'stress state: plane-stress' D4_e = D4_e_2D_plane_stress C4_e = C4_e_2D_plane_stress if self.stress_state == 'plane_strain': # print 'stress state: plane-strain' D4_e = D4_e_2D_plane_strain C4_e = C4_e_2D_plane_strain return D4_e, C4_e, D2_e def _get_explorer_config(self): '''Get the specific configuration of this material model in the explorer ''' c = super(MATS2DMicroplaneDamage, self)._get_explorer_config() from ibvpy.mats.mats2D.mats2D_rtrace_cylinder import MATS2DRTraceCylinder # overload the default configuration c['rtrace_list'] += [ MATS2DRTraceCylinder(name='Laterne', var_axis='time', idx_axis=0, var_surface='microplane_damage', record_on='update'), ] return c #------------------------------------------------------------------------- # Dock-based view with its own id #------------------------------------------------------------------------- traits_view = View(Include('polar_fn_group'), dock='tab', id='ibvpy.mats.mats3D.mats_2D_cmdm.MATS2D_cmdm', kind='modal', resizable=True, scrollable=True, width=0.6, height=0.8, buttons=['OK', 'Cancel'])
class FETSLSEval(FETSEval): x_slice = slice(0, 0) parent_fets = Instance(FETSEval) nip_disc = Int(0) #number of integration points on the discontinuity def setup(self, sctx, n_ip): ''' overloading the default method mats state array has to account for different number of ip in elements Perform the setup in the all integration points. TODO: original setup can be used after adaptation the ip_coords param ''' # print 'n_ip ', n_ip # print 'self.m_arr_size ',self.m_arr_size # print 'shape ',sctx.elem_state_array.shape for i in range(n_ip): sctx.mats_state_array = sctx.elem_state_array[( i * self.m_arr_size):((i + 1) * self.m_arr_size)] self.mats_eval.setup(sctx) n_nodes = Property #TODO: define dependencies @cached_property def _get_n_nodes(self): return self.parent_fets.n_e_dofs / self.parent_fets.n_nodal_dofs #dots_class = DOTSUnstructuredEval dots_class = Class(DOTSEval) int_order = Int(1) mats_eval = Delegate('parent_fets') mats_eval_pos = Trait(None, Instance(IMATSEval)) mats_eval_neg = Trait(None, Instance(IMATSEval)) mats_eval_disc = Trait(None, Instance(IMATSEval)) dim_slice = Delegate('parent_fets') dof_r = Delegate('parent_fets') geo_r = Delegate('parent_fets') n_nodal_dofs = Delegate('parent_fets') n_e_dofs = Delegate('parent_fets') get_dNr_mtx = Delegate('parent_fets') get_dNr_geo_mtx = Delegate('parent_fets') get_N_geo_mtx = Delegate('parent_fets') def get_B_mtx(self, r_pnt, X_mtx, node_ls_values, r_ls_value): B_mtx = self.parent_fets.get_B_mtx(r_pnt, X_mtx) return B_mtx def get_u(self, sctx, u): N_mtx = self.parent_fets.get_N_mtx(sctx.loc) return dot(N_mtx, u) def get_eps_eng(self, sctx, u): B_mtx = self.parent_fets.get_B_mtx(sctx.loc, sctx.X) return dot(B_mtx, u) dof_r = Delegate('parent_fets') geo_r = Delegate('parent_fets') node_ls_values = Array(float) tri_subdivision = Int(0) def get_triangulation(self, point_set): dim = point_set[0].shape[1] n_add = 3 - dim if dim == 1: #sideway for 1D structure = [ array([ min(point_set[0]), max(point_set[0]), min(point_set[1]), max(point_set[1]) ], dtype=float), array([[0, 1], [2, 3]], dtype=int) ] return structure points_list = [] triangles_list = [] point_offset = 0 for pts in point_set: if self.tri_subdivision == 1: new_pt = average(pts, 0) pts = vstack((pts, new_pt)) if n_add > 0: points = hstack( [pts, zeros([pts.shape[0], n_add], dtype='float_')]) # Create a polydata with the points we just created. profile = tvtk.PolyData(points=points) # Perform a 2D Delaunay triangulation on them. delny = tvtk.Delaunay2D(input=profile, offset=1.e1) tri = delny.output tri.update() #initiate triangulation triangles = array(tri.polys.data, dtype=int_) pt = tri.points.data tri = (triangles.reshape((triangles.shape[0] / 4), 4))[:, 1:] points_list += list(pt) triangles_list += list(tri + point_offset) point_offset += len(unique(tri)) #Triangulation points = array(points_list) triangles = array(triangles_list) return [points, triangles] vtk_point_ip_map = Property(Array(Int)) def _get_vtk_point_ip_map(self): ''' mapping of the visualization point to the integration points according to mutual proximity in the local coordinates ''' vtk_pt_arr = zeros((1, 3), dtype='float_') ip_map = zeros(self.vtk_r.shape[0], dtype='int_') for i, vtk_pt in enumerate(self.vtk_r): vtk_pt_arr[0, self.dim_slice] = vtk_pt # get the nearest ip_coord ip_map[i] = argmin(cdist(vtk_pt_arr, self.ip_coords)) return array(ip_map) def get_ip_coords(self, int_triangles, int_order): '''Get the array of integration points''' gps = [] points, triangles = int_triangles if triangles.shape[1] == 1: #0D - points if int_order == 1: gps.append(points[0]) else: raise TraitError, 'does not make sense' elif triangles.shape[1] == 2: #1D - lines if int_order == 1: for id in triangles: gp = average(points[ix_(id)], 0) gps.append(gp) elif int_order == 2: weigths = array([[0.21132486540518713, 0.78867513459481287], [0.78867513459481287, 0.21132486540518713]]) for id in triangles: gps += average( points[ix_( id )], 0, weigths[0] ), \ average( points[ix_( id )], 0, weigths[1] ) else: raise NotImplementedError elif triangles.shape[1] == 3: #2D - triangles if int_order == 1: for id in triangles: gp = average(points[ix_(id)], 0) #print "gp ",gp gps.append(gp) elif int_order == 2: raise NotImplementedError elif int_order == 3: weigths = array([[0.6, 0.2, 0.2], [0.2, 0.6, 0.2], [0.2, 0.2, 0.6]]) for id in triangles: gps += average( points[ix_( id )], 0 ), \ average( points[ix_( id )], 0, weigths[0] ), \ average( points[ix_( id )], 0, weigths[1] ), \ average( points[ix_( id )], 0, weigths[2] ) elif int_order == 4: raise NotImplementedError elif int_order == 5: weigths = array( [[0.0597158717, 0.4701420641, 0.4701420641], \ [0.4701420641, 0.0597158717, 0.4701420641], \ [0.4701420641, 0.4701420641, 0.0597158717], \ [0.7974269853, 0.1012865073, 0.1012865073], \ [0.1012865073, 0.7974269853, 0.1012865073], \ [0.1012865073, 0.1012865073, 0.7974269853]] ) for id in triangles: weigts_sum = False #for debug gps += average( points[ix_( id )], 0 ), \ average( points[ix_( id )], 0, weigths[0], weigts_sum ), \ average( points[ix_( id )], 0, weigths[1], weigts_sum ), \ average( points[ix_( id )], 0, weigths[2], weigts_sum ), \ average( points[ix_( id )], 0, weigths[3], weigts_sum ), \ average( points[ix_( id )], 0, weigths[4], weigts_sum ), \ average( points[ix_( id )], 0, weigths[5], weigts_sum ) else: raise NotImplementedError elif triangles.shape[1] == 4: #3D - tetrahedrons raise NotImplementedError else: raise TraitError, 'unsupported geometric form with %s nodes ' % triangles.shape[ 1] return array(gps, dtype='float_') def get_ip_weights(self, int_triangles, int_order): '''Get the array of integration points''' gps = [] points, triangles = int_triangles if triangles.shape[1] == 1: #0D - points if int_order == 1: gps.append(1.) else: raise TraitError, 'does not make sense' elif triangles.shape[1] == 2: #1D - lines if int_order == 1: for id in triangles: r_pnt = points[ix_(id)] J_det_ip = norm(r_pnt[1] - r_pnt[0]) * 0.5 gp = 2. * J_det_ip gps.append(gp) elif int_order == 2: for id in triangles: r_pnt = points[ix_(id)] J_det_ip = norm(r_pnt[1] - r_pnt[0]) * 0.5 gps += J_det_ip, J_det_ip else: raise NotImplementedError elif triangles.shape[1] == 3: #2D - triangles if int_order == 1: for id in triangles: r_pnt = points[ix_(id)] J_det_ip = self._get_J_det_ip(r_pnt) gp = 1. * J_det_ip #print "gp ",gp gps.append(gp) elif int_order == 2: raise NotImplementedError elif int_order == 3: for id in triangles: r_pnt = points[ix_(id)] J_det_ip = self._get_J_det_ip(r_pnt) gps += -0.5625 * J_det_ip, \ 0.52083333333333337 * J_det_ip, \ 0.52083333333333337 * J_det_ip, \ 0.52083333333333337 * J_det_ip elif int_order == 4: raise NotImplementedError elif int_order == 5: for id in triangles: r_pnt = points[ix_(id)] J_det_ip = self._get_J_det_ip(r_pnt) gps += 0.225 * J_det_ip, 0.1323941527 * J_det_ip, \ 0.1323941527 * J_det_ip, 0.1323941527 * J_det_ip, \ 0.1259391805 * J_det_ip, 0.1259391805 * J_det_ip, \ 0.1259391805 * J_det_ip else: raise NotImplementedError elif triangles.shape[1] == 4: #3D - tetrahedrons raise NotImplementedError else: raise TraitError, 'unsupported geometric form with %s nodes ' % triangles.shape[ 1] return array(gps, dtype='float_') def _get_J_det_ip(self, r_pnt): ''' Helper function just for 2D #todo:3D @param r_pnt: ''' dNr_geo = self.dNr_geo_triangle return det(dot(dNr_geo, r_pnt[:, :2])) / 2. #factor 2 due to triangular form dNr_geo_triangle = Property(Array(float)) @cached_property def _get_dNr_geo_triangle(self): dN_geo = array([[-1., 1., 0.], [-1., 0., 1.]], dtype='float_') return dN_geo def get_corr_pred(self, sctx, u, du, tn, tn1, u_avg=None, B_mtx_grid=None, J_det_grid=None, ip_coords=None, ip_weights=None): ''' Corrector and predictor evaluation. @param u current element displacement vector ''' if J_det_grid == None or B_mtx_grid == None: X_mtx = sctx.X show_comparison = True if ip_coords == None: ip_coords = self.ip_coords show_comparison = False if ip_weights == None: ip_weights = self.ip_weights ### Use for Jacobi Transformation n_e_dofs = self.n_e_dofs K = zeros((n_e_dofs, n_e_dofs)) F = zeros(n_e_dofs) sctx.fets_eval = self ip = 0 for r_pnt, wt in zip(ip_coords, ip_weights): #r_pnt = gp[0] sctx.r_pnt = r_pnt #caching cannot be switched off in the moment # if J_det_grid == None: # J_det = self._get_J_det( r_pnt, X_mtx ) # else: # J_det = J_det_grid[ip, ... ] # if B_mtx_grid == None: # B_mtx = self.get_B_mtx( r_pnt, X_mtx ) # else: # B_mtx = B_mtx_grid[ip, ... ] J_det = J_det_grid[ip, ...] B_mtx = B_mtx_grid[ip, ...] eps_mtx = dot(B_mtx, u) d_eps_mtx = dot(B_mtx, du) sctx.mats_state_array = sctx.elem_state_array[ip * self.m_arr_size:(ip + 1) * self.m_arr_size] #print 'elem state ', sctx.elem_state_array #print 'mats state ', sctx.mats_state_array sctx.r_ls = sctx.ls_val[ip] sig_mtx, D_mtx = self.get_mtrl_corr_pred(sctx, eps_mtx, d_eps_mtx, tn, tn1) k = dot(B_mtx.T, dot(D_mtx, B_mtx)) k *= (wt * J_det) K += k f = dot(B_mtx.T, sig_mtx) f *= (wt * J_det) F += f ip += 1 return F, K def get_J_det(self, r_pnt, X_mtx, ls_nodes, ls_r): #unified interface for caching return array(self._get_J_det(r_pnt, X_mtx), dtype='float_') def get_mtrl_corr_pred(self, sctx, eps_mtx, d_eps, tn, tn1): ls = sctx.r_ls if ls == 0. and self.mats_eval_disc: sig_mtx, D_mtx = self.mats_eval_disc.get_corr_pred( sctx, eps_mtx, d_eps, tn, tn1, ) elif ls > 0. and self.mats_eval_pos: sig_mtx, D_mtx = self.mats_eval_pos.get_corr_pred( sctx, eps_mtx, d_eps, tn, tn1, ) elif ls < 0. and self.mats_eval_neg: sig_mtx, D_mtx = self.mats_eval_neg.get_corr_pred( sctx, eps_mtx, d_eps, tn, tn1, ) else: sig_mtx, D_mtx = self.mats_eval.get_corr_pred( sctx, eps_mtx, d_eps, tn, tn1, ) return sig_mtx, D_mtx
class PolarDiscr(HasTraits): ''' Manager of the microplane arrays. This class is responsible for the generation and initialization and state management of an array of microplanes. Additionally, it can perform the setup of damage function parameters using the value of the microplane integrator object. ''' mfn_class = Class(None) #------------------------------------------------------------------------- # Common parameters for for isotropic and anisotropic damage function specifications #------------------------------------------------------------------------- n_mp = Range(0, 50, 6, label='Number of microplanes', auto_set=False) E = Float(34e+3, label="E", desc="Young's Modulus", auto_set=False, enter_set=True) nu = Float(0.2, label='nu', desc="Poison's ratio", auto_set=False, enter_set=True) c_T = Float( 0.0, label='c_T', desc='fraction of tangential stress accounted on each microplane', auto_set=False, enter_set=True) #------------------------------------------------------------------------- # list of angles #------------------------------------------------------------------------- alpha_list = Property(Array, depends_on='n_mp') @cached_property def _get_alpha_list(self): return array( [Pi / self.n_mp * (i - 0.5) for i in range(1, self.n_mp + 1)]) #------------------------------------------------------------------------- # Damage function specification #------------------------------------------------------------------------- phi_fn = EitherType(klasses=[ PhiFnGeneral, PhiFnGeneralExtended, PhiFnGeneralExtendedExp, PhiFnStrainSoftening, PhiFnStrainHardening, PhiFnStrainHardeningLinear, PhiFnStrainHardeningBezier ]) def _phi_fn_default(self): print 'setting phi_fn default' return PhiFnStrainSoftening(polar_discr=self) def _phi_fn_changed(self): print 'setting phi_fn changed' self.phi_fn.polar_discr = self varied_params = List(Str, []) #------------------------------------------------------------------------- # Management of spatially varying parameters depending on the value of mats_eval #------------------------------------------------------------------------- varpars = Dict def _varpars_default(self): return self._get_varpars() @on_trait_change('phi_fn,varied_params') def _update_varpars(self): self.varpars = self._get_varpars() def _get_varpars(self): ''' reset the varpar list according to the current phi_fn object. ''' params = self.phi_fn.identify_parameters() varset = {} for key in params: par_val = getattr(self.phi_fn, key) varset[key] = VariedParam(phi_fn=self.phi_fn, mats_eval=self, varname=key) if key in self.varied_params: varset[key].switched_on = True return varset varpar_list = Property(List(VariedParam), depends_on='varpars') @cached_property def _get_varpar_list(self): return [self.varpars[key] for key in self.phi_fn.identify_parameters()] # variable selectable in the table of varied params (just for viewing) current_varpar = Instance(VariedParam) def _current_varpar_default(self): if len(self.varpar_list) > 0: return self.varpar_list[0] return None @on_trait_change('phi_fn') def set_current_varpar(self): if len(self.varpar_list) > 0: self.current_varpar = self.varpar_list[0] #------------------------------------------------------------------------- # Get the damage state for all microplanes #------------------------------------------------------------------------- def get_phi_arr(self, sctx, e_max_arr): ''' Return the damage coefficients ''' # gather the coefficients for parameters depending on the orientation carr_list = [ self.varpars[key].polar_fn_vectorized(self.alpha_list) for key in self.phi_fn.identify_parameters() ] # vectorize the damage function evaluation n_arr = 1 + len(carr_list) phi_fn_vectorized = frompyfunc(self.phi_fn.get_value, n_arr, 1) # damage parameter for each microplane return phi_fn_vectorized(e_max_arr, *carr_list) def get_polar_fn_fracture_energy_arr(self, sctx, e_max_arr): ''' Return the fracture energy contributions ''' carr_list = [ self.varpars[key].polar_fn_vectorized(self.alpha_list) for key in self.phi_fn.identify_parameters() ] # vectorize the damage function evaluation n_arr = 1 + len(carr_list) integ_phi_fn_vectorized = frompyfunc(self.phi_fn.get_integ, n_arr, 1) return self.E * integ_phi_fn_vectorized(e_max_arr, *carr_list) polar_fn_group = Group( Group(Item('n_mp@', width=200), Item('E'), Item('nu'), Item('c_T'), Spring(), label='Elasticity parameters'), Group(Item('phi_fn@', show_label=False), label='Damage parameters'), Group(VSplit( Item('varpar_list', label='List of material variables', show_label=False, editor=varpar_editor), Item('current_varpar', label='Selected variable', show_label=False, style='custom', resizable=True), dock='tab', ), label='Angle-dependent variations'), Include('config_param_vgroup'), layout='tabbed', springy=True, dock='tab', id='ibvpy.mats.matsXD_cmdm.MATSXDPolarDiscr', ) traits_view = View(Include('polar_fn_group'), resizable=True, scrollable=True, width=0.6, height=0.9)
class FETSEval(TStepperEval): implements(IFETSEval) dots_class = Class(DOTSEval) dof_r = Array( 'float_', desc='Local coordinates of nodes included in the field ansatz') geo_r = Array( 'float_', desc='Local coordinates of nodes included in the geometry ansatz') n_nodal_dofs = Int(desc='Number of nodal degrees of freedom') id_number = Int mats_eval = Instance(IMATSEval, desc='Material model') #------------------------------------------------------------------------- # Derived info about the finite element formulation #------------------------------------------------------------------------- n_dof_r = Int def _n_dof_r_default(self): return len(self.dof_r) n_geo_r = Int def _n_geo_r_default(self): return len(self.geo_r) #------------------------------------------------------------------------- # Field visualization #------------------------------------------------------------------------- vtk_r = Array( Float, desc='Local coordinates of nodes included in the field visualization') vtk_cell_types = Any( desc= 'Tuple of vtk cell types in the same order as they are specified in the vtk_cells list' ) vtk_cells = List( desc= 'List of maps of nodes constituting the vtk cells covering the single element' ) # Distinguish the type of base geometric entity to be used for # the visualization of the results. # # field_entity_type = Enum('vertex','line','triangle','quad','tetra','hexa') vtk_node_cell_data = Property(depends_on='vtk_cells, vtk_cell_types') @cached_property def _get_vtk_node_cell_data(self): n_cells = len(self.vtk_cells) # check if vtk_cell_types is a list, if not make one if isinstance(self.vtk_cell_types, str): cell_classes = [self.vtk_cell_types for i in range(n_cells)] else: cell_classes = self.vtk_cell_types cell_types = [] for cell_str in cell_classes: cell_class = tvtk_helper.get_class(cell_str) cell_types.append(cell_class().cell_type) if isinstance(self.vtk_cells[0], int): # just a single cell defined return (array([ 0, ], dtype=int), array(self.vtk_cells.shape[0], dtype=int), array(self.vtk_cells, dtype=int), cell_types) offset_list = [] length_list = [] cell_list = [] vtk_offset = 0 for cell in self.vtk_cells: cell_len = len(cell) cell_list += cell length_list.append(cell_len) offset_list.append(vtk_offset) vtk_offset += cell_len + 1 return (array(offset_list, dtype=int), array(length_list, dtype=int), array(cell_list, dtype=int), array(cell_types, dtype=int)) vtk_ip_cell_data = Property(depends_on='vtk_cells, vtk_cell_types') @cached_property def _get_vtk_ip_cell_data(self): n_ip_pnts = self.ip_coords.shape[0] cell_types = array([(tvtk_helper.get_class('PolyVertex')()).cell_type]) return (array([ 0, ], dtype=int), array([n_ip_pnts], dtype=int), arange(n_ip_pnts), cell_types) n_vtk_r = Property(Int, depends_on='vtk_r') @cached_property def _get_n_vtk_r(self): return self.vtk_r.shape[0] n_vtk_cells = Property(Int, depends_on='field_faces') @cached_property def _get_n_vtk_cells(self): return self.field_faces.shape[0] vtk_pnt_ip_map = Property(Array(Int)) @cached_property def _get_vtk_pnt_ip_map(self): return self.get_vtk_pnt_ip_map_data(self.vtk_r) def adjust_spatial_context_for_point(self, sctx): ''' Method gets called prior to the evaluation at the material point level. The method can be used for dimensionally reduced evaluators. This is FETS specific and should be moved there. However, the RTraceEval is not distinguished at the moment, therefore it is here - move!!!. ''' sctx.X_reg = sctx.X def get_vtk_pnt_ip_map_data(self, vtk_r): ''' mapping of the visualization point to the integration points according to mutual proximity in the local coordinates ''' vtk_pt_arr = zeros((1, 3), dtype='float_') ip_map = zeros(vtk_r.shape[0], dtype='int_') for i, vtk_pt in enumerate(vtk_r): vtk_pt_arr[0, self.dim_slice] = vtk_pt[self.dim_slice] # get the nearest ip_coord ip_map[i] = argmin(cdist(vtk_pt_arr, self.ip_coords)) return array(ip_map) #------------------------------------------------------------------------- # NUMERICAL INTEGRATION #------------------------------------------------------------------------- # # The integration grid is constructed using broadcasting # provided by numpy. This provides a loopless implementation # with well defined ordering of gauss points and further the # slicing of gauss points using the numpy indexing slices. # # The old loop-based expansion is preserved below for reference. # gp_r_grid = Property(depends_on='ngp_r,ngp_s,ngp_t') @cached_property def _get_gp_r_grid(self): '''Return a tuple of three arrays for X, Y, Z coordinates of the gauss points within the element. ''' # get the oriented arrays for each direction prepared for broadcasting # gp_coords = [ oriented_3d_array(self._GP_COORDS[ngp], dim) for dim, ngp in enumerate(self.n_gp_list) ] # broadcast the values to construct all combinations of all gauss point # coordinates. # x, y, z = apply(broadcast_arrays, gp_coords) return x, y, z gp_w_grid = Property(depends_on='ngp_r,ngp_s,ngp_t') @cached_property def _get_gp_w_grid(self): '''In analogy to the above, get the grid of gauss weights in 3D. ''' # get the oriented arrays for each direction prepared for broadcasting # gp_w = [ oriented_3d_array(self._GP_WEIGHTS[ngp], dim) for dim, ngp in enumerate(self.n_gp_list) ] # broadcast the values to construct all combinations of all gauss point # coordinates. # w = reduce(lambda x, y: x * y, gp_w) return w ip_coords = Property(depends_on='ngp_r,ngp_s,ngp_t') def _get_ip_coords(self): '''Generate the flat array of ip_coords used for integration. ''' x, y, z = self.gp_r_grid return c_[x.flatten(), y.flatten(), z.flatten()] ip_coords_grid = Property(depends_on='ngp_r,ngp_s,ngp_t') def _get_ip_coords_grid(self): '''Generate the grid of ip_coords ''' return c_[self.gp_r_grid] ip_weights = Property(depends_on='ngp_r,ngp_s,ngp_t') def _get_ip_weights(self): '''Generate the flat array of ip_coords used for integration. ''' w = self.gp_w_grid return w.flatten() ip_weights_grid = Property(depends_on='ngp_r,ngp_s,ngp_t') def _get_ip_weights_grid(self): '''Generate the flat array of ip_coords used for integration. ''' w = self.gp_w_grid return w def get_ip_scheme(self, *params): return (self.ip_coords, self.ip_weights) n_gp = Property(depends_on='ngp_r,ngp_s,ngp_t') @cached_property def _get_n_gp(self): nr = max(1, self.ngp_r) ns = max(1, self.ngp_s) nt = max(1, self.ngp_t) return nr * ns * nt n_gp_list = Property(depends_on='ngp_r,ngp_s,ngp_r') @cached_property def _get_n_gp_list(self): nr = self.ngp_r ns = self.ngp_s nt = self.ngp_t return [nr, ns, nt] #------------------------------------------------------------------------- # SUBSPACE INTEGRATION #------------------------------------------------------------------------- # # Get the integration scheme for a subspace specified using the slicing indexes. # def get_sliced_ip_scheme(self, ip_idx_list): minmax = {0: min, -1: max} w = [] r = [] ix = [] for dim_idx, ip_idx in enumerate(ip_idx_list): if isinstance(ip_idx, types.IntType): w.append(1.0) r.append(minmax[ip_idx](self.dof_r[:, dim_idx])) elif isinstance(ip_idx, types.SliceType): # if there is a slice - put the array in the corresponding dimension # - only the full slide - i.e. slice(None,None,None) # is allowed n_gp = self.n_gp_list[dim_idx] w.append(oriented_3d_array(self._GP_WEIGHTS[n_gp], dim_idx)) r.append(oriented_3d_array(self._GP_COORDS[n_gp], dim_idx)) ix.append(dim_idx) r_grid = apply(broadcast_arrays, r) r_c = c_[tuple([r.flatten() for r in r_grid])] w_grid = reduce(lambda x, y: x * y, w) if isinstance(w_grid, types.FloatType): w_grid = array([w_grid], dtype='float_') else: w_grid = w_grid.flatten() return r_c, w_grid, ix #------------------------------------------------------------------------- # FIELD TRACING / VISUALIZATION #------------------------------------------------------------------------- # The user-specified fv_loc_coords list gets transform to an internal # array representation # vtk_r_arr = Property(depends_on='vtk_r') @cached_property def _get_vtk_r_arr(self): if len(self.vtk_r) == 0: raise ValueError, 'Cannot generate plot, no vtk_r specified in fets_eval' return array(self.vtk_r) def get_vtk_r_glb_arr(self, X_mtx, r_mtx=None): ''' Get an array with global coordinates of the element decomposition. If the local_point_list is non-empty then use it instead of the one supplied by the element specification. This is useful for augmented specification of RTraceEval evaluations with a specific profile of a field variable to be traced. ''' if self.dim_slice: X_mtx = X_mtx[:, self.dim_slice] if r_mtx == None: r_mtx = self.vtk_r_arr # TODO - efficiency in the extraction of the global coordinates. Is broadcasting # a possibility - we only need to augment the matrix with zero coordinates in the # in the unhandled dimensions. # X3D = array( [dot(self.get_N_geo_mtx(r_pnt), X_mtx)[0, :] for r_pnt in r_mtx]) n_dims = r_mtx.shape[1] n_add = 3 - n_dims if n_add > 0: X3D = hstack([X3D, zeros([r_mtx.shape[0], n_add], dtype='float_')]) return X3D def get_X_pnt(self, sctx): ''' Get the global coordinates for the specified local coordinats r_pnt @param r_pnt: local coordinates ''' r_pnt = sctx.r_pnt X_mtx = sctx.X return np.einsum('', self.Nr_i_geo, X_mtx) return dot(self.get_N_geo(r_pnt), X_mtx) def get_x_pnt(self, sctx): ''' Get the global coordinates for the specified local coordinats r_pnt @param r_pnt: local coordinates ''' r_pnt = sctx.r_pnt x_mtx = sctx.x # TODO - efficiency in the extraction of the global coordinates. Is broadcasting # a possibility - we only need to augment the matrix with zero coordinates in the # in the unhandled dimensions. # return dot(self.get_N_geo(r_pnt), x_mtx) def map_r2X(self, r_pnt, X_mtx): ''' Map the local coords to global @param r_pnt: local coords @param X_mtx: matrix of the global coords of geo nodes ''' # print "mapping ",dot( self.get_N_geo_mtx(r_pnt)[0], X_mtx )," ", # r_pnt return dot(self.get_N_geo(r_pnt), X_mtx) # Number of element DOFs # n_e_dofs = Int # Dimensionality dim_slice = None # Parameters for the time-loop # def new_cntl_var(self): return zeros(self.n_e_dofs, float_) def new_resp_var(self): return zeros(self.n_e_dofs, float_) def get_state_array_size(self): r_range = max(1, self.ngp_r) s_range = max(1, self.ngp_s) t_range = max(1, self.ngp_t) return self.m_arr_size * r_range * s_range * t_range m_arr_size = Property() @cached_property def _get_m_arr_size(self): return self.get_mp_state_array_size(None) def get_mp_state_array_size(self, sctx): '''Get the size of the state array for a single material point. ''' return self.mats_eval.get_state_array_size() def setup(self, sctx): '''Perform the setup in the all integration points. ''' for i, gp in enumerate(self.ip_coords): sctx.mats_state_array = sctx.elem_state_array[( i * self.m_arr_size):((i + 1) * self.m_arr_size)] self.mats_eval.setup(sctx) ngp_r = Int(0, label='Number of Gauss points in r-direction') ngp_s = Int(0, label='Number of Gauss points in s-direction') ngp_t = Int(0, label='Number of Gauss points in t-direction') #------------------------------------------------------------------- # Overloadable methods #------------------------------------------------------------------- def get_corr_pred(self, sctx, u, du, tn, tn1, eps_avg=None, B_mtx_grid=None, J_det_grid=None, ip_coords=None, ip_weights=None): ''' Corrector and predictor evaluation. @param u current element displacement vector ''' u_avg = eps_avg # temporary if J_det_grid == None or B_mtx_grid == None: # if self.dim_slice: # X_mtx = sctx.X[:, self.dim_slice] # else: X_mtx = sctx.X show_comparison = True if ip_coords == None: ip_coords = self.ip_coords show_comparison = False if ip_weights == None: ip_weights = self.ip_weights # Use for Jacobi Transformation n_e_dofs = self.n_e_dofs K = zeros((n_e_dofs, n_e_dofs)) F = zeros(n_e_dofs) sctx.fets_eval = self ip = 0 # use enumerate # Element formulation-specific adjustment of the spatial context # for the material level (needed for combination of regularized # material models with elements of lower dimensions. # self.adjust_spatial_context_for_point(sctx) # Numerical quadrature loop # for r_pnt, wt in zip(ip_coords, ip_weights): sctx.r_pnt = r_pnt if J_det_grid == None: J_det = self._get_J_det(r_pnt, X_mtx) else: J_det = J_det_grid[ip, ...] if B_mtx_grid == None: B_mtx = self.get_B_mtx(r_pnt, X_mtx) else: B_mtx = B_mtx_grid[ip, ...] # Map displacements to strains # eps_mtx = dot(B_mtx, u) d_eps_mtx = dot(B_mtx, du) # Set the state array slice into the spatial context # sctx.mats_state_array = sctx.elem_state_array[ip * self.m_arr_size:(ip + 1) * self.m_arr_size] # Evaluate the corrector and predictor for the current iteration # if u_avg != None: eps_avg = dot(B_mtx, u_avg) sig_mtx, D_mtx = self.get_mtrl_corr_pred( sctx, eps_mtx, d_eps_mtx, tn, tn1, eps_avg) else: sig_mtx, D_mtx = self.get_mtrl_corr_pred( sctx, eps_mtx, d_eps_mtx, tn, tn1) # Evaluate the element stiffness matrix # k = dot(B_mtx.T, dot(D_mtx, B_mtx)) k *= (wt * J_det) K += k # Evaluate the internal force vector # f = dot(B_mtx.T, sig_mtx) f *= (wt * J_det) F += f ip += 1 return F, K #------------------------------------------------------------------- # Standard evaluation methods #------------------------------------------------------------------- def get_J_mtx(self, r_pnt, X_mtx): dNr_geo_mtx = self.get_dNr_geo_mtx(r_pnt) return dot(dNr_geo_mtx, X_mtx) #------------------------------------------------------------------- # Required methods #------------------------------------------------------------------- def get_N_geo_mtx(self, r_pnt): raise NotImplementedError def get_dNr_geo_mtx(self, r_pnt): raise NotImplementedError def get_N_mtx(self, r_pnt): raise NotImplementedError def get_B_mtx(self, r_pnt, X_mtx): ''' Get the matrix for kinematic mapping between displacements and strains. @param r local position within the element. @param X nodal coordinates of the element. @TODO[jakub] generalize ''' raise NotImplementedError def get_mtrl_corr_pred(self, sctx, eps_eng, d_eps_eng, tn, tn1, eps_avg=None): if self.mats_eval.initial_strain: X_pnt = self.get_X_pnt(sctx) x_pnt = self.get_x_pnt(sctx) eps_ini_mtx = self.mats_eval.initial_strain(X_pnt, x_pnt) eps0_eng = self.mats_eval.map_eps_mtx_to_eng(eps_ini_mtx) eps_eng -= eps0_eng if eps_avg != None: sig_mtx, D_mtx = self.mats_eval.get_corr_pred( sctx, eps_eng, d_eps_eng, tn, tn1, eps_avg) else: sig_mtx, D_mtx = self.mats_eval.get_corr_pred( sctx, eps_eng, d_eps_eng, tn, tn1, ) return sig_mtx, D_mtx #------------------------------------------------------------------- # Private methods #------------------------------------------------------------------- def get_J_det(self, r_pnt, X_mtx): return array(self._get_J_det(r_pnt, X_mtx), dtype='float_') def _get_J_det(self, r_pnt3d, X_mtx): if self.dim_slice: r_pnt = r_pnt3d[self.dim_slice] return det(self.get_J_mtx(r_pnt, X_mtx)) # if no gauss point is defined in one direction (e.g. for ngp_t=0 for a 2D-problem) # then the default value for ngp_t=0 is used and a weighting coefficient of value 1. # In 'get_gp' the maximum of npg and 1 is used as range in the loop which leads to # a simple multiplication with 1 for that direction. The coordinate of the gauss point # in this direction is set to zero ('gp' : [0.]). # The index of the entry corresponds to the order of the polynomial # _GP_COORDS = [ [0.], # for polynomial order = 0 [0.], # for polynomial order = 1 # por polynomial order = 2 [-0.57735026918962584, 0.57735026918962584], # for polynomial order = 3 [-0.7745966692414834, 0., 0.7745966692414834], # for polynomial order = 4 [ -0.861136311594053, -0.339981043584856, 0.339981043584856, 0.861136311594053 ] ] _GP_WEIGHTS = [[1.], [2.], [1., 1.], [ 0.55555555555555558, 0.88888888888888884, 0.55555555555555558 ], [ 0.347854845137454, 0.652145154862546, 0.652145154862546, 0.347854845137454 ]] #------------------------------------------------------------------------- # Epsilon as an engineering value #------------------------------------------------------------------------- def get_eps0_eng(self, sctx, u): '''Get epsilon without the initial strain ''' if self.mats_eval.initial_strain: X_pnt = self.get_X_pnt(sctx) x_pnt = self.get_x_pnt(sctx) eps0_mtx = self.mats_eval.initial_strain(X_pnt, x_pnt) return self.mats_eval.map_eps_mtx_to_eng(eps0_mtx) else: return None def get_eps_eng(self, sctx, u): X_mtx = sctx.X r_pnt = sctx.loc B_mtx = self.get_B_mtx(r_pnt, X_mtx) eps_eng = dot(B_mtx, u) return eps_eng def get_eps1t_eng(self, sctx, u): '''Get epsilon without the initial strain ''' eps_eng = self.get_eps_eng(sctx, u) eps0_eng = self.get_eps0_eng(sctx, u) if eps0_eng != None: eps_eng -= eps0_eng return eps_eng #------------------------------------------------------------------------- # Epsilon as a tensorial value - used for visualization in mayavi #------------------------------------------------------------------------- def get_eps0_mtx33(self, sctx, u): '''Get epsilon without the initial strain ''' eps0_mtx33 = zeros((3, 3), dtype='float_') if self.mats_eval.initial_strain: X_pnt = self.get_X_pnt(sctx) x_pnt = self.get_x_pnt(sctx) eps0_mtx33[self.dim_slice, self.dim_slice] = self.mats_eval.initial_strain( X_pnt, x_pnt) return eps0_mtx33 def get_eps_mtx33(self, sctx, u): eps_eng = self.get_eps_eng(sctx, u) eps_mtx33 = zeros((3, 3), dtype='float_') eps_mtx33[self.dim_slice, self.dim_slice] = self.mats_eval.map_eps_eng_to_mtx(eps_eng) return eps_mtx33 def get_eps1t_mtx33(self, sctx, u): '''Get epsilon without the initial strain ''' eps_mtx33 = self.get_eps_mtx33(sctx, u) eps0_mtx33 = self.get_eps0_mtx33(sctx, u) return eps_mtx33 - eps0_mtx33 #------------------------------------------------------------------------- # Displacement in a n arbitrary point within an element #------------------------------------------------------------------------- def map_eps(self, sctx, u): X_mtx = sctx.X r_pnt = sctx.loc B_mtx = self.get_B_mtx(r_pnt, X_mtx) eps = dot(B_mtx, u) return eps def get_u(self, sctx, u): N_mtx = self.get_N_mtx(sctx.loc) return dot(N_mtx, u) debug_on = Bool(False) def _debug_rte_dict(self): ''' RTraceEval dictionary with field variables used to verify the element implementation ''' if self.debug_on: return { 'Ngeo_mtx': RTraceEvalElemFieldVar( eval=lambda sctx, u: self.get_N_geo_mtx(sctx.loc), ts=self), 'N_mtx': RTraceEvalElemFieldVar( eval=lambda sctx, u: self.get_N_mtx(sctx.loc)[0], ts=self), 'B_mtx0': RTraceEvalElemFieldVar( eval=lambda sctx, u: self.get_B_mtx(sctx.loc, sctx.X)[0], ts=self), 'B_mtx1': RTraceEvalElemFieldVar( eval=lambda sctx, u: self.get_B_mtx(sctx.loc, sctx.X)[1], ts=self), 'B_mtx2': RTraceEvalElemFieldVar( eval=lambda sctx, u: self.get_B_mtx(sctx.loc, sctx.X)[2], ts=self), 'J_det': RTraceEvalElemFieldVar(eval=lambda sctx, u: array( [det(self.get_J_mtx(sctx.loc, sctx.X))]), ts=self) } else: return {} # List of mats that are to be chained # # Declare and fill-in the rte_dict - it is used by the clients to # assemble all the available time-steppers. # rte_dict = Trait(Dict) def _rte_dict_default(self): ''' RTraceEval dictionary with standard field variables. ''' rte_dict = self._debug_rte_dict() for key, v_eval in self.mats_eval.rte_dict.items(): # add the eval into the loop. # rte_dict[key] = RTraceEvalElemFieldVar( name=key, u_mapping=self.get_eps1t_eng, eval=v_eval) rte_dict.update({ 'eps_app': RTraceEvalElemFieldVar(eval=self.get_eps_mtx33), 'eps0_app': RTraceEvalElemFieldVar(eval=self.get_eps0_mtx33), 'eps1t_app': RTraceEvalElemFieldVar(eval=self.get_eps1t_mtx33), 'u': RTraceEvalElemFieldVar(eval=self.get_u) }) return rte_dict traits_view = View( Group(Item('mats_eval'), Item('n_e_dofs'), Item('n_nodal_dofs'), label='Numerical parameters'), Group( # Item( 'dof_r' ), # Item( 'geo_r' ), Item('vtk_r'), Item('vtk_cells'), Item('vtk_cell_types'), label='Visualization parameters'), # Item('rte_dict'), resizable=True, scrollable=True, width=0.2, height=0.4)