diff --git a/PRIVATE b/PRIVATE index 23eac08c9..d58a002b0 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 23eac08c9f9638f8dae76710095222d00f948eec +Subproject commit d58a002b0a43d240f143aee1396fdc766d87a886 diff --git a/python/damask/_configmaterial.py b/python/damask/_configmaterial.py index 557907594..ad085e5c2 100644 --- a/python/damask/_configmaterial.py +++ b/python/damask/_configmaterial.py @@ -14,7 +14,7 @@ class ConfigMaterial(Config): A complete material configuration file has the entries 'material', 'phase', and 'homogenization'. For use in DAMASK, it needs to be stored as 'material.yaml'. - + """ def __init__(self,d=None): @@ -60,54 +60,6 @@ class ConfigMaterial(Config): return super(ConfigMaterial,cls).load(fname) - @staticmethod - def from_table(table,**kwargs): - """ - Generate from an ASCII table. - - Parameters - ---------- - table : damask.Table - Table that contains material information. - **kwargs - Keyword arguments where the key is the name and the value specifies - the label of the data column in the table. - - Examples - -------- - >>> import damask - >>> import damask.ConfigMaterial as cm - >>> t = damask.Table.load('small.txt') - >>> t - pos pos pos qu qu qu qu phase homog - 0 0 0 0 0.19 0.8 0.24 -0.51 Aluminum SX - 1 1 0 0 0.8 0.19 0.24 -0.51 Steel SX - 1 1 1 0 0.8 0.19 0.24 -0.51 Steel SX - >>> cm.from_table(t,O='qu',phase='phase',homogenization='homog') - material: - - constituents: - - O: [0.19, 0.8, 0.24, -0.51] - v: 1.0 - phase: Aluminum - homogenization: SX - - constituents: - - O: [0.8, 0.19, 0.24, -0.51] - v: 1.0 - phase: Steel - homogenization: SX - homogenization: {} - phase: {} - - """ - kwargs_ = {k:table.get(v) for k,v in kwargs.items()} - - _,idx = np.unique(np.hstack(list(kwargs_.values())),return_index=True,axis=0) - idx = np.sort(idx) - kwargs_ = {k:np.atleast_1d(v[idx].squeeze()) for k,v in kwargs_.items()} - - return ConfigMaterial().material_add(**kwargs_) - - @staticmethod def load_DREAM3D(fname, grain_data=None,cell_data=None,cell_ensemble_data='CellEnsembleData', @@ -181,9 +133,69 @@ class ConfigMaterial(Config): return base_config.material_add(**constituent,homogenization='direct') + @staticmethod + def from_table(table,**kwargs): + """ + Generate from an ASCII table. + + Parameters + ---------- + table : damask.Table + Table that contains material information. + **kwargs + Keyword arguments where the key is the name and the value specifies + the label of the data column in the table. + + Examples + -------- + >>> import damask + >>> import damask.ConfigMaterial as cm + >>> t = damask.Table.load('small.txt') + >>> t + pos pos pos qu qu qu qu phase homog + 0 0 0 0 0.19 0.8 0.24 -0.51 Aluminum SX + 1 1 0 0 0.8 0.19 0.24 -0.51 Steel SX + 1 1 1 0 0.8 0.19 0.24 -0.51 Steel SX + >>> cm.from_table(t,O='qu',phase='phase',homogenization='homog') + material: + - constituents: + - O: [0.19, 0.8, 0.24, -0.51] + v: 1.0 + phase: Aluminum + homogenization: SX + - constituents: + - O: [0.8, 0.19, 0.24, -0.51] + v: 1.0 + phase: Steel + homogenization: SX + homogenization: {} + phase: {} + + """ + kwargs_ = {k:table.get(v) for k,v in kwargs.items()} + + _,idx = np.unique(np.hstack(list(kwargs_.values())),return_index=True,axis=0) + idx = np.sort(idx) + kwargs_ = {k:np.atleast_1d(v[idx].squeeze()) for k,v in kwargs_.items()} + + return ConfigMaterial().material_add(**kwargs_) + + @property def is_complete(self): - """Check for completeness.""" + """ + Check for completeness. + + Only the general file layout is considered. + This check does not consider whether parameters for + a particular phase/homogenization model are missing. + + Returns + ------- + complete : bool + Whether the material.yaml definition is complete. + + """ ok = True for top_level in ['homogenization','phase','material']: ok &= top_level in self @@ -236,7 +248,19 @@ class ConfigMaterial(Config): @property def is_valid(self): - """Check for valid content.""" + """ + Check for valid content. + + Only the generic file content is considered. + This check does not consider whether parameters for a + particular phase/homogenization mode are out of bounds. + + Returns + ------- + valid : bool + Whether the material.yaml definition is valid. + + """ ok = True if 'phase' in self: @@ -282,7 +306,7 @@ class ConfigMaterial(Config): Returns ------- - cfg : damask.ConfigMaterial + updated : damask.ConfigMaterial Updated material configuration. """ @@ -311,7 +335,7 @@ class ConfigMaterial(Config): Returns ------- - cfg : damask.ConfigMaterial + updated : damask.ConfigMaterial Updated material configuration. """ @@ -336,7 +360,7 @@ class ConfigMaterial(Config): Returns ------- - cfg : damask.ConfigMaterial + updated : damask.ConfigMaterial Updated material configuration. Examples diff --git a/python/damask/_result.py b/python/damask/_result.py index 96e76374a..a7f211f20 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -57,11 +57,15 @@ def _empty_like(dataset,N_materialpoints,fill_float,fill_int): class Result: """ - Manipulate and read DADF5 files. + Add data to and export from DADF5 files. DADF5 (DAMASK HDF5) files contain DAMASK results. - The group/folder structure reflects the input data - in material.yaml. + Their group/folder structure reflects the input data in material.yaml. + + This class provides a custom view on the DADF5 file. + Upon initialization, all attributes are visible. + Derived quantities can be added to the file and existing data can be exported based on the current view. + """ def __init__(self,fname): @@ -274,6 +278,11 @@ class Result: Name of datasets; supports '?' and '*' wildcards. True is equivalent to '*', False is equivalent to []. + Returns + ------- + view : damask.Result + View with where selected attributes are visible. + """ return self._manage_view('set',what,datasets) @@ -290,6 +299,11 @@ class Result: Name of datasets; supports '?' and '*' wildcards. True is equivalent to '*', False is equivalent to []. + Returns + ------- + modified_view : damask.Result + View with more visible attributes. + """ return self._manage_view('add',what,datasets) @@ -306,6 +320,11 @@ class Result: Name of datasets; supports '?' and '*' wildcards. True is equivalent to '*', False is equivalent to []. + Returns + ------- + modified_view : damask.Result + View with less visible attributes. + """ return self._manage_view('del',what,datasets) @@ -372,7 +391,7 @@ class Result: @property def coordinates0_point(self): - """Return initial coordinates of the cell centers.""" + """Initial/undeformed cell center coordinates.""" if self.structured: return grid_filters.coordinates0_point(self.cells,self.size,self.origin).reshape(-1,3,order='F') else: @@ -381,7 +400,7 @@ class Result: @property def coordinates0_node(self): - """Return initial coordinates of the cell centers.""" + """Initial/undeformed nodal coordinates.""" if self.structured: return grid_filters.coordinates0_node(self.cells,self.size,self.origin).reshape(-1,3,order='F') else: @@ -390,6 +409,7 @@ class Result: @property def geometry0(self): + """Initial/undeformed geometry.""" if self.structured: return VTK.from_rectilinear_grid(self.cells,self.size,self.origin) else: diff --git a/python/damask/_table.py b/python/damask/_table.py index 01d8c9863..a068ec02a 100644 --- a/python/damask/_table.py +++ b/python/damask/_table.py @@ -223,6 +223,11 @@ class Table: fname : file, str, or pathlib.Path Filename or file for reading. + Returns + ------- + loaded : damask.Table + Table data from file. + """ try: f = open(fname) @@ -275,6 +280,11 @@ class Table: fname : file, str, or pathlib.Path Filename or file for reading. + Returns + ------- + loaded : damask.Table + Table data from file. + """ try: f = open(fname) @@ -334,14 +344,14 @@ class Table: ---------- label : str Column label. - data : np.ndarray + data : numpy.ndarray New data. info : str, optional Human-readable information about the new data. Returns ------- - table : Table + updated : damask.Table Updated table. """ @@ -367,14 +377,14 @@ class Table: ---------- label : str Column label. - data : np.ndarray + data : numpy.ndarray Modified data. info : str, optional Human-readable information about the modified data. Returns ------- - table : Table + udated : damask.Table Updated table. """ @@ -402,7 +412,7 @@ class Table: Returns ------- - table : Table + udated : damask.Table Updated table. """ @@ -425,7 +435,7 @@ class Table: Returns ------- - table : Table + udated : damask.Table Updated table. """ @@ -451,7 +461,7 @@ class Table: Returns ------- - table : Table + udated : damask.Table Updated table. """ @@ -479,13 +489,13 @@ class Table: Parameters ---------- - other : Table + other : damask.Table Table to append. Returns ------- - table : Table - Concatenated table. + udated : damask.Table + Updated table. """ if self.shapes != other.shapes or not self.data.columns.equals(other.data.columns): @@ -504,13 +514,13 @@ class Table: Parameters ---------- - other : Table + other : damask.Table Table to join. Returns ------- - table : Table - Joined table. + udated : damask.Table + Updated table. """ if set(self.shapes) & set(other.shapes) or self.data.shape[0] != other.data.shape[0]: diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index ee929b3bf..d82f25ea9 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -5,11 +5,12 @@ Notes ----- The grids are defined as (x,y,z,...) where x is fastest and z is slowest. This convention is consistent with the layout in grid vtr files. + When converting to/from a plain list (e.g. storage in ASCII table), the following operations are required for tensorial data: -D3 = D1.reshape(cells+(-1,),order='F').reshape(cells+(3,3)) -D1 = D3.reshape(cells+(-1,)).reshape(-1,9,order='F') + - D3 = D1.reshape(cells+(-1,),order='F').reshape(cells+(3,3)) + - D1 = D3.reshape(cells+(-1,)).reshape(-1,9,order='F') """ from scipy import spatial as _spatial @@ -29,10 +30,12 @@ def _ks(size,cells,first_order=False): Correction for first order derivatives, defaults to False. """ - k_sk = _np.where(_np.arange(cells[0])>cells[0]//2,_np.arange(cells[0])-cells[0],_np.arange(cells[0]))/size[0] + k_sk = _np.where(_np.arange(cells[0])>cells[0]//2, + _np.arange(cells[0])-cells[0],_np.arange(cells[0]))/size[0] if cells[0]%2 == 0 and first_order: k_sk[cells[0]//2] = 0 # Nyquist freq=0 for even cells (Johnson, MIT, 2011) - k_sj = _np.where(_np.arange(cells[1])>cells[1]//2,_np.arange(cells[1])-cells[1],_np.arange(cells[1]))/size[1] + k_sj = _np.where(_np.arange(cells[1])>cells[1]//2, + _np.arange(cells[1])-cells[1],_np.arange(cells[1]))/size[1] if cells[1]%2 == 0 and first_order: k_sj[cells[1]//2] = 0 # Nyquist freq=0 for even cells (Johnson, MIT, 2011) k_si = _np.arange(cells[2]//2+1)/size[2] @@ -40,74 +43,89 @@ def _ks(size,cells,first_order=False): return _np.stack(_np.meshgrid(k_sk,k_sj,k_si,indexing = 'ij'), axis=-1) -def curl(size,field): - """ +def curl(size,f): + u""" Calculate curl of a vector or tensor field in Fourier space. Parameters ---------- size : numpy.ndarray of shape (3) Physical size of the periodic field. - field : numpy.ndarray of shape (:,:,:,3) or (:,:,:,3,3) + f : numpy.ndarray of shape (:,:,:,3) or (:,:,:,3,3) Periodic field of which the curl is calculated. + Returns + ------- + ∇ × f : numpy.ndarray + Curl of f. + """ - n = _np.prod(field.shape[3:]) - k_s = _ks(size,field.shape[:3],True) + n = _np.prod(f.shape[3:]) + k_s = _ks(size,f.shape[:3],True) e = _np.zeros((3, 3, 3)) e[0, 1, 2] = e[1, 2, 0] = e[2, 0, 1] = +1.0 # Levi-Civita symbol e[0, 2, 1] = e[2, 1, 0] = e[1, 0, 2] = -1.0 - field_fourier = _np.fft.rfftn(field,axes=(0,1,2)) - curl_ = (_np.einsum('slm,ijkl,ijkm ->ijks', e,k_s,field_fourier)*2.0j*_np.pi if n == 3 else # vector, 3 -> 3 - _np.einsum('slm,ijkl,ijknm->ijksn',e,k_s,field_fourier)*2.0j*_np.pi) # tensor, 3x3 -> 3x3 + f_fourier = _np.fft.rfftn(f,axes=(0,1,2)) + curl_ = (_np.einsum('slm,ijkl,ijkm ->ijks', e,k_s,f_fourier)*2.0j*_np.pi if n == 3 else # vector, 3 -> 3 + _np.einsum('slm,ijkl,ijknm->ijksn',e,k_s,f_fourier)*2.0j*_np.pi) # tensor, 3x3 -> 3x3 - return _np.fft.irfftn(curl_,axes=(0,1,2),s=field.shape[:3]) + return _np.fft.irfftn(curl_,axes=(0,1,2),s=f.shape[:3]) -def divergence(size,field): - """ +def divergence(size,f): + u""" Calculate divergence of a vector or tensor field in Fourier space. Parameters ---------- size : numpy.ndarray of shape (3) Physical size of the periodic field. - field : numpy.ndarray of shape (:,:,:,3) or (:,:,:,3,3) + f : numpy.ndarray of shape (:,:,:,3) or (:,:,:,3,3) Periodic field of which the divergence is calculated. + Returns + ------- + ∇ · f : numpy.ndarray + Divergence of f. + """ - n = _np.prod(field.shape[3:]) - k_s = _ks(size,field.shape[:3],True) + n = _np.prod(f.shape[3:]) + k_s = _ks(size,f.shape[:3],True) - field_fourier = _np.fft.rfftn(field,axes=(0,1,2)) - div_ = (_np.einsum('ijkl,ijkl ->ijk', k_s,field_fourier)*2.0j*_np.pi if n == 3 else # vector, 3 -> 1 - _np.einsum('ijkm,ijklm->ijkl',k_s,field_fourier)*2.0j*_np.pi) # tensor, 3x3 -> 3 + f_fourier = _np.fft.rfftn(f,axes=(0,1,2)) + div_ = (_np.einsum('ijkl,ijkl ->ijk', k_s,f_fourier)*2.0j*_np.pi if n == 3 else # vector, 3 -> 1 + _np.einsum('ijkm,ijklm->ijkl',k_s,f_fourier)*2.0j*_np.pi) # tensor, 3x3 -> 3 - return _np.fft.irfftn(div_,axes=(0,1,2),s=field.shape[:3]) + return _np.fft.irfftn(div_,axes=(0,1,2),s=f.shape[:3]) -def gradient(size,field): - """ - Calculate gradient of a scalar or vector field in Fourier space. +def gradient(size,f): + u""" + Calculate gradient of a scalar or vector fieldin Fourier space. Parameters ---------- size : numpy.ndarray of shape (3) Physical size of the periodic field. - field : numpy.ndarray of shape (:,:,:,1) or (:,:,:,3) + f : numpy.ndarray of shape (:,:,:,1) or (:,:,:,3) Periodic field of which the gradient is calculated. + Returns + ------- + ∇ f : numpy.ndarray + Divergence of f. + """ - n = _np.prod(field.shape[3:]) - k_s = _ks(size,field.shape[:3],True) + n = _np.prod(f.shape[3:]) + k_s = _ks(size,f.shape[:3],True) - field_fourier = _np.fft.rfftn(field,axes=(0,1,2)) - grad_ = (_np.einsum('ijkl,ijkm->ijkm', field_fourier,k_s)*2.0j*_np.pi if n == 1 else # scalar, 1 -> 3 - _np.einsum('ijkl,ijkm->ijklm',field_fourier,k_s)*2.0j*_np.pi) # vector, 3 -> 3x3 + f_fourier = _np.fft.rfftn(f,axes=(0,1,2)) + grad_ = (_np.einsum('ijkl,ijkm->ijkm', f_fourier,k_s)*2.0j*_np.pi if n == 1 else # scalar, 1 -> 3 + _np.einsum('ijkl,ijkm->ijklm',f_fourier,k_s)*2.0j*_np.pi) # vector, 3 -> 3x3 - return _np.fft.irfftn(grad_,axes=(0,1,2),s=field.shape[:3]) + return _np.fft.irfftn(grad_,axes=(0,1,2),s=f.shape[:3]) def coordinates0_point(cells,size,origin=_np.zeros(3)): @@ -123,6 +141,11 @@ def coordinates0_point(cells,size,origin=_np.zeros(3)): origin : numpy.ndarray, optional Physical origin of the periodic field. Defaults to [0.0,0.0,0.0]. + Returns + ------- + x_p_0 : numpy.ndarray + Undeformed cell center coordinates. + """ start = origin + size/cells*.5 end = origin + size - size/cells*.5 @@ -144,6 +167,11 @@ def displacement_fluct_point(size,F): F : numpy.ndarray Deformation gradient field. + Returns + ------- + u_p_fluct : numpy.ndarray + Fluctuating part of the cell center displacements. + """ integrator = 0.5j*size/_np.pi @@ -171,6 +199,11 @@ def displacement_avg_point(size,F): F : numpy.ndarray Deformation gradient field. + Returns + ------- + u_p_avg : numpy.ndarray + Average part of the cell center displacements. + """ F_avg = _np.average(F,axis=(0,1,2)) return _np.einsum('ml,ijkl->ijkm',F_avg - _np.eye(3),coordinates0_point(F.shape[:3],size)) @@ -187,6 +220,11 @@ def displacement_point(size,F): F : numpy.ndarray Deformation gradient field. + Returns + ------- + u_p : numpy.ndarray + Cell center displacements. + """ return displacement_avg_point(size,F) + displacement_fluct_point(size,F) @@ -204,6 +242,11 @@ def coordinates_point(size,F,origin=_np.zeros(3)): origin : numpy.ndarray of shape (3), optional Physical origin of the periodic field. Defaults to [0.0,0.0,0.0]. + Returns + ------- + x_p : numpy.ndarray + Cell center coordinates. + """ return coordinates0_point(F.shape[:3],size,origin) + displacement_point(size,F) @@ -252,19 +295,6 @@ def cellsSizeOrigin_coordinates0_point(coordinates0,ordered=True): return (cells,size,origin) -def coordinates0_check(coordinates0): - """ - Check whether coordinates lie on a regular grid. - - Parameters - ---------- - coordinates0 : numpy.ndarray - Array of undeformed cell coordinates. - - """ - cellsSizeOrigin_coordinates0_point(coordinates0,ordered=True) - - def coordinates0_node(cells,size,origin=_np.zeros(3)): """ Nodal positions (undeformed). @@ -278,6 +308,11 @@ def coordinates0_node(cells,size,origin=_np.zeros(3)): origin : numpy.ndarray of shape (3), optional Physical origin of the periodic field. Defaults to [0.0,0.0,0.0]. + Returns + ------- + x_n_0 : numpy.ndarray + Undeformed nodal coordinates. + """ return _np.stack(_np.meshgrid(_np.linspace(origin[0],size[0]+origin[0],cells[0]+1), _np.linspace(origin[1],size[1]+origin[1],cells[1]+1), @@ -296,6 +331,11 @@ def displacement_fluct_node(size,F): F : numpy.ndarray Deformation gradient field. + Returns + ------- + u_n_fluct : numpy.ndarray + Fluctuating part of the nodal displacements. + """ return point_to_node(displacement_fluct_point(size,F)) @@ -311,6 +351,11 @@ def displacement_avg_node(size,F): F : numpy.ndarray Deformation gradient field. + Returns + ------- + u_n_avg : numpy.ndarray + Average part of the nodal displacements. + """ F_avg = _np.average(F,axis=(0,1,2)) return _np.einsum('ml,ijkl->ijkm',F_avg - _np.eye(3),coordinates0_node(F.shape[:3],size)) @@ -327,6 +372,11 @@ def displacement_node(size,F): F : numpy.ndarray Deformation gradient field. + Returns + ------- + u_p : numpy.ndarray + Nodal displacements. + """ return displacement_avg_node(size,F) + displacement_fluct_node(size,F) @@ -344,28 +394,15 @@ def coordinates_node(size,F,origin=_np.zeros(3)): origin : numpy.ndarray of shape (3), optional Physical origin of the periodic field. Defaults to [0.0,0.0,0.0]. + Returns + ------- + x_n : numpy.ndarray + Nodal coordinates. + """ return coordinates0_node(F.shape[:3],size,origin) + displacement_node(size,F) -def point_to_node(cell_data): - """Interpolate periodic point data to nodal data.""" - n = ( cell_data + _np.roll(cell_data,1,(0,1,2)) - + _np.roll(cell_data,1,(0,)) + _np.roll(cell_data,1,(1,)) + _np.roll(cell_data,1,(2,)) - + _np.roll(cell_data,1,(0,1)) + _np.roll(cell_data,1,(1,2)) + _np.roll(cell_data,1,(2,0)))*0.125 - - return _np.pad(n,((0,1),(0,1),(0,1))+((0,0),)*len(cell_data.shape[3:]),mode='wrap') - - -def node_2_point(node_data): - """Interpolate periodic nodal data to point data.""" - c = ( node_data + _np.roll(node_data,1,(0,1,2)) - + _np.roll(node_data,1,(0,)) + _np.roll(node_data,1,(1,)) + _np.roll(node_data,1,(2,)) - + _np.roll(node_data,1,(0,1)) + _np.roll(node_data,1,(1,2)) + _np.roll(node_data,1,(2,0)))*0.125 - - return c[1:,1:,1:] - - def cellsSizeOrigin_coordinates0_node(coordinates0,ordered=True): """ Return grid 'DNA', i.e. cells, size, and origin from 1D array of nodal positions. @@ -402,6 +439,66 @@ def cellsSizeOrigin_coordinates0_node(coordinates0,ordered=True): return (cells,size,origin) +def point_to_node(cell_data): + """ + Interpolate periodic point data to nodal data. + + Parameters + ---------- + cell_data : numpy.ndarray of shape (:,:,:,...) + Data defined on the cell centers of a periodic grid. + + Returns + ------- + node_data : numpy.ndarray of shape (:,:,:,...) + Data defined on the nodes of a periodic grid. + """ + n = ( cell_data + _np.roll(cell_data,1,(0,1,2)) + + _np.roll(cell_data,1,(0,)) + _np.roll(cell_data,1,(1,)) + _np.roll(cell_data,1,(2,)) + + _np.roll(cell_data,1,(0,1)) + _np.roll(cell_data,1,(1,2)) + _np.roll(cell_data,1,(2,0)))*0.125 + + return _np.pad(n,((0,1),(0,1),(0,1))+((0,0),)*len(cell_data.shape[3:]),mode='wrap') + + +def node_to_point(node_data): + """ + Interpolate periodic nodal data to point data. + + Parameters + ---------- + node_data : numpy.ndarray of shape (:,:,:,...) + Data defined on the nodes of a periodic grid. + + Returns + ------- + cell_data : numpy.ndarray of shape (:,:,:,...) + Data defined on the cell centers of a periodic grid. + + """ + c = ( node_data + _np.roll(node_data,1,(0,1,2)) + + _np.roll(node_data,1,(0,)) + _np.roll(node_data,1,(1,)) + _np.roll(node_data,1,(2,)) + + _np.roll(node_data,1,(0,1)) + _np.roll(node_data,1,(1,2)) + _np.roll(node_data,1,(2,0)))*0.125 + + return c[1:,1:,1:] + + +def coordinates0_valid(coordinates0): + """ + Check whether coordinates lie on a regular grid. + + Parameters + ---------- + coordinates0 : numpy.ndarray + Array of undeformed cell coordinates. + + """ + try: + cellsSizeOrigin_coordinates0_point(coordinates0,ordered=True) + return True + except ValueError: + return False + + def regrid(size,F,cells): """ Return mapping from coordinates in deformed configuration to a regular grid. diff --git a/python/damask/mechanics.py b/python/damask/mechanics.py index 66f5c9916..8512023fc 100644 --- a/python/damask/mechanics.py +++ b/python/damask/mechanics.py @@ -1,4 +1,11 @@ -"""Finite-strain continuum mechanics.""" +""" +Finite-strain continuum mechanics. + +Notes +----- +Collection of routines to operate on numpy.ndarrays of shape (...,3,3). + +""" from . import tensor as _tensor from . import _rotation @@ -154,7 +161,6 @@ def strain(F,t,m): return eps - def stress_Cauchy(P,F): """ Calculate the Cauchy stress (true stress). diff --git a/python/damask/seeds.py b/python/damask/seeds.py index 9ab148a82..74d15f1ef 100644 --- a/python/damask/seeds.py +++ b/python/damask/seeds.py @@ -24,6 +24,11 @@ def from_random(size,N_seeds,cells=None,rng_seed=None): A seed to initialize the BitGenerator. Defaults to None. If None, then fresh, unpredictable entropy will be pulled from the OS. + Returns + ------- + new : numpy.ndarray of shape (N_seeds,3) + Coordinates in 3D space. + """ rng = _np.random.default_rng(rng_seed) if cells is None: @@ -56,6 +61,11 @@ def from_Poisson_disc(size,N_seeds,N_candidates,distance,periodic=True,rng_seed= A seed to initialize the BitGenerator. Defaults to None. If None, then fresh, unpredictable entropy will be pulled from the OS. + Returns + ------- + new : numpy.ndarray of shape (N_seeds,3) + Coordinates in 3D space. + """ rng = _np.random.default_rng(rng_seed) coords = _np.empty((N_seeds,3)) @@ -94,6 +104,11 @@ def from_grid(grid,selection=None,invert=False,average=False,periodic=True): periodic : boolean, optional Center of gravity with periodic boundaries. + Returns + ------- + new : numpy.ndarray of shape (...,3) + Coordinates in 3D space. + """ material = grid.material.reshape((-1,1),order='F') mask = _np.full(grid.cells.prod(),True,dtype=bool) if selection is None else \ diff --git a/python/tests/test_grid_filters.py b/python/tests/test_grid_filters.py index d43e94c3c..d9c074c11 100644 --- a/python/tests/test_grid_filters.py +++ b/python/tests/test_grid_filters.py @@ -60,7 +60,7 @@ class TestGridFilters: cell_field_x = np.interp(coordinates0_point_x,coordinates_node_x,node_field_x,period=np.pi*2.) cell_field = np.broadcast_to(cell_field_x.reshape(-1,1,1),cells) - assert np.allclose(cell_field,grid_filters.node_2_point(node_field)) + assert np.allclose(cell_field,grid_filters.node_to_point(node_field)) @pytest.mark.parametrize('mode',['point','node']) def test_coordinates0_origin(self,mode): @@ -93,14 +93,24 @@ class TestGridFilters: F = np.broadcast_to(np.random.random((3,3)), tuple(cells)+(3,3)) assert np.allclose(function(size,F),0.0) - @pytest.mark.parametrize('function',[grid_filters.coordinates0_check, - grid_filters.cellsSizeOrigin_coordinates0_node, - grid_filters.cellsSizeOrigin_coordinates0_point]) + @pytest.mark.parametrize('function',[grid_filters.cellsSizeOrigin_coordinates0_point, + grid_filters.cellsSizeOrigin_coordinates0_node]) def test_invalid_coordinates(self,function): invalid_coordinates = np.random.random((np.random.randint(12,52),3)) with pytest.raises(ValueError): function(invalid_coordinates) + @pytest.mark.parametrize('function',[grid_filters.coordinates0_point, + grid_filters.coordinates0_node]) + def test_valid_coordinates_check(self,function): + valid_coordinates = function(np.random.randint(4,10,(3)),np.random.rand(3)) + assert grid_filters.coordinates0_valid(valid_coordinates.reshape(-1,3,order='F')) + + def test_invalid_coordinates_check(self): + invalid_coordinates = np.random.random((np.random.randint(12,52),3)) + assert not grid_filters.coordinates0_valid(invalid_coordinates) + + @pytest.mark.parametrize('function',[grid_filters.cellsSizeOrigin_coordinates0_node, grid_filters.cellsSizeOrigin_coordinates0_point]) def test_uneven_spaced_coordinates(self,function):