From 26f37d822d39702b1117785dd4c7d9dc69915a9e Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 23 Apr 2021 19:15:11 +0200 Subject: [PATCH 1/7] state return value not for Rotation as its docstring is extended by Orientation --- python/damask/_colormap.py | 14 +++++- python/damask/_grid.py | 99 +++++++++++++++++++++++++++++++++++++- python/damask/_rotation.py | 40 ++++++++++++--- 3 files changed, 142 insertions(+), 11 deletions(-) diff --git a/python/damask/_colormap.py b/python/damask/_colormap.py index de63508b3..0d36c1ee6 100644 --- a/python/damask/_colormap.py +++ b/python/damask/_colormap.py @@ -91,6 +91,11 @@ class Colormap(mpl.colors.ListedColormap): - 'lab': CIE Lab. - 'msh': Msh (for perceptual uniform interpolation). + Returns + ------- + new : damask.Colormap + Colormap within given bounds. + """ low_high = np.vstack((low,high)) if model.lower() == 'rgb': @@ -150,6 +155,11 @@ class Colormap(mpl.colors.ListedColormap): This parameter is not used for matplotlib colormaps that are of type `ListedColormap`. + Returns + ------- + new : damask.Colormap + Predefined colormap. + """ # matplotlib presets try: @@ -239,8 +249,8 @@ class Colormap(mpl.colors.ListedColormap): Returns ------- - f - File handle + f : file object + File handle with write access. """ if fname is None: diff --git a/python/damask/_grid.py b/python/damask/_grid.py index 308be5c80..5f5bad63d 100644 --- a/python/damask/_grid.py +++ b/python/damask/_grid.py @@ -161,6 +161,11 @@ class Grid: Grid file to read. Valid extension is .vtr, which will be appended if not given. + Returns + ------- + loaded : damask.Grid + Geometry representation from file. + """ v = VTK.load(fname if str(fname).endswith('.vtr') else str(fname)+'.vtr') comments = v.get_comments() @@ -190,6 +195,11 @@ class Grid: fname : str, pathlib.Path, or file handle Geometry file to read. + Returns + ------- + loaded : damask.Grid + Geometry representation from file. + """ warnings.warn('Support for ASCII-based geom format will be removed in DAMASK 3.1.0', DeprecationWarning,2) try: @@ -281,6 +291,10 @@ class Grid: and grain- or cell-wise data. Defaults to None, in which case it is set as the path that contains _SIMPL_GEOMETRY/SPACING. + Returns + ------- + loaded : damask.Grid + Geometry representation from file. """ b = util.DREAM3D_base_group(fname) if base_group is None else base_group @@ -319,6 +333,11 @@ class Grid: Label(s) of the columns containing the material definition. Each unique combination of values results in one material ID. + Returns + ------- + new : damask.Grid + Geometry representation from values in table. + """ cells,size,origin = grid_filters.cellsSizeOrigin_coordinates0_point(table.get(coordinates)) @@ -356,6 +375,11 @@ class Grid: periodic : Boolean, optional Assume grid to be periodic. Defaults to True. + Returns + ------- + new : damask.Grid + Geometry representation from tessellation. + """ if periodic: weights_p = np.tile(weights,27) # Laguerre weights (1,2,3,1,2,3,...,1,2,3) @@ -405,6 +429,11 @@ class Grid: periodic : Boolean, optional Assume grid to be periodic. Defaults to True. + Returns + ------- + new : damask.Grid + Geometry representation from tessellation. + """ coords = grid_filters.coordinates0_point(cells,size).reshape(-1,3) KDTree = spatial.cKDTree(seeds,boxsize=size) if periodic else spatial.cKDTree(seeds) @@ -478,6 +507,11 @@ class Grid: materials : (int, int), optional Material IDs. Defaults to (1,2). + Returns + ------- + new : damask.Grid + Geometry representation defined by a minimal surface. + Notes ----- The following triply-periodic minimal surfaces are implemented: @@ -598,6 +632,11 @@ class Grid: periodic : Boolean, optional Assume grid to be periodic. Defaults to True. + Returns + ------- + updated : damask.Grid + Updated geometry representation. + """ # radius and center r = np.array(dimension)/2.0*self.size/self.cells if np.array(dimension).dtype in np.sctypes['int'] else \ @@ -638,6 +677,11 @@ class Grid: reflect : bool, optional Reflect (include) outermost layers. Defaults to False. + Returns + ------- + updated : damask.Grid + Updated geometry representation. + """ valid = ['x','y','z'] if not set(directions).issubset(valid): @@ -670,6 +714,11 @@ class Grid: Direction(s) along which the grid is flipped. Valid entries are 'x', 'y', 'z'. + Returns + ------- + updated : damask.Grid + Updated geometry representation. + """ valid = ['x','y','z'] if not set(directions).issubset(valid): @@ -695,6 +744,11 @@ class Grid: periodic : Boolean, optional Assume grid to be periodic. Defaults to True. + Returns + ------- + updated : damask.Grid + Updated geometry representation. + """ return Grid(material = ndimage.interpolation.zoom( self.material, @@ -723,6 +777,11 @@ class Grid: periodic : Boolean, optional Assume grid to be periodic. Defaults to True. + Returns + ------- + updated : damask.Grid + Updated geometry representation. + """ def mostFrequent(arr,selection=None): me = arr[arr.size//2] @@ -746,7 +805,15 @@ class Grid: def renumber(self): - """Renumber sorted material indices as 0,...,N-1.""" + """ + Renumber sorted material indices as 0,...,N-1. + + Returns + ------- + updated : damask.Grid + Updated geometry representation. + + """ _,renumbered = np.unique(self.material,return_inverse=True) return Grid(material = renumbered.reshape(self.cells), @@ -767,6 +834,11 @@ class Grid: fill : int or float, optional Material index to fill the corners. Defaults to material.max() + 1. + Returns + ------- + updated : damask.Grid + Updated geometry representation. + """ if fill is None: fill = np.nanmax(self.material) + 1 dtype = float if isinstance(fill,float) or self.material.dtype in np.sctypes['float'] else int @@ -802,6 +874,11 @@ class Grid: fill : int or float, optional Material index to fill the background. Defaults to material.max() + 1. + Returns + ------- + updated : damask.Grid + Updated geometry representation. + """ if offset is None: offset = 0 if fill is None: fill = np.nanmax(self.material) + 1 @@ -834,6 +911,11 @@ class Grid: to_material : iterable of ints New material indices. + Returns + ------- + updated : damask.Grid + Updated geometry representation. + """ def mp(entry,mapper): return mapper[entry] if entry in mapper else entry @@ -849,7 +931,15 @@ class Grid: def sort(self): - """Sort material indices such that min(material) is located at (0,0,0).""" + """ + Sort material indices such that min(material) is located at (0,0,0). + + Returns + ------- + updated : damask.Grid + Updated geometry representation. + + """ a = self.material.flatten(order='F') from_ma = pd.unique(a) sort_idx = np.argsort(from_ma) @@ -884,6 +974,11 @@ class Grid: periodic : Boolean, optional Assume grid to be periodic. Defaults to True. + Returns + ------- + updated : damask.Grid + Updated geometry representation. + """ def tainted_neighborhood(stencil,trigger): diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index fff53fc73..47ac59eb3 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -357,7 +357,7 @@ class Rotation: Parameters ---------- - other : Rotation or list of Rotations. + other : damask.Rotation """ return self.copy(rotation=np.vstack(tuple(map(lambda x:x.quaternion, @@ -365,12 +365,28 @@ class Rotation: def flatten(self,order = 'C'): - """Flatten array.""" + """ + Flatten array. + + Returns + ------- + flattened : damask.Rotation + Rotation flattened to single dimension. + + """ return self.copy(rotation=self.quaternion.reshape((-1,4),order=order)) def reshape(self,shape,order = 'C'): - """Reshape array.""" + """ + Reshape array. + + Returns + ------- + reshaped : damask.Rotation + Rotation of given shape. + + """ if isinstance(shape,(int,np.integer)): shape = (shape,) return self.copy(rotation=self.quaternion.reshape(tuple(shape)+(4,),order=order)) @@ -387,6 +403,11 @@ class Rotation: Where to preferentially locate missing dimensions. Either 'left' or 'right' (default). + Returns + ------- + broadcasted : damask.Rotation + Rotation broadcasted to given shape. + """ if isinstance(shape,(int,np.integer)): shape = (shape,) return self.copy(rotation=np.broadcast_to(self.quaternion.reshape(util.shapeshifter(self.shape,shape,mode)+(4,)), @@ -404,7 +425,7 @@ class Rotation: Returns ------- - average : Rotation + average : damask.Rotation Weighted average of original Rotation field. References @@ -438,9 +459,14 @@ class Rotation: Parameters ---------- - other : Rotation + other : damask.Rotation Rotation to which the misorientation is computed. + Returns + ------- + g : damask.Rotation + Misorientation. + """ return other*~self @@ -531,10 +557,10 @@ class Rotation: Returns ------- - rho : numpy.ndarray of shape (...,4) containing + rho : numpy.ndarray of shape (...,4) containing [n_1, n_2, n_3, tan(ω/2)], ǀnǀ = 1 and ω ∈ [0,π] unless compact == True: - numpy.ndarray of shape (...,3) containing + numpy.ndarray of shape (...,3) containing tan(ω/2) [n_1, n_2, n_3], ω ∈ [0,π]. """ From 4d67c85a334cc47c12065d23199bfc7c22c3158a Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Fri, 23 Apr 2021 19:20:07 +0200 Subject: [PATCH 2/7] clarify out-of-place behavior and document return values --- PRIVATE | 2 +- python/damask/_configmaterial.py | 132 ++++++++++-------- python/damask/_result.py | 30 +++- python/damask/_table.py | 36 +++-- python/damask/grid_filters.py | 223 +++++++++++++++++++++--------- python/damask/mechanics.py | 10 +- python/damask/seeds.py | 15 ++ python/tests/test_grid_filters.py | 18 ++- 8 files changed, 324 insertions(+), 142 deletions(-) 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): From 3977e230b36849acbafb12c4667756da75254245 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 24 Apr 2021 07:13:36 +0200 Subject: [PATCH 3/7] documenting and ensuring consistent argument names 'name' for a dataset matches the analogy to a file name --- .../plastic/dislotwin_IF-steel.yaml | 1 + python/damask/_configmaterial.py | 34 +- python/damask/_orientation.py | 12 +- python/damask/_result.py | 332 +++++++++++++++--- python/damask/_table.py | 6 +- python/damask/_vtk.py | 10 +- python/damask/mechanics.py | 2 +- python/tests/test_Result.py | 20 +- 8 files changed, 329 insertions(+), 88 deletions(-) diff --git a/examples/config/phase/mechanical/plastic/dislotwin_IF-steel.yaml b/examples/config/phase/mechanical/plastic/dislotwin_IF-steel.yaml index 9d0c5be8b..bebe0f341 100644 --- a/examples/config/phase/mechanical/plastic/dislotwin_IF-steel.yaml +++ b/examples/config/phase/mechanical/plastic/dislotwin_IF-steel.yaml @@ -5,6 +5,7 @@ references: 10.1016/j.ijplas.2020.102779 - K. Sedighiani et al., Mechanics of Materials, submitted +output: [rho_dip, rho_mob] N_sl: [12, 12] b_sl: [2.49e-10, 2.49e-10] rho_mob_0: [2.81e12, 2.8e12] diff --git a/python/damask/_configmaterial.py b/python/damask/_configmaterial.py index ad085e5c2..e927874f3 100644 --- a/python/damask/_configmaterial.py +++ b/python/damask/_configmaterial.py @@ -365,48 +365,56 @@ class ConfigMaterial(Config): Examples -------- + Create a dual-phase steel microstructure for micromechanical simulations: + >>> import numpy as np >>> import damask - >>> m = damask.ConfigMaterial().material_add(phase = ['Aluminum','Steel'], - ... O = damask.Rotation.from_random(2), - ... homogenization = 'SX') + >>> m = damask.ConfigMaterial() + >>> m = m.material_add(phase = ['Ferrite','Martensite'], + ... O = damask.Rotation.from_random(2), + ... homogenization = 'SX') >>> m material: - constituents: - O: [0.577764, -0.146299, -0.617669, 0.513010] v: 1.0 - phase: Aluminum + phase: Ferrite homogenization: SX - constituents: - O: [0.184176, 0.340305, 0.737247, 0.553840] v: 1.0 - phase: Steel + phase: Martensite homogenization: SX homogenization: {} phase: {} - >>> m = damask.ConfigMaterial().material_add(phase = np.array(['Austenite','Martensite']).reshape(1,2), - ... O = damask.Rotation.from_random((2,2)), - ... v = np.array([0.2,0.8]).reshape(1,2), - ... homogenization = ['A','B']) + Create a duplex stainless steel microstructure for forming simulations: + + >>> import numpy as np + >>> import damask + >>> m = damask.ConfigMaterial() + >>> m = m.material_add(phase = np.array(['Austenite','Ferrite']).reshape(1,2), + ... O = damask.Rotation.from_random((2,2)), + ... v = np.array([0.2,0.8]).reshape(1,2), + ... homogenization = 'Taylor') >>> m material: - constituents: - phase: Austenite O: [0.659802978293224, 0.6953785848195171, 0.22426295326327111, -0.17554139512785227] v: 0.2 - - phase: Martensite + - phase: Ferrite O: [0.49356745891301596, 0.2841806579193434, -0.7487679215072818, -0.339085707289975] v: 0.8 - homogenization: A + homogenization: Taylor - constituents: - phase: Austenite O: [0.26542221365204055, 0.7268854930702071, 0.4474726435701472, -0.44828201137283735] v: 0.2 - - phase: Martensite + - phase: Ferrite O: [0.6545817158479885, -0.08004812803625233, -0.6226561293931374, 0.4212059104577611] v: 0.8 - homogenization: B + homogenization: Taylor homogenization: {} phase: {} diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 5cc412c98..00e74d36c 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -8,15 +8,15 @@ from . import tensor _parameter_doc = \ """lattice : str - Either a crystal family out of [triclinic, monoclinic, orthorhombic, tetragonal, hexagonal, cubic] - or a Bravais lattice out of [aP, mP, mS, oP, oS, oI, oF, tP, tI, hP, cP, cI, cF]. - When specifying a Bravais lattice, additional lattice parameters might be required: + Either a crystal family out of {triclinic, monoclinic, orthorhombic, tetragonal, hexagonal, cubic} + or a Bravais lattice out of {aP, mP, mS, oP, oS, oI, oF, tP, tI, hP, cP, cI, cF}. + When specifying a Bravais lattice, additional lattice parameters might be required. a : float, optional - Length of lattice parameter "a". + Length of lattice parameter 'a'. b : float, optional - Length of lattice parameter "b". + Length of lattice parameter 'b'. c : float, optional - Length of lattice parameter "c". + Length of lattice parameter 'c'. alpha : float, optional Angle between b and c lattice basis. beta : float, optional diff --git a/python/damask/_result.py b/python/damask/_result.py index a7f211f20..62ee2d8fe 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -34,7 +34,7 @@ def _read(dataset): return np.array(dataset,dtype=dtype) def _match(requested,existing): - """Find matches among two sets of labels.""" + """Find matches among two sets of names.""" def flatten_list(list_of_lists): return [e for e_ in list_of_lists for e in e_] @@ -57,14 +57,29 @@ def _empty_like(dataset,N_materialpoints,fill_float,fill_int): class Result: """ - Add data to and export from DADF5 files. + Add data to and export data from a DADF5 file. - DADF5 (DAMASK HDF5) files contain DAMASK results. - Their group/folder structure reflects the input data in material.yaml. + A DADF5 (DAMASK HDF5) file contain DAMASK results. + Its group/folder structure reflects the layout in material.yaml. - This class provides a custom view on the DADF5 file. + This class provides a customable 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. + Derived quantities are added to the file and existing data is + exported based on the current view. + + Examples + -------- + Open 'my_file.hdf5', which needs to contain deformation gradient 'F' + and first Piola-Kirchhoff stress 'P', add the Mises equivalent of the + Cauchy stress, and export it to VTK (file) and numpy.ndarray (memory). + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r.add_Cauchy() + >>> r.add_equivalent_Mises('sigma') + >>> r.save_VTK() + >>> r_last = r.view('increments',-1) + >>> sigma_vM_last = r_last.get('sigma_vM') """ @@ -167,6 +182,11 @@ class Result: Name of datasets; supports '?' and '*' wildcards. True is equivalent to '*', False is equivalent to []. + Returns + ------- + view : damask.Result + Modified or new view on the DADF5 file. + """ # allow True/False and string arguments if datasets is True: @@ -180,6 +200,7 @@ class Result: if what == 'increments': choice = [c if isinstance(c,str) and c.startswith(inc) else f'{inc}{c}' for c in choice] + if datasets == -1: choice = [self.increments[-1]] elif what == 'times': what = 'increments' if choice == ['*']: @@ -211,15 +232,31 @@ class Result: return dup - def allow_modification(self): - """Allow to overwrite existing data.""" + def modification_enable(self): + """ + Allow to modify existing data. + + Returns + ------- + modified_view : damask.Result + View where data is not write-protected. + + """ print(util.warn('Warning: Modification of existing datasets allowed!')) dup = self.copy() dup._allow_modification = True return dup - def disallow_modification(self): - """Disallow to overwrite existing data (default case).""" + def modification_disable(self): + """ + Disallow to modify existing data (default case). + + Returns + ------- + modified_view : damask.Result + View where data is write-protected. + + """ dup = self.copy() dup._allow_modification = False return dup @@ -227,7 +264,7 @@ class Result: def increments_in_range(self,start,end): """ - Select all increments within a given range. + Get all increments within a given range. Parameters ---------- @@ -236,6 +273,10 @@ class Result: end : int or str End increment. + Returns + ------- + increments : list of ints + Increment number of all increments within the given bounds. """ # compatibility hack ln = 3 if self.version_minor < 12 else 10 @@ -243,13 +284,13 @@ class Result: for i,inc in enumerate([int(i[ln:]) for i in self.increments]): s,e = map(lambda x: int(x[ln:] if isinstance(x,str) and x.startswith('inc') else x), (start,end)) if s <= inc <= e: - selected.append(self.increments[i]) + selected.append(int(self.increments[i].split('_')[1])) return selected def times_in_range(self,start,end): """ - Select all increments within a given time range. + Get all increments within a given time range. Parameters ---------- @@ -258,6 +299,10 @@ class Result: end : float Time of end increment. + Returns + ------- + times : list of float + Simulation time of all increments within the given bounds. """ selected = [] for i,time in enumerate(self.times): @@ -283,6 +328,20 @@ class Result: view : damask.Result View with where selected attributes are visible. + Examples + -------- + Get a view that shows only results from the initial configuration: + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r_first = r.view('increment',0) + + Get a view that shows all results of in simulation time [10,40]: + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r_t10to40 = r.view('times',r.times_in_range(10.0,40.0)) + """ return self._manage_view('set',what,datasets) @@ -304,6 +363,15 @@ class Result: modified_view : damask.Result View with more visible attributes. + Examples + -------- + Get a view that shows only results from first and last increment: + + >>> import damask + >>> r_empty = damask.Result('my_file.hdf5').view('increments',False) + >>> r_first = r_empty.view_more('increments',0) + >>> r_first_and_last = r.first.view_more('increments',-1) + """ return self._manage_view('add',what,datasets) @@ -325,37 +393,93 @@ class Result: modified_view : damask.Result View with less visible attributes. + Examples + -------- + Get a view that does not show the undeformed configuration: + + >>> import damask + >>> r_all = damask.Result('my_file.hdf5') + >>> r_deformed = r_all.view_less('increments',0) + """ return self._manage_view('del',what,datasets) - def rename(self,name_old,name_new): + def rename(self,name_src,name_dst): """ - Rename dataset. + Rename/move datasets (within the same group/folder). + + This operation is discouraged because the history of the + data becomes untracable and scientific integrity cannot be + ensured. Parameters ---------- - name_old : str - Name of the dataset to be renamed. - name_new : str - New name of the dataset. + name_src : str + Name of the datasets to be renamed. + name_dst : str + New name of the datasets. + + Examples + -------- + Rename datasets containing the deformation gradient from 'F' to 'def_grad': + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r_unprotected = r.modification_enable() + >>> r_unprotected.rename('F','def_grad') """ if not self._allow_modification: - raise PermissionError('Rename operation not permitted') + raise PermissionError('Renaming datasets not permitted') with h5py.File(self.fname,'a') as f: for inc in self.visible['increments']: for ty in ['phase','homogenization']: for label in self.visible[ty+'s']: for field in _match(self.visible['fields'],f['/'.join([inc,ty,label])].keys()): - path_old = '/'.join([inc,ty,label,field,name_old]) - path_new = '/'.join([inc,ty,label,field,name_new]) - if path_old in f.keys(): - f[path_new] = f[path_old] - f[path_new].attrs['renamed'] = f'original name: {name_old}' if h5py3 else \ - f'original name: {name_old}'.encode() - del f[path_old] + path_src = '/'.join([inc,ty,label,field,name_src]) + path_dst = '/'.join([inc,ty,label,field,name_dst]) + if path_src in f.keys(): + f[path_dst] = f[path_src] + f[path_dst].attrs['renamed'] = f'original name: {name_src}' if h5py3 else \ + f'original name: {name_src}'.encode() + del f[path_src] + + + def remove(self,name): + """ + Remove/delete datasets. + + This operation is discouraged because the history of the + data becomes untracable and scientific integrity cannot be + ensured. + + Parameters + ---------- + name : str + Name of the datasets to be deleted. + + Examples + -------- + Delete the deformation gradient 'F': + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r_unprotected = r.modification_enable() + >>> r_unprotected.remove('F') + + """ + if not self._allow_modification: + raise PermissionError('Removing datasets not permitted') + + with h5py.File(self.fname,'a') as f: + for inc in self.visible['increments']: + for ty in ['phase','homogenization']: + for label in self.visible[ty+'s']: + for field in _match(self.visible['fields'],f['/'.join([inc,ty,label])].keys()): + path = '/'.join([inc,ty,label,field,name]) + if path in f.keys(): del f[path] def list_data(self): @@ -438,7 +562,7 @@ class Result: Parameters ---------- x : str - Label of scalar, vector, or tensor dataset to take absolute value of. + Name of scalar, vector, or tensor dataset to take absolute value of. """ self._add_generic_pointwise(self._add_absolute,{'x':x}) @@ -459,24 +583,51 @@ class Result: 'creator': 'add_calculation' } } - def add_calculation(self,label,formula,unit='n/a',description=None): + def add_calculation(self,name,formula,unit='n/a',description=None): """ Add result of a general formula. Parameters ---------- - label : str - Label of resulting dataset. + name : str + Name of resulting dataset. formula : str - Formula to calculate resulting dataset. Existing datasets are referenced by '#TheirLabel#'. + Formula to calculate resulting dataset. Existing datasets are referenced by '#TheirName#'. unit : str, optional Physical unit of the result. description : str, optional Human-readable description of the result. + Examples + -------- + Add total dislocation density, i.e. the sum of mobile dislocation + density 'rho_mob' and dislocation dipole density 'rho_dip' over + all slip systems: + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r.add_calculation('rho_mob_total','np.sum(#rho_mob#,axis=1)', + ... '1/m²','total mobile dislocation density') + >>> r.add_calculation('rho_dip_total','np.sum(#rho_dip#,axis=1)', + ... '1/m²','total dislocation dipole density') + >>> r.add_calculation('rho_total','#rho_dip_total#+#rho_mob_total', + ... '1/m²','total dislocation density') + + Add Mises equivalent of the Cauchy stress without storage of + intermediate results. Define a user function for better readability: + + >>> import damask + >>> def equivalent_stress(F,P): + ... sigma = damask.mechanics.stress_Cauchy(F=F,P=P) + ... return damask.mechanics.equivalent_stress_Mises(sigma) + >>> r = damask.Result('my_file.hdf5') + >>> r.enable_user_function(equivalent_stress) + >>> r.add_calculation('sigma_vM','equivalent_stress(#F#,#P#)','Pa', + ... 'Mises equivalent of the Cauchy stress') + """ dataset_mapping = {d:d for d in set(re.findall(r'#(.*?)#',formula))} # datasets used in the formula - args = {'formula':formula,'label':label,'unit':unit,'description':description} + args = {'formula':formula,'label':name,'unit':unit,'description':description} self._add_generic_pointwise(self._add_calculation,dataset_mapping,args) @@ -500,9 +651,9 @@ class Result: Parameters ---------- P : str, optional - Label of the dataset containing the first Piola-Kirchhoff stress. Defaults to 'P'. + Name of the dataset containing the first Piola-Kirchhoff stress. Defaults to 'P'. F : str, optional - Label of the dataset containing the deformation gradient. Defaults to 'F'. + Name of the dataset containing the deformation gradient. Defaults to 'F'. """ self._add_generic_pointwise(self._add_stress_Cauchy,{'P':P,'F':F}) @@ -526,7 +677,15 @@ class Result: Parameters ---------- T : str - Label of tensor dataset. + Name of tensor dataset. + + Examples + -------- + Add the determinant of plastic deformation gradient 'F_p': + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r.add_determinant('F_p') """ self._add_generic_pointwise(self._add_determinant,{'T':T}) @@ -550,7 +709,7 @@ class Result: Parameters ---------- T : str - Label of tensor dataset. + Name of tensor dataset. """ self._add_generic_pointwise(self._add_deviator,{'T':T}) @@ -581,7 +740,7 @@ class Result: Parameters ---------- T_sym : str - Label of symmetric tensor dataset. + Name of symmetric tensor dataset. eigenvalue : str, optional Eigenvalue. Select from 'max', 'mid', 'min'. Defaults to 'max'. @@ -614,7 +773,7 @@ class Result: Parameters ---------- T_sym : str - Label of symmetric tensor dataset. + Name of symmetric tensor dataset. eigenvalue : str, optional Eigenvalue to which the eigenvector corresponds. Select from 'max', 'mid', 'min'. Defaults to 'max'. @@ -654,9 +813,17 @@ class Result: l : numpy.array of shape (3) Lab frame direction for inverse pole figure. q : str - Label of the dataset containing the crystallographic orientation as quaternions. + Name of the dataset containing the crystallographic orientation as quaternions. Defaults to 'O'. + Examples + -------- + Add the IPF color along [0,1,1] for orientation 'O': + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r.add_IPF_color(np.array([0,1,1])) + """ self._add_generic_pointwise(self._add_IPF_color,{'q':q},{'l':l}) @@ -679,7 +846,7 @@ class Result: Parameters ---------- T_sym : str - Label of symmetric tensor dataset. + Name of symmetric tensor dataset. """ self._add_generic_pointwise(self._add_maximum_shear,{'T_sym':T_sym}) @@ -713,11 +880,25 @@ class Result: Parameters ---------- T_sym : str - Label of symmetric tensorial stress or strain dataset. + Name of symmetric tensorial stress or strain dataset. kind : {'stress', 'strain', None}, optional Kind of the von Mises equivalent. Defaults to None, in which case it is selected based on the unit of the dataset ('1' -> strain, 'Pa' -> stress). + Examples + -------- + Add the Mises equivalent of the Cauchy stress 'sigma': + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r.add_equivalent_Mises('sigma') + + Add the Mises equivalent of the spatial logarithmic strain 'epsilon_V^0.0(F)': + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r.add_equivalent_Mises('epsilon_V^0.0(F)') + """ self._add_generic_pointwise(self._add_equivalent_Mises,{'T_sym':T_sym},{'kind':kind}) @@ -752,7 +933,7 @@ class Result: Parameters ---------- x : str - Label of vector or tensor dataset. + Name of vector or tensor dataset. ord : {non-zero int, inf, -inf, 'fro', 'nuc'}, optional Order of the norm. inf means NumPy’s inf object. For details refer to numpy.linalg.norm. @@ -780,9 +961,9 @@ class Result: Parameters ---------- P : str, optional - Label of first Piola-Kirchhoff stress dataset. Defaults to 'P'. + Name of first Piola-Kirchhoff stress dataset. Defaults to 'P'. F : str, optional - Label of deformation gradient dataset. Defaults to 'F'. + Name of deformation gradient dataset. Defaults to 'F'. """ self._add_generic_pointwise(self._add_stress_second_Piola_Kirchhoff,{'P':P,'F':F}) @@ -821,7 +1002,7 @@ class Result: # Parameters # ---------- # q : str - # Label of the dataset containing the crystallographic orientation as quaternions. + # Name of the dataset containing the crystallographic orientation as quaternions. # p : numpy.array of shape (3) # Crystallographic direction or plane. # polar : bool, optional @@ -848,8 +1029,16 @@ class Result: Parameters ---------- - F : str, optional - Label of deformation gradient dataset. + F : str + Name of deformation gradient dataset. + + Examples + -------- + Add the rotational part of deformation gradient 'F': + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r.add_rotation('F') """ self._add_generic_pointwise(self._add_rotation,{'F':F}) @@ -873,7 +1062,15 @@ class Result: Parameters ---------- T : str - Label of tensor dataset. + Name of tensor dataset. + + Examples + -------- + Add the hydrostatic part of the Cauchy stress 'sigma': + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r.add_spherical('sigma') """ self._add_generic_pointwise(self._add_spherical,{'T':T}) @@ -899,13 +1096,28 @@ class Result: Parameters ---------- F : str, optional - Label of deformation gradient dataset. Defaults to 'F'. + Name of deformation gradient dataset. Defaults to 'F'. t : {'V', 'U'}, optional Type of the polar decomposition, 'V' for left stretch tensor and 'U' for right stretch tensor. Defaults to 'V'. m : float, optional Order of the strain calculation. Defaults to 0.0. + Examples + -------- + Add the Biot strain based on the deformation gradient 'F': + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r.strain(t='U',m=0.5) + + Add the plastic Euler-Almansi strain based on the + plastic deformation gradient 'F_p': + + >>> import damask + >>> r = damask.Result('my_file.hdf5') + >>> r.strain('F_p','V',-1) + """ self._add_generic_pointwise(self._add_strain,{'F':F},{'t':t,'m':m}) @@ -929,7 +1141,7 @@ class Result: Parameters ---------- F : str, optional - Label of deformation gradient dataset. Defaults to 'F'. + Name of deformation gradient dataset. Defaults to 'F'. t : {'V', 'U'}, optional Type of the polar decomposition, 'V' for left stretch tensor and 'U' for right stretch tensor. Defaults to 'V'. @@ -1036,10 +1248,14 @@ class Result: """ Write XDMF file to directly visualize data in DADF5 file. + The XDMF format is only supported for structured grids + with single phase and single constituent. + For other cases use `save_VTK`. + Parameters ---------- output : (list of) str - Labels of the datasets to read. + Names of the datasets included in the XDMF file. Defaults to '*', in which case all datasets are considered. """ @@ -1169,10 +1385,16 @@ class Result: """ Export to VTK cell/point data. + One VTK file per visible increment is created. + For cell data, the VTK format is a rectilinear grid (.vtr) for + grid-based simulations and an unstructured grid (.vtu) for + mesh-baed simulations. For point data, the VTK format is poly + data (.vtp). + Parameters ---------- output : (list of) str, optional - Labels of the datasets to place. + Names of the datasets included in the VTK file. Defaults to '*', in which case all datasets are exported. mode : {'cell', 'point'} Export in cell format or point format. @@ -1251,7 +1473,7 @@ class Result: Parameters ---------- output : (list of) str - Labels of the datasets to read. + Names of the datasets to read. Defaults to '*', in which case all datasets are read. flatten : bool Remove singular levels of the folder hierarchy. @@ -1303,7 +1525,7 @@ class Result: Parameters ---------- output : (list of) str, optional - Labels of the datasets to place. + Names of the datasets to read. Defaults to '*', in which case all datasets are placed. flatten : bool Remove singular levels of the folder hierarchy. diff --git a/python/damask/_table.py b/python/damask/_table.py index a068ec02a..dd6a981b7 100644 --- a/python/damask/_table.py +++ b/python/damask/_table.py @@ -49,7 +49,7 @@ class Table: Returns ------- - slice : Table + slice : damask.Table Sliced part of the Table. Examples @@ -157,7 +157,7 @@ class Table: Parameters ---------- - other : Table + other : damask.Table Table to compare against. rtol : float, optional Relative tolerance of equality. @@ -185,7 +185,7 @@ class Table: Parameters ---------- - other : Table + other : damask.Table Table to compare against. rtol : float, optional Relative tolerance of equality. diff --git a/python/damask/_vtk.py b/python/damask/_vtk.py index 340bd1ea3..e415d5e90 100644 --- a/python/damask/_vtk.py +++ b/python/damask/_vtk.py @@ -137,8 +137,8 @@ class VTK: fname : str or pathlib.Path Filename for reading. Valid extensions are .vtr, .vtu, .vtp, and .vtk. dataset_type : str, optional - Name of the vtk.vtkDataSet subclass when opening a .vtk file. Valid types are vtkRectilinearGrid, - vtkUnstructuredGrid, and vtkPolyData. + Name of the vtk.vtkDataSet subclass when opening a .vtk file. + Valid types are vtkRectilinearGrid, vtkUnstructuredGrid, and vtkPolyData. """ if not os.path.isfile(fname): # vtk has a strange error handling @@ -149,13 +149,13 @@ class VTK: reader.SetFileName(str(fname)) if dataset_type is None: raise TypeError('Dataset type for *.vtk file not given.') - elif dataset_type.lower().endswith('rectilineargrid'): + elif dataset_type.lower().endswith(('rectilineargrid','rectilinear_grid')): reader.Update() vtk_data = reader.GetRectilinearGridOutput() - elif dataset_type.lower().endswith('unstructuredgrid'): + elif dataset_type.lower().endswith(('unstructuredgrid','unstructured_grid')): reader.Update() vtk_data = reader.GetUnstructuredGridOutput() - elif dataset_type.lower().endswith('polydata'): + elif dataset_type.lower().endswith(('polydata','poly_data')): reader.Update() vtk_data = reader.GetPolyDataOutput() else: diff --git a/python/damask/mechanics.py b/python/damask/mechanics.py index 8512023fc..2396d969e 100644 --- a/python/damask/mechanics.py +++ b/python/damask/mechanics.py @@ -3,7 +3,7 @@ Finite-strain continuum mechanics. Notes ----- -Collection of routines to operate on numpy.ndarrays of shape (...,3,3). +All routines operate on numpy.ndarrays of shape (...,3,3). """ diff --git a/python/tests/test_Result.py b/python/tests/test_Result.py index 0c34e7989..e79e25784 100644 --- a/python/tests/test_Result.py +++ b/python/tests/test_Result.py @@ -271,7 +271,7 @@ class TestResult: @pytest.mark.parametrize('overwrite',['off','on']) def test_add_overwrite(self,default,overwrite): - last = default.view('times',default.times_in_range(0,np.inf)[-1]) + last = default.view('increments',-1) last.add_stress_Cauchy() @@ -279,9 +279,9 @@ class TestResult: created_first = datetime.strptime(created_first,'%Y-%m-%d %H:%M:%S%z') if overwrite == 'on': - last = last.allow_modification() + last = last.modification_enable() else: - last = last.disallow_modification() + last = last.modification_disable() time.sleep(2.) try: @@ -301,14 +301,24 @@ class TestResult: def test_rename(self,default,allowed): if allowed == 'on': F = default.place('F') - default = default.allow_modification() + default = default.modification_enable() default.rename('F','new_name') assert np.all(F == default.place('new_name')) - default = default.disallow_modification() + default = default.modification_disable() with pytest.raises(PermissionError): default.rename('P','another_new_name') + @pytest.mark.parametrize('allowed',['off','on']) + def test_remove(self,default,allowed): + if allowed == 'on': + unsafe = default.modification_enable() + unsafe.remove('F') + assert unsafe.get('F') is None + else: + with pytest.raises(PermissionError): + default.remove('F') + @pytest.mark.parametrize('mode',['cell','node']) def test_coordinates(self,default,mode): if mode == 'cell': From 95831e53f621246c2f06ba1090fff26eca39a764 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 24 Apr 2021 14:47:52 +0200 Subject: [PATCH 4/7] using improved documentation --- PRIVATE | 2 +- python/damask/_grid.py | 36 ++++++++++++++++---------------- python/damask/_result.py | 2 ++ python/damask/_rotation.py | 2 +- python/damask/_vtk.py | 30 +++++++++++++++++++++++--- python/damask/grid_filters.py | 18 ++++++++++++++-- python/damask/mechanics.py | 2 -- python/damask/solver/__init__.py | 2 +- python/damask/tensor.py | 7 ++----- 9 files changed, 68 insertions(+), 33 deletions(-) diff --git a/PRIVATE b/PRIVATE index d58a002b0..156831f79 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit d58a002b0a43d240f143aee1396fdc766d87a886 +Subproject commit 156831f7962a1eb965023331a900121c5aee5e55 diff --git a/python/damask/_grid.py b/python/damask/_grid.py index 5f5bad63d..aebd8f92c 100644 --- a/python/damask/_grid.py +++ b/python/damask/_grid.py @@ -164,7 +164,7 @@ class Grid: Returns ------- loaded : damask.Grid - Geometry representation from file. + Grid-based geometry from file. """ v = VTK.load(fname if str(fname).endswith('.vtr') else str(fname)+'.vtr') @@ -198,7 +198,7 @@ class Grid: Returns ------- loaded : damask.Grid - Geometry representation from file. + Grid-based geometry from file. """ warnings.warn('Support for ASCII-based geom format will be removed in DAMASK 3.1.0', DeprecationWarning,2) @@ -294,7 +294,7 @@ class Grid: Returns ------- loaded : damask.Grid - Geometry representation from file. + Grid-based geometry from file. """ b = util.DREAM3D_base_group(fname) if base_group is None else base_group @@ -336,7 +336,7 @@ class Grid: Returns ------- new : damask.Grid - Geometry representation from values in table. + Grid-based geometry from values in table. """ cells,size,origin = grid_filters.cellsSizeOrigin_coordinates0_point(table.get(coordinates)) @@ -378,7 +378,7 @@ class Grid: Returns ------- new : damask.Grid - Geometry representation from tessellation. + Grid-based geometry from tessellation. """ if periodic: @@ -432,7 +432,7 @@ class Grid: Returns ------- new : damask.Grid - Geometry representation from tessellation. + Grid-based geometry from tessellation. """ coords = grid_filters.coordinates0_point(cells,size).reshape(-1,3) @@ -510,7 +510,7 @@ class Grid: Returns ------- new : damask.Grid - Geometry representation defined by a minimal surface. + Grid-based geometry from definition of minimal surface. Notes ----- @@ -635,7 +635,7 @@ class Grid: Returns ------- updated : damask.Grid - Updated geometry representation. + Updated grid-based geometry. """ # radius and center @@ -680,7 +680,7 @@ class Grid: Returns ------- updated : damask.Grid - Updated geometry representation. + Updated grid-based geometry. """ valid = ['x','y','z'] @@ -717,7 +717,7 @@ class Grid: Returns ------- updated : damask.Grid - Updated geometry representation. + Updated grid-based geometry. """ valid = ['x','y','z'] @@ -747,7 +747,7 @@ class Grid: Returns ------- updated : damask.Grid - Updated geometry representation. + Updated grid-based geometry. """ return Grid(material = ndimage.interpolation.zoom( @@ -780,7 +780,7 @@ class Grid: Returns ------- updated : damask.Grid - Updated geometry representation. + Updated grid-based geometry. """ def mostFrequent(arr,selection=None): @@ -811,7 +811,7 @@ class Grid: Returns ------- updated : damask.Grid - Updated geometry representation. + Updated grid-based geometry. """ _,renumbered = np.unique(self.material,return_inverse=True) @@ -837,7 +837,7 @@ class Grid: Returns ------- updated : damask.Grid - Updated geometry representation. + Updated grid-based geometry. """ if fill is None: fill = np.nanmax(self.material) + 1 @@ -877,7 +877,7 @@ class Grid: Returns ------- updated : damask.Grid - Updated geometry representation. + Updated grid-based geometry. """ if offset is None: offset = 0 @@ -914,7 +914,7 @@ class Grid: Returns ------- updated : damask.Grid - Updated geometry representation. + Updated grid-based geometry. """ def mp(entry,mapper): @@ -937,7 +937,7 @@ class Grid: Returns ------- updated : damask.Grid - Updated geometry representation. + Updated grid-based geometry. """ a = self.material.flatten(order='F') @@ -977,7 +977,7 @@ class Grid: Returns ------- updated : damask.Grid - Updated geometry representation. + Updated grid-based geometry. """ def tainted_neighborhood(stencil,trigger): diff --git a/python/damask/_result.py b/python/damask/_result.py index 62ee2d8fe..bc3d3916d 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -277,6 +277,7 @@ class Result: ------- increments : list of ints Increment number of all increments within the given bounds. + """ # compatibility hack ln = 3 if self.version_minor < 12 else 10 @@ -303,6 +304,7 @@ class Result: ------- times : list of float Simulation time of all increments within the given bounds. + """ selected = [] for i,time in enumerate(self.times): diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 47ac59eb3..6b2869033 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -24,7 +24,7 @@ class Rotation: - Euler angle triplets are implemented using the Bunge convention, with angular ranges of [0,2π], [0,π], [0,2π]. - The rotation angle ω is limited to the interval [0,π]. - - The real part of a quaternion is positive, Re(q) > 0 + - The real part of a quaternion is positive, Re(q) ≥ 0 - P = -1 (as default). Examples diff --git a/python/damask/_vtk.py b/python/damask/_vtk.py index e415d5e90..2a6cbd3f2 100644 --- a/python/damask/_vtk.py +++ b/python/damask/_vtk.py @@ -3,7 +3,6 @@ import multiprocessing as mp from pathlib import Path import numpy as np -import numpy.ma as ma import vtk from vtk.util.numpy_support import numpy_to_vtk as np_to_vtk from vtk.util.numpy_support import numpy_to_vtkIdTypeArray as np_to_vtkIdTypeArray @@ -51,6 +50,11 @@ class VTK: origin : iterable of float, len (3), optional Spatial origin coordinates. + Returns + ------- + new : damask.VTK + VTK-based geometry without nodal or cell data. + """ vtk_data = vtk.vtkRectilinearGrid() vtk_data.SetDimensions(*(np.array(grid)+1)) @@ -68,7 +72,7 @@ class VTK: """ Create VTK of type vtk.vtkUnstructuredGrid. - This is the common type for FEM solver results. + This is the common type for mesh solver results. Parameters ---------- @@ -80,6 +84,11 @@ class VTK: cell_type : str Name of the vtk.vtkCell subclass. Tested for TRIANGLE, QUAD, TETRA, and HEXAHEDRON. + Returns + ------- + new : damask.VTK + VTK-based geometry without nodal or cell data. + """ vtk_nodes = vtk.vtkPoints() vtk_nodes.SetData(np_to_vtk(nodes)) @@ -110,6 +119,11 @@ class VTK: points : numpy.ndarray of shape (:,3) Spatial position of the points. + Returns + ------- + new : damask.VTK + VTK-based geometry without nodal or cell data. + """ N = points.shape[0] vtk_points = vtk.vtkPoints() @@ -140,6 +154,11 @@ class VTK: Name of the vtk.vtkDataSet subclass when opening a .vtk file. Valid types are vtkRectilinearGrid, vtkUnstructuredGrid, and vtkPolyData. + Returns + ------- + loaded : damask.VTK + VTK-based geometry from file. + """ if not os.path.isfile(fname): # vtk has a strange error handling raise FileNotFoundError(f'no such file: {fname}') @@ -246,7 +265,7 @@ class VTK: raise ValueError('No label defined for numpy.ndarray') N_data = data.shape[0] - data_ = np.where(data.mask,data.fill_value,data) if isinstance(data,ma.MaskedArray) else\ + data_ = np.where(data.mask,data.fill_value,data) if isinstance(data,np.ma.MaskedArray) else\ data d = np_to_vtk((data_.astype(np.single) if data_.dtype in [np.double, np.longdouble] else data_).reshape(N_data,-1),deep=True) # avoid large files @@ -277,6 +296,11 @@ class VTK: label : str Data label. + Returns + ------- + data : numpy.ndarray + Data stored under the given label. + """ cell_data = self.vtk_data.GetCellData() for a in range(cell_data.GetNumberOfArrays()): diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index d82f25ea9..fe5e290c6 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -1,8 +1,6 @@ """ Filters for operations on regular grids. -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. @@ -263,6 +261,11 @@ def cellsSizeOrigin_coordinates0_point(coordinates0,ordered=True): Expect coordinates0 data to be ordered (x fast, z slow). Defaults to True. + Returns + ------- + DNA : tuple with 3 numpy.ndarray of shape (3) + Information to reconstruct grid: cells, size, origin. + """ coords = [_np.unique(coordinates0[:,i]) for i in range(3)] mincorner = _np.array(list(map(min,coords))) @@ -415,6 +418,11 @@ def cellsSizeOrigin_coordinates0_node(coordinates0,ordered=True): Expect coordinates0 data to be ordered (x fast, z slow). Defaults to True. + Returns + ------- + DNA : tuple with 3 numpy.ndarray of shape (3) + Information to reconstruct grid: cells, size, origin. + """ coords = [_np.unique(coordinates0[:,i]) for i in range(3)] mincorner = _np.array(list(map(min,coords))) @@ -452,6 +460,7 @@ def point_to_node(cell_data): ------- 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,)) @@ -491,6 +500,11 @@ def coordinates0_valid(coordinates0): coordinates0 : numpy.ndarray Array of undeformed cell coordinates. + Returns + ------- + valid : bool + Wheter the coordinates lie on a regular grid. + """ try: cellsSizeOrigin_coordinates0_point(coordinates0,ordered=True) diff --git a/python/damask/mechanics.py b/python/damask/mechanics.py index 2396d969e..6ba785df1 100644 --- a/python/damask/mechanics.py +++ b/python/damask/mechanics.py @@ -1,8 +1,6 @@ """ Finite-strain continuum mechanics. -Notes ------ All routines operate on numpy.ndarrays of shape (...,3,3). """ diff --git a/python/damask/solver/__init__.py b/python/damask/solver/__init__.py index 298adb1f7..01cfeed9b 100644 --- a/python/damask/solver/__init__.py +++ b/python/damask/solver/__init__.py @@ -1,3 +1,3 @@ -"""Tools to control the various solvers.""" +"""Run simulations directly from python.""" from ._marc import Marc # noqa diff --git a/python/damask/tensor.py b/python/damask/tensor.py index 00fc7e72a..cf5d94020 100644 --- a/python/damask/tensor.py +++ b/python/damask/tensor.py @@ -1,10 +1,7 @@ """ -Tensor operations. +Tensor mathematics. -Notes ------ -This is not a tensor class, but a collection of routines -to operate on numpy.ndarrays of shape (...,3,3). +All routines operate on numpy.ndarrays of shape (...,3,3). """ From 24046ef6830e2fea4751efe3c4059d477853986c Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sat, 24 Apr 2021 18:00:57 +0200 Subject: [PATCH 5/7] support the user --- PRIVATE | 2 +- python/damask/__init__.py | 12 ++--- python/damask/_colormap.py | 15 ++++++ python/damask/grid_filters.py | 8 +-- python/damask/seeds.py | 12 ++--- python/damask/util.py | 91 ++++++++++++++++++++++++++++++++--- 6 files changed, 113 insertions(+), 27 deletions(-) diff --git a/PRIVATE b/PRIVATE index 156831f79..afffa8d04 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 156831f7962a1eb965023331a900121c5aee5e55 +Subproject commit afffa8d04e110282e514a4e57d0bad9c76effe01 diff --git a/python/damask/__init__.py b/python/damask/__init__.py index af9933954..ad46d454f 100644 --- a/python/damask/__init__.py +++ b/python/damask/__init__.py @@ -1,11 +1,4 @@ -""" -Tools for pre and post processing of DAMASK simulations. - -Modules that contain only one class (of the same name), -are prefixed by a '_'. For example, '_colormap' contains -a class called 'Colormap' which is imported as 'damask.Colormap'. - -""" +"""Tools for managing DAMASK simulations.""" from pathlib import Path as _Path import re as _re @@ -15,7 +8,6 @@ with open(_Path(__file__).parent/_Path('VERSION')) as _f: version = _re.sub(r'^v','',_f.readline().strip()) __version__ = version -# make classes directly accessible as damask.Class from . import util # noqa from . import seeds # noqa from . import tensor # noqa @@ -23,6 +15,8 @@ from . import mechanics # noqa from . import solver # noqa from . import grid_filters # noqa from . import lattice # noqa +#Modules that contain only one class (of the same name), are prefixed by a '_'. +#For example, '_colormap' containsa class called 'Colormap' which is imported as 'damask.Colormap'. from ._rotation import Rotation # noqa from ._orientation import Orientation # noqa from ._table import Table # noqa diff --git a/python/damask/_colormap.py b/python/damask/_colormap.py index 0d36c1ee6..02e2b922c 100644 --- a/python/damask/_colormap.py +++ b/python/damask/_colormap.py @@ -96,6 +96,11 @@ class Colormap(mpl.colors.ListedColormap): new : damask.Colormap Colormap within given bounds. + Examples + -------- + >>> import damask + >>> damask.Colormap.from_range((0,0,1),(0,0,0),'blue_to_black') + """ low_high = np.vstack((low,high)) if model.lower() == 'rgb': @@ -160,6 +165,11 @@ class Colormap(mpl.colors.ListedColormap): new : damask.Colormap Predefined colormap. + Examples + -------- + >>> import damask + >>> damask.Colormap.from_predefined('strain') + """ # matplotlib presets try: @@ -230,6 +240,11 @@ class Colormap(mpl.colors.ListedColormap): damask.Colormap The reversed colormap. + Examples + -------- + >>> import damask + >>> damask.Colormap.from_predefined('stress').reversed() + """ rev = super(Colormap,self).reversed(name) return Colormap(np.array(rev.colors),rev.name[:-4] if rev.name.endswith('_r_r') else rev.name) diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index fe5e290c6..bc9194947 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -263,8 +263,8 @@ def cellsSizeOrigin_coordinates0_point(coordinates0,ordered=True): Returns ------- - DNA : tuple with 3 numpy.ndarray of shape (3) - Information to reconstruct grid: cells, size, origin. + cells, size, origin : Three numpy.ndarray, each of shape (3) + Information to reconstruct grid. """ coords = [_np.unique(coordinates0[:,i]) for i in range(3)] @@ -420,8 +420,8 @@ def cellsSizeOrigin_coordinates0_node(coordinates0,ordered=True): Returns ------- - DNA : tuple with 3 numpy.ndarray of shape (3) - Information to reconstruct grid: cells, size, origin. + cells, size, origin : Three numpy.ndarray, each of shape (3) + Information to reconstruct grid. """ coords = [_np.unique(coordinates0[:,i]) for i in range(3)] diff --git a/python/damask/seeds.py b/python/damask/seeds.py index 74d15f1ef..aeab2e64a 100644 --- a/python/damask/seeds.py +++ b/python/damask/seeds.py @@ -26,8 +26,8 @@ def from_random(size,N_seeds,cells=None,rng_seed=None): Returns ------- - new : numpy.ndarray of shape (N_seeds,3) - Coordinates in 3D space. + coords : numpy.ndarray of shape (N_seeds,3) + Seed coordinates in 3D space. """ rng = _np.random.default_rng(rng_seed) @@ -63,8 +63,8 @@ def from_Poisson_disc(size,N_seeds,N_candidates,distance,periodic=True,rng_seed= Returns ------- - new : numpy.ndarray of shape (N_seeds,3) - Coordinates in 3D space. + coords : numpy.ndarray of shape (N_seeds,3) + Seed coordinates in 3D space. """ rng = _np.random.default_rng(rng_seed) @@ -106,8 +106,8 @@ def from_grid(grid,selection=None,invert=False,average=False,periodic=True): Returns ------- - new : numpy.ndarray of shape (...,3) - Coordinates in 3D space. + coords, materials : numpy.ndarray of shape (:,3), numpy.ndarray of shape (:) + Seed coordinates in 3D space, material IDs. """ material = grid.material.reshape((-1,1),order='F') diff --git a/python/damask/util.py b/python/damask/util.py index b519fb15e..fcf41c251 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -49,7 +49,7 @@ _colors = { #################################################################################################### def srepr(arg,glue = '\n'): r""" - Join arguments with glue string. + Join items with glue string. Parameters ---------- @@ -58,6 +58,11 @@ def srepr(arg,glue = '\n'): glue : str, optional Glue used for joining operation. Defaults to \n. + Returns + ------- + joined : str + String representation of the joined items. + """ if (not hasattr(arg, 'strip') and (hasattr(arg, '__getitem__') or @@ -68,19 +73,71 @@ def srepr(arg,glue = '\n'): def emph(what): - """Formats string with emphasis.""" + """ + Format with emphasis. + + Parameters + ---------- + what : object with __repr__ or iterable of objects with __repr__. + Message to format. + + Returns + ------- + formatted : str + Formatted string representation of the joined items. + + """ return _colors['bold']+srepr(what)+_colors['end_color'] def deemph(what): - """Formats string with deemphasis.""" + """ + Format with deemphasis. + + Parameters + ---------- + what : object with __repr__ or iterable of objects with __repr__. + Message to format. + + Returns + ------- + formatted : str + Formatted string representation of the joined items. + + """ return _colors['dim']+srepr(what)+_colors['end_color'] def warn(what): - """Formats string for warning.""" + """ + Format for warning. + + Parameters + ---------- + what : object with __repr__ or iterable of objects with __repr__. + Message to format. + + Returns + ------- + formatted : str + Formatted string representation of the joined items. + + """ return _colors['warning']+emph(what)+_colors['end_color'] def strikeout(what): - """Formats string as strikeout.""" + """ + Format as strikeout. + + Parameters + ---------- + what : object with __repr__ or iterable of objects with __repr__. + Message to format. + + Returns + ------- + formatted : str + Formatted string representation of the joined items. + + """ return _colors['crossout']+srepr(what)+_colors['end_color'] @@ -97,6 +154,11 @@ def execute(cmd,wd='./',env=None): env : dict, optional Environment for execution. + Returns + ------- + stdout, stderr : str + Output of the executed command. + """ print(f"executing '{cmd}' in '{wd}'") process = subprocess.run(shlex.split(cmd), @@ -157,6 +219,11 @@ def scale_to_coprime(v): v : numpy.ndarray of shape (:) Vector to scale. + Returns + ------- + m : numpy.ndarray of shape (:) + Vector scaled to co-prime numbers. + """ MAX_DENOMINATOR = 1000000 @@ -371,9 +438,14 @@ def DREAM3D_base_group(fname): Parameters ---------- - fname : str + fname : str or pathlib.Path Filename of the DREAM.3D (HDF5) file. + Returns + ------- + path : str + Path to the base group. + """ with h5py.File(fname,'r') as f: base_group = f.visit(lambda path: path.rsplit('/',2)[0] if '_SIMPL_GEOMETRY/SPACING' in path else None) @@ -393,9 +465,14 @@ def DREAM3D_cell_data_group(fname): Parameters ---------- - fname : str + fname : str or pathlib.Path Filename of the DREAM.3D (HDF5) file. + Returns + ------- + path : str + Path to the cell data group. + """ base_group = DREAM3D_base_group(fname) with h5py.File(fname,'r') as f: From 690b478013758459d25a9227cbd223145dde6bc2 Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Sat, 24 Apr 2021 13:12:44 -0400 Subject: [PATCH 6/7] [skip ci] language polishing --- python/damask/_result.py | 32 ++++++++++++++++---------------- python/damask/_vtk.py | 6 +++--- python/damask/grid_filters.py | 4 ++-- python/damask/solver/__init__.py | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/python/damask/_result.py b/python/damask/_result.py index bc3d3916d..0ca160922 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -59,17 +59,17 @@ class Result: """ Add data to and export data from a DADF5 file. - A DADF5 (DAMASK HDF5) file contain DAMASK results. + A DADF5 (DAMASK HDF5) file contains DAMASK results. Its group/folder structure reflects the layout in material.yaml. - This class provides a customable view on the DADF5 file. + This class provides a customizable view on the DADF5 file. Upon initialization, all attributes are visible. Derived quantities are added to the file and existing data is exported based on the current view. Examples -------- - Open 'my_file.hdf5', which needs to contain deformation gradient 'F' + Open 'my_file.hdf5', which is assumed to contain deformation gradient 'F' and first Piola-Kirchhoff stress 'P', add the Mises equivalent of the Cauchy stress, and export it to VTK (file) and numpy.ndarray (memory). @@ -234,12 +234,12 @@ class Result: def modification_enable(self): """ - Allow to modify existing data. + Allow modification of existing data. Returns ------- modified_view : damask.Result - View where data is not write-protected. + View without write-protection of existing data. """ print(util.warn('Warning: Modification of existing datasets allowed!')) @@ -249,12 +249,12 @@ class Result: def modification_disable(self): """ - Disallow to modify existing data (default case). + Prevent modification of existing data (default case). Returns ------- modified_view : damask.Result - View where data is write-protected. + View with write-protection of existing data. """ dup = self.copy() @@ -328,7 +328,7 @@ class Result: Returns ------- view : damask.Result - View with where selected attributes are visible. + View with only the selected attributes being visible. Examples -------- @@ -338,7 +338,7 @@ class Result: >>> r = damask.Result('my_file.hdf5') >>> r_first = r.view('increment',0) - Get a view that shows all results of in simulation time [10,40]: + Get a view that shows all results between simulation times of 10 to 40: >>> import damask >>> r = damask.Result('my_file.hdf5') @@ -363,7 +363,7 @@ class Result: Returns ------- modified_view : damask.Result - View with more visible attributes. + View with additional visible attributes. Examples -------- @@ -380,7 +380,7 @@ class Result: def view_less(self,what,datasets): """ - Delete from view. + Remove from view. Parameters ---------- @@ -393,11 +393,11 @@ class Result: Returns ------- modified_view : damask.Result - View with less visible attributes. + View with fewer visible attributes. Examples -------- - Get a view that does not show the undeformed configuration: + Get a view that omits the undeformed configuration: >>> import damask >>> r_all = damask.Result('my_file.hdf5') @@ -1396,7 +1396,7 @@ class Result: Parameters ---------- output : (list of) str, optional - Names of the datasets included in the VTK file. + Names of the datasets to export to the VTK file. Defaults to '*', in which case all datasets are exported. mode : {'cell', 'point'} Export in cell format or point format. @@ -1521,7 +1521,7 @@ class Result: in the DADF5 file. Multi-phase data is fused into a single output. - `place` is equivalent to `read` if only one phase/homogenization + `place` is equivalent to `get` if only one phase/homogenization and one constituent is present. Parameters @@ -1537,7 +1537,7 @@ class Result: Remove branches with no data. Defaults to True. constituents : (list of) int, optional Constituents to consider. - Defaults to 'None', in which case all constituents are considered. + Defaults to None, in which case all constituents are considered. fill_float : float Fill value for non-existent entries of floating point type. Defaults to NaN. diff --git a/python/damask/_vtk.py b/python/damask/_vtk.py index 2a6cbd3f2..61f3efd80 100644 --- a/python/damask/_vtk.py +++ b/python/damask/_vtk.py @@ -46,9 +46,9 @@ class VTK: grid : iterable of int, len (3) Number of cells along each dimension. size : iterable of float, len (3) - Physical lengths along each dimension. + Physical length along each dimension. origin : iterable of float, len (3), optional - Spatial origin coordinates. + Coordinates of grid origin. Returns ------- @@ -161,7 +161,7 @@ class VTK: """ if not os.path.isfile(fname): # vtk has a strange error handling - raise FileNotFoundError(f'no such file: {fname}') + raise FileNotFoundError(f'No such file: {fname}') ext = Path(fname).suffix if ext == '.vtk' or dataset_type is not None: reader = vtk.vtkGenericDataObjectReader() diff --git a/python/damask/grid_filters.py b/python/damask/grid_filters.py index bc9194947..7d0bdab64 100644 --- a/python/damask/grid_filters.py +++ b/python/damask/grid_filters.py @@ -493,7 +493,7 @@ def node_to_point(node_data): def coordinates0_valid(coordinates0): """ - Check whether coordinates lie on a regular grid. + Check whether coordinates form a regular grid. Parameters ---------- @@ -503,7 +503,7 @@ def coordinates0_valid(coordinates0): Returns ------- valid : bool - Wheter the coordinates lie on a regular grid. + Whether the coordinates form a regular grid. """ try: diff --git a/python/damask/solver/__init__.py b/python/damask/solver/__init__.py index 01cfeed9b..bd92ee9a8 100644 --- a/python/damask/solver/__init__.py +++ b/python/damask/solver/__init__.py @@ -1,3 +1,3 @@ -"""Run simulations directly from python.""" +"""Run simulations directly from Python.""" from ._marc import Marc # noqa From 0459f17f583458652766c165779190d59f43410a Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 25 Apr 2021 07:47:00 +0200 Subject: [PATCH 7/7] improved usability --- PRIVATE | 2 +- python/damask/_config.py | 15 +++++++++++++++ python/damask/_configmaterial.py | 17 ++++++++++++++++- python/damask/_grid.py | 5 +++++ python/damask/_result.py | 17 +++++++++-------- python/damask/_rotation.py | 10 +++++----- python/tests/test_Result.py | 14 +++++++------- 7 files changed, 58 insertions(+), 22 deletions(-) diff --git a/PRIVATE b/PRIVATE index afffa8d04..7f0594060 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit afffa8d04e110282e514a4e57d0bad9c76effe01 +Subproject commit 7f0594060779d9a8a4e774d558134309ab77b96e diff --git a/python/damask/_config.py b/python/damask/_config.py index 989f28fcd..627fc88c8 100644 --- a/python/damask/_config.py +++ b/python/damask/_config.py @@ -61,6 +61,11 @@ class Config(dict): other : damask.Config or dict Key-value pairs that update self. + Returns + ------- + updated : damask.Config + Updated configuration. + """ duplicate = self.copy() duplicate.update(other) @@ -81,6 +86,11 @@ class Config(dict): keys : iterable or scalar Label of the key(s) to remove. + Returns + ------- + updated : damask.Config + Updated configuration. + """ duplicate = self.copy() for k in keys if isinstance(keys, Iterable) and not isinstance(keys, str) else [keys]: @@ -98,6 +108,11 @@ class Config(dict): fname : file, str, or pathlib.Path Filename or file for writing. + Returns + ------- + loaded : damask.Config + Configuration from file. + """ try: fhandle = open(fname) diff --git a/python/damask/_configmaterial.py b/python/damask/_configmaterial.py index e927874f3..b5b38a417 100644 --- a/python/damask/_configmaterial.py +++ b/python/damask/_configmaterial.py @@ -54,7 +54,12 @@ class ConfigMaterial(Config): Parameters ---------- fname : file, str, or pathlib.Path, optional - Filename or file for writing. Defaults to 'material.yaml'. + Filename or file to read from. Defaults to 'material.yaml'. + + Returns + ------- + loaded : damask.ConfigMaterial + Material configuration from file. """ return super(ConfigMaterial,cls).load(fname) @@ -103,6 +108,11 @@ class ConfigMaterial(Config): and grain- or cell-wise data. Defaults to None, in which case it is set as the path that contains _SIMPL_GEOMETRY/SPACING. + Returns + ------- + loaded : damask.ConfigMaterial + Material configuration from file. + """ b = util.DREAM3D_base_group(fname) if base_group is None else base_group c = util.DREAM3D_cell_data_group(fname) if cell_data is None else cell_data @@ -146,6 +156,11 @@ class ConfigMaterial(Config): Keyword arguments where the key is the name and the value specifies the label of the data column in the table. + Returns + ------- + new : damask.ConfigMaterial + Material configuration from values in table. + Examples -------- >>> import damask diff --git a/python/damask/_grid.py b/python/damask/_grid.py index aebd8f92c..1b58870e4 100644 --- a/python/damask/_grid.py +++ b/python/damask/_grid.py @@ -1012,6 +1012,11 @@ class Grid: Direction(s) along which the boundaries are determined. Valid entries are 'x', 'y', 'z'. Defaults to 'xyz'. + Returns + ------- + grain_boundaries : damask.VTK + VTK-based geometry of grain boundary network. + """ valid = ['x','y','z'] if not set(directions).issubset(valid): diff --git a/python/damask/_result.py b/python/damask/_result.py index 0ca160922..f0f944790 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -585,16 +585,17 @@ class Result: 'creator': 'add_calculation' } } - def add_calculation(self,name,formula,unit='n/a',description=None): + def add_calculation(self,formula,name,unit='n/a',description=None): """ Add result of a general formula. Parameters ---------- - name : str - Name of resulting dataset. formula : str - Formula to calculate resulting dataset. Existing datasets are referenced by '#TheirName#'. + Formula to calculate resulting dataset. + Existing datasets are referenced by '#TheirName#'. + name : str + Name of resulting dataset. unit : str, optional Physical unit of the result. description : str, optional @@ -608,11 +609,11 @@ class Result: >>> import damask >>> r = damask.Result('my_file.hdf5') - >>> r.add_calculation('rho_mob_total','np.sum(#rho_mob#,axis=1)', + >>> r.add_calculation('np.sum(#rho_mob#,axis=1)','rho_mob_total', ... '1/m²','total mobile dislocation density') - >>> r.add_calculation('rho_dip_total','np.sum(#rho_dip#,axis=1)', + >>> r.add_calculation(''np.sum(#rho_dip#,axis=1)',rho_dip_total', ... '1/m²','total dislocation dipole density') - >>> r.add_calculation('rho_total','#rho_dip_total#+#rho_mob_total', + >>> r.add_calculation('#rho_dip_total#+#rho_mob_total','rho_total', ... '1/m²','total dislocation density') Add Mises equivalent of the Cauchy stress without storage of @@ -624,7 +625,7 @@ class Result: ... return damask.mechanics.equivalent_stress_Mises(sigma) >>> r = damask.Result('my_file.hdf5') >>> r.enable_user_function(equivalent_stress) - >>> r.add_calculation('sigma_vM','equivalent_stress(#F#,#P#)','Pa', + >>> r.add_calculation('equivalent_stress(#F#,#P#)','sigma_vM','Pa', ... 'Mises equivalent of the Cauchy stress') """ diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 6b2869033..3bd84fcb1 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -357,7 +357,7 @@ class Rotation: Parameters ---------- - other : damask.Rotation + other : damask.Rotation """ return self.copy(rotation=np.vstack(tuple(map(lambda x:x.quaternion, @@ -370,7 +370,7 @@ class Rotation: Returns ------- - flattened : damask.Rotation + flattened : damask.Rotation Rotation flattened to single dimension. """ @@ -383,7 +383,7 @@ class Rotation: Returns ------- - reshaped : damask.Rotation + reshaped : damask.Rotation Rotation of given shape. """ @@ -405,7 +405,7 @@ class Rotation: Returns ------- - broadcasted : damask.Rotation + broadcasted : damask.Rotation Rotation broadcasted to given shape. """ @@ -464,7 +464,7 @@ class Rotation: Returns ------- - g : damask.Rotation + g : damask.Rotation Misorientation. """ diff --git a/python/tests/test_Result.py b/python/tests/test_Result.py index e79e25784..56bc4a00f 100644 --- a/python/tests/test_Result.py +++ b/python/tests/test_Result.py @@ -110,14 +110,14 @@ class TestResult: def test_add_calculation(self,default,tmp_path,mode): if mode == 'direct': - default.add_calculation('x','2.0*np.abs(#F#)-1.0','-','my notes') + default.add_calculation('2.0*np.abs(#F#)-1.0','x','-','my notes') else: with open(tmp_path/'f.py','w') as f: f.write("import numpy as np\ndef my_func(field):\n return 2.0*np.abs(field)-1.0\n") sys.path.insert(0,str(tmp_path)) import f default.enable_user_function(f.my_func) - default.add_calculation('x','my_func(#F#)','-','my notes') + default.add_calculation('my_func(#F#)','x','-','my notes') in_memory = 2.0*np.abs(default.place('F'))-1.0 in_file = default.place('x') @@ -193,14 +193,14 @@ class TestResult: def test_add_Mises_invalid(self,default): default.add_stress_Cauchy('P','F') - default.add_calculation('sigma_y','#sigma#',unit='y') + default.add_calculation('#sigma#','sigma_y',unit='y') default.add_equivalent_Mises('sigma_y') assert default.get('sigma_y_vM') is None def test_add_Mises_stress_strain(self,default): default.add_stress_Cauchy('P','F') - default.add_calculation('sigma_y','#sigma#',unit='y') - default.add_calculation('sigma_x','#sigma#',unit='x') + default.add_calculation('#sigma#','sigma_y',unit='y') + default.add_calculation('#sigma#','sigma_x',unit='x') default.add_equivalent_Mises('sigma_y',kind='strain') default.add_equivalent_Mises('sigma_x',kind='stress') assert not np.allclose(default.place('sigma_y_vM'),default.place('sigma_x_vM')) @@ -285,7 +285,7 @@ class TestResult: time.sleep(2.) try: - last.add_calculation('sigma','#sigma#*0.0+311.','not the Cauchy stress') + last.add_calculation('#sigma#*0.0+311.','sigma','not the Cauchy stress') except ValueError: pass @@ -362,7 +362,7 @@ class TestResult: def test_XDMF(self,tmp_path,single_phase,update,ref_path): for shape in [('scalar',()),('vector',(3,)),('tensor',(3,3)),('matrix',(12,))]: for dtype in ['f4','f8','i1','i2','i4','i8','u1','u2','u4','u8']: - single_phase.add_calculation(f'{shape[0]}_{dtype}',f"np.ones(np.shape(#F#)[0:1]+{shape[1]},'{dtype}')") + single_phase.add_calculation(f"np.ones(np.shape(#F#)[0:1]+{shape[1]},'{dtype}')",f'{shape[0]}_{dtype}') fname = os.path.splitext(os.path.basename(single_phase.fname))[0]+'.xdmf' os.chdir(tmp_path) single_phase.save_XDMF()