Merge branch 'improved-docstrings' into 'development'

Improvements to Python docstrings

See merge request damask/DAMASK!729
This commit is contained in:
Franz Roters 2023-02-21 15:27:08 +00:00
commit 538770278f
13 changed files with 280 additions and 187 deletions

View File

@ -28,10 +28,10 @@ _REF_WHITE = np.array([.95047, 1.00000, 1.08883])
class Colormap(mpl.colors.ListedColormap): class Colormap(mpl.colors.ListedColormap):
""" """
Enhance matplotlib colormap functionality to be used within DAMASK. Enhance matplotlib colormap functionality for use within DAMASK.
Colors are internally stored as R(ed) G(green) B(lue) values. Colors are internally stored as R(ed) G(green) B(lue) values.
The colormap can be used in matplotlib, seaborn, etc., or can A colormap can be used in matplotlib, seaborn, etc., or can be
exported to file for external use. exported to file for external use.
References References
@ -153,12 +153,12 @@ class Colormap(mpl.colors.ListedColormap):
- 'hsl': Hue Saturation Luminance. - 'hsl': Hue Saturation Luminance.
- 'xyz': CIE Xyz. - 'xyz': CIE Xyz.
- 'lab': CIE Lab. - 'lab': CIE Lab.
- 'msh': Msh (for perceptual uniform interpolation). - 'msh': Msh (for perceptually uniform interpolation).
Returns Returns
------- -------
new : damask.Colormap new : damask.Colormap
Colormap within given bounds. Colormap spanning given bounds.
Examples Examples
-------- --------
@ -288,6 +288,7 @@ class Colormap(mpl.colors.ListedColormap):
Value range (left,right) spanned by colormap. Value range (left,right) spanned by colormap.
gap : field.dtype, optional gap : field.dtype, optional
Transparent value. NaN will always be rendered transparent. Transparent value. NaN will always be rendered transparent.
Defaults to None.
Returns Returns
------- -------
@ -334,6 +335,7 @@ class Colormap(mpl.colors.ListedColormap):
-------- --------
>>> import damask >>> import damask
>>> damask.Colormap.from_predefined('stress').reversed() >>> damask.Colormap.from_predefined('stress').reversed()
Colormap: stress_r
""" """
rev = super().reversed(name) rev = super().reversed(name)
@ -353,6 +355,7 @@ class Colormap(mpl.colors.ListedColormap):
If None, colormap name + suffix. If None, colormap name + suffix.
suffix: str, optional suffix: str, optional
Extension to use for colormap file. Extension to use for colormap file.
Defaults to empty.
Returns Returns
------- -------
@ -452,8 +455,8 @@ class Colormap(mpl.colors.ListedColormap):
References References
---------- ----------
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf | https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
https://www.kennethmoreland.com/color-maps/diverging_map.py | https://www.kennethmoreland.com/color-maps/diverging_map.py
""" """
def rad_diff(a,b): def rad_diff(a,b):
@ -735,8 +738,8 @@ class Colormap(mpl.colors.ListedColormap):
References References
---------- ----------
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf | https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
https://www.kennethmoreland.com/color-maps/diverging_map.py | https://www.kennethmoreland.com/color-maps/diverging_map.py
""" """
M = np.linalg.norm(lab) M = np.linalg.norm(lab)
@ -763,8 +766,8 @@ class Colormap(mpl.colors.ListedColormap):
References References
---------- ----------
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf | https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
https://www.kennethmoreland.com/color-maps/diverging_map.py | https://www.kennethmoreland.com/color-maps/diverging_map.py
""" """
return np.array([ return np.array([

View File

@ -307,15 +307,13 @@ class Crystal():
Cubic crystal family: Cubic crystal family:
>>> import damask >>> import damask
>>> cubic = damask.Crystal(family='cubic') >>> (cubic := damask.Crystal(family='cubic'))
>>> cubic
Crystal family: cubic Crystal family: cubic
Body-centered cubic Bravais lattice with parameters of iron: Body-centered cubic Bravais lattice with parameters of iron:
>>> import damask >>> import damask
>>> Fe = damask.Crystal(lattice='cI', a=287e-12) >>> (Fe := damask.Crystal(lattice='cI', a=287e-12))
>>> Fe
Crystal family: cubic Crystal family: cubic
Bravais lattice: cI Bravais lattice: cI
a=2.87e-10 m, b=2.87e-10 m, c=2.87e-10 m a=2.87e-10 m, b=2.87e-10 m, c=2.87e-10 m
@ -406,7 +404,7 @@ class Crystal():
""" """
Return repr(self). Return repr(self).
Give short human-readable summary. Give short, human-readable summary.
""" """
family = f'Crystal family: {self.family}' family = f'Crystal family: {self.family}'

View File

@ -32,10 +32,10 @@ class Grid:
""" """
Geometry definition for grid solvers. Geometry definition for grid solvers.
Create and manipulate geometry definitions for storage as VTK Create and manipulate geometry definitions for storage as VTK ImageData
image data files ('.vti' extension). A grid contains the files ('.vti' extension). A grid has a physical size, a coordinate origin,
material ID (referring to the entry in 'material.yaml') and and contains the material ID (indexing an entry in 'material.yaml')
the physical size. as well as initial condition fields.
""" """
def __init__(self, def __init__(self,
@ -57,7 +57,7 @@ class Grid:
origin : sequence of float, len (3), optional origin : sequence of float, len (3), optional
Coordinates of grid origin in meter. Defaults to [0.0,0.0,0.0]. Coordinates of grid origin in meter. Defaults to [0.0,0.0,0.0].
initial_conditions : dictionary, optional initial_conditions : dictionary, optional
Labels and values of the inital conditions at each material point. Initial condition label and field values at each grid point.
comments : (sequence of) str, optional comments : (sequence of) str, optional
Additional, human-readable information, e.g. history of operations. Additional, human-readable information, e.g. history of operations.
@ -74,7 +74,7 @@ class Grid:
""" """
Return repr(self). Return repr(self).
Give short human-readable summary. Give short, human-readable summary.
""" """
mat_min = np.nanmin(self.material) mat_min = np.nanmin(self.material)
@ -144,7 +144,7 @@ class Grid:
@property @property
def size(self) -> np.ndarray: def size(self) -> np.ndarray:
"""Physical size of grid in meter.""" """Edge lengths of grid in meter."""
return self._size return self._size
@size.setter @size.setter
@ -157,7 +157,7 @@ class Grid:
@property @property
def origin(self) -> np.ndarray: def origin(self) -> np.ndarray:
"""Coordinates of grid origin in meter.""" """Vector to grid origin in meter."""
return self._origin return self._origin
@origin.setter @origin.setter
@ -186,7 +186,7 @@ class Grid:
@property @property
def cells(self) -> np.ndarray: def cells(self) -> np.ndarray:
"""Number of cells in x,y,z direction.""" """Cell counts along x,y,z direction."""
return np.asarray(self.material.shape) return np.asarray(self.material.shape)
@ -199,7 +199,7 @@ class Grid:
@staticmethod @staticmethod
def load(fname: Union[str, Path]) -> 'Grid': def load(fname: Union[str, Path]) -> 'Grid':
""" """
Load from VTK image data file. Load from VTK ImageData file.
Parameters Parameters
---------- ----------
@ -470,9 +470,9 @@ class Grid:
Parameters Parameters
---------- ----------
cells : sequence of int, len (3) cells : sequence of int, len (3)
Number of cells in x,y,z direction. Cell counts along x,y,z direction.
size : sequence of float, len (3) size : sequence of float, len (3)
Physical size of the grid in meter. Edge lengths of the grid in meter.
seeds : numpy.ndarray of float, shape (:,3) seeds : numpy.ndarray of float, shape (:,3)
Position of the seed points in meter. All points need to lay within the box. Position of the seed points in meter. All points need to lay within the box.
weights : sequence of float, len (seeds.shape[0]) weights : sequence of float, len (seeds.shape[0])
@ -527,9 +527,9 @@ class Grid:
Parameters Parameters
---------- ----------
cells : sequence of int, len (3) cells : sequence of int, len (3)
Number of cells in x,y,z direction. Cell counts along x,y,z direction.
size : sequence of float, len (3) size : sequence of float, len (3)
Physical size of the grid in meter. Edge lengths of the grid in meter.
seeds : numpy.ndarray of float, shape (:,3) seeds : numpy.ndarray of float, shape (:,3)
Position of the seed points in meter. All points need to lay within the box. Position of the seed points in meter. All points need to lay within the box.
material : sequence of int, len (seeds.shape[0]), optional material : sequence of int, len (seeds.shape[0]), optional
@ -608,14 +608,14 @@ class Grid:
periods: int = 1, periods: int = 1,
materials: IntSequence = (0,1)) -> 'Grid': materials: IntSequence = (0,1)) -> 'Grid':
""" """
Create grid from definition of triply periodic minimal surface. Create grid from definition of triply-periodic minimal surface.
Parameters Parameters
---------- ----------
cells : sequence of int, len (3) cells : sequence of int, len (3)
Number of cells in x,y,z direction. Cell counts along x,y,z direction.
size : sequence of float, len (3) size : sequence of float, len (3)
Physical size of the grid in meter. Edge lengths of the grid in meter.
surface : str surface : str
Type of the minimal surface. See notes for details. Type of the minimal surface. See notes for details.
threshold : float, optional. threshold : float, optional.
@ -664,19 +664,19 @@ class Grid:
>>> import numpy as np >>> import numpy as np
>>> import damask >>> import damask
>>> damask.Grid.from_minimal_surface([64]*3,np.ones(3)*1.e-4,'Gyroid') >>> damask.Grid.from_minimal_surface([64]*3,np.ones(3)*1.e-4,'Gyroid')
cells : 64 x 64 x 64 cells : 64 × 64 × 64
size : 0.0001 x 0.0001 x 0.0001 size : 0.0001 × 0.0001 × 0.0001
origin: 0.0 0.0 0.0 m origin: 0.0 0.0 0.0 m
# materials: 2 # materials: 2
Minimal surface of 'Neovius' type with non-default material IDs. Minimal surface of 'Neovius' type with specific material IDs.
>>> import numpy as np >>> import numpy as np
>>> import damask >>> import damask
>>> damask.Grid.from_minimal_surface([80]*3,np.ones(3)*5.e-4, >>> damask.Grid.from_minimal_surface([80]*3,np.ones(3)*5.e-4,
... 'Neovius',materials=(1,5)) ... 'Neovius',materials=(1,5))
cells : 80 x 80 x 80 cells : 80 × 80 × 80
size : 0.0005 x 0.0005 x 0.0005 size : 0.0005 × 0.0005 × 0.0005
origin: 0.0 0.0 0.0 m origin: 0.0 0.0 0.0 m
# materials: 2 (min: 1, max: 5) # materials: 2 (min: 1, max: 5)
@ -695,12 +695,13 @@ class Grid:
fname: Union[str, Path], fname: Union[str, Path],
compress: bool = True): compress: bool = True):
""" """
Save as VTK image data file. Save as VTK ImageData file.
Parameters Parameters
---------- ----------
fname : str or pathlib.Path fname : str or pathlib.Path
Filename to write. Valid extension is .vti, it will be appended if not given. Filename to write.
Valid extension is .vti, which will be appended if not given.
compress : bool, optional compress : bool, optional
Compress with zlib algorithm. Defaults to True. Compress with zlib algorithm. Defaults to True.
@ -727,7 +728,7 @@ class Grid:
fname : str or file handle fname : str or file handle
Geometry file to write with extension '.geom'. Geometry file to write with extension '.geom'.
compress : bool, optional compress : bool, optional
Compress geometry with 'x of y' and 'a to b'. Compress geometry using 'x of y' and 'a to b'.
""" """
warnings.warn('Support for ASCII-based geom format will be removed in DAMASK 3.0.0', DeprecationWarning,2) warnings.warn('Support for ASCII-based geom format will be removed in DAMASK 3.0.0', DeprecationWarning,2)
@ -771,13 +772,13 @@ class Grid:
Parameters Parameters
---------- ----------
cells : sequence of int, len (3), optional cells : sequence of int, len (3), optional
Number of cells x,y,z direction. Cell counts along x,y,z direction.
offset : sequence of int, len (3), optional offset : sequence of int, len (3), optional
Offset (measured in cells) from old to new grid. Offset (measured in cells) from old to new grid.
Defaults to [0,0,0]. Defaults to [0,0,0].
fill : int, optional fill : int, optional
Material ID to fill the background. Material ID to fill the background.
Defaults to material.max() + 1. Defaults to material.max()+1.
Returns Returns
------- -------
@ -790,11 +791,11 @@ class Grid:
>>> import numpy as np >>> import numpy as np
>>> import damask >>> import damask
>>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4) >>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-3)
>>> g.canvas([32,32,16],[0,0,16]) >>> g.canvas([32,32,16],[0,0,16])
cells : 33 x 32 x 16 cells: 32 × 32 × 16
size : 0.0001 x 0.0001 x 5e-05 size: 0.001 × 0.001 × 0.0005
origin: 0.0 0.0 5e-05 m origin: 0.0 0.0 0.0005 m
# materials: 1 # materials: 1
""" """
@ -837,16 +838,33 @@ class Grid:
Examples Examples
-------- --------
Mirror along x- and y-direction. Mirror along y-direction.
>>> import numpy as np >>> import numpy as np
>>> import damask >>> import damask
>>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4) >>> (g := damask.Grid(np.arange(4*5*6).reshape([4,5,6]),np.ones(3)))
>>> g.mirror('xy',True) cells: 4 × 5 × 6
cells : 64 x 64 x 32 size: 1.0 × 1.0 × 1.0
size : 0.0002 x 0.0002 x 0.0001
origin: 0.0 0.0 0.0 m origin: 0.0 0.0 0.0 m
# materials: 1 # materials: 120
>>> g.mirror('y')
cells: 4 × 8 × 6
size: 1.0 × 1.6 × 1.0
origin: 0.0 0.0 0.0 m
# materials: 120
Reflect along x- and y-direction.
>>> g.mirror('xy',reflect=True)
cells: 8 × 10 × 6
size: 2.0 × 2.0 × 1.0
origin: 0.0 0.0 0.0 m
# materials: 120
Independence of mirroring order.
>>> g.mirror('xy') == g.mirror(['y','x'])
True
""" """
if not set(directions).issubset(valid := ['x', 'y', 'z']): if not set(directions).issubset(valid := ['x', 'y', 'z']):
@ -884,11 +902,29 @@ class Grid:
updated : damask.Grid updated : damask.Grid
Updated grid-based geometry. Updated grid-based geometry.
Examples
--------
Invariance of flipping order.
>>> import numpy as np
>>> import damask
>>> (g := damask.Grid(np.arange(4*5*6).reshape([4,5,6]),np.ones(3)))
cells: 4 × 5 × 6
size: 1.0 × 1.0 × 1.0
origin: 0.0 0.0 0.0 m
# materials: 120
>>> g.flip('xyz') == g.flip(['x','z','y'])
True
Invariance of flipping a (fully) mirrored grid.
>>> g.mirror('x',True) == g.mirror('x',True).flip('x')
True
""" """
if not set(directions).issubset(valid := ['x', 'y', 'z']): if not set(directions).issubset(valid := ['x', 'y', 'z']):
raise ValueError(f'invalid direction "{set(directions).difference(valid)}" specified') raise ValueError(f'invalid direction "{set(directions).difference(valid)}" specified')
mat = np.flip(self.material, [valid.index(d) for d in directions if d in valid]) mat = np.flip(self.material, [valid.index(d) for d in directions if d in valid])
return Grid(material = mat, return Grid(material = mat,
@ -902,7 +938,7 @@ class Grid:
R: Rotation, R: Rotation,
fill: Optional[int] = None) -> 'Grid': fill: Optional[int] = None) -> 'Grid':
""" """
Rotate grid (and pad if required). Rotate grid (possibly extending its bounding box).
Parameters Parameters
---------- ----------
@ -910,13 +946,27 @@ class Grid:
Rotation to apply to the grid. Rotation to apply to the grid.
fill : int, optional fill : int, optional
Material ID to fill enlarged bounding box. Material ID to fill enlarged bounding box.
Defaults to material.max() + 1. Defaults to material.max()+1.
Returns Returns
------- -------
updated : damask.Grid updated : damask.Grid
Updated grid-based geometry. Updated grid-based geometry.
Examples
--------
Rotation by 180° (π) is equivalent to twice flipping.
>>> import numpy as np
>>> import damask
>>> (g := damask.Grid(np.arange(4*5*6).reshape([4,5,6]),np.ones(3)))
cells: 4 × 5 × 6
size: 1.0 × 1.0 × 1.0
origin: 0.0 0.0 0.0 m
# materials: 120
>>> g.rotate(damask.Rotation.from_axis_angle([0,0,1,180],degrees=True)) == g.flip('xy')
True
""" """
material = self.material material = self.material
# These rotations are always applied in the reference coordinate system, i.e. (z,x,z) not (z,x',z'') # These rotations are always applied in the reference coordinate system, i.e. (z,x,z) not (z,x',z'')
@ -941,12 +991,12 @@ class Grid:
def scale(self, def scale(self,
cells: IntSequence) -> 'Grid': cells: IntSequence) -> 'Grid':
""" """
Scale grid to new cell count. Scale grid to new cell counts.
Parameters Parameters
---------- ----------
cells : sequence of int, len (3) cells : sequence of int, len (3)
Number of cells in x,y,z direction. Cell counts along x,y,z direction.
Returns Returns
------- -------
@ -955,7 +1005,7 @@ class Grid:
Examples Examples
-------- --------
Double resolution. Double grid resolution.
>>> import numpy as np >>> import numpy as np
>>> import damask >>> import damask
@ -965,8 +1015,8 @@ class Grid:
origin: 0.0 0.0 0.0 m origin: 0.0 0.0 0.0 m
# materials: 1 # materials: 1
>>> g.scale(g.cells*2) >>> g.scale(g.cells*2)
cells : 64 x 64 x 64 cells : 64 × 64 × 64
size : 0.0001 x 0.0001 x 0.0001 size : 0.0001 × 0.0001 × 0.0001
origin: 0.0 0.0 0.0 m origin: 0.0 0.0 0.0 m
# materials: 1 # materials: 1
@ -1069,7 +1119,7 @@ class Grid:
def sort(self) -> 'Grid': def sort(self) -> 'Grid':
""" """
Sort material indices such that min(material) is located at (0,0,0). Sort material indices such that min(material ID) is located at (0,0,0).
Returns Returns
------- -------
@ -1186,7 +1236,7 @@ class Grid:
fill : int, optional fill : int, optional
Fill value for primitive. Defaults to material.max()+1. Fill value for primitive. Defaults to material.max()+1.
R : damask.Rotation, optional R : damask.Rotation, optional
Rotation of primitive. Defaults to no rotation. Rotation of the primitive. Defaults to no rotation.
inverse : bool, optional inverse : bool, optional
Retain original materials within primitive and fill outside. Retain original materials within primitive and fill outside.
Defaults to False. Defaults to False.
@ -1206,8 +1256,8 @@ class Grid:
>>> import damask >>> import damask
>>> g = damask.Grid(np.zeros([64]*3,int), np.ones(3)*1e-4) >>> g = damask.Grid(np.zeros([64]*3,int), np.ones(3)*1e-4)
>>> g.add_primitive(np.ones(3)*5e-5,np.ones(3)*5e-5,1) >>> g.add_primitive(np.ones(3)*5e-5,np.ones(3)*5e-5,1)
cells : 64 x 64 x 64 cells : 64 × 64 × 64
size : 0.0001 x 0.0001 x 0.0001 size : 0.0001 × 0.0001 × 0.0001
origin: 0.0 0.0 0.0 m origin: 0.0 0.0 0.0 m
# materials: 2 # materials: 2
@ -1217,8 +1267,8 @@ class Grid:
>>> import damask >>> import damask
>>> g = damask.Grid(np.zeros([64]*3,int), np.ones(3)*1e-4) >>> g = damask.Grid(np.zeros([64]*3,int), np.ones(3)*1e-4)
>>> g.add_primitive(np.ones(3,int)*32,np.zeros(3),np.inf) >>> g.add_primitive(np.ones(3,int)*32,np.zeros(3),np.inf)
cells : 64 x 64 x 64 cells : 64 × 64 × 64
size : 0.0001 x 0.0001 x 0.0001 size : 0.0001 × 0.0001 × 0.0001
origin: 0.0 0.0 0.0 m origin: 0.0 0.0 0.0 m
# materials: 2 # materials: 2

View File

@ -108,7 +108,7 @@ class Orientation(Rotation,Crystal):
Parameters Parameters
---------- ----------
rotation : list, numpy.ndarray, Rotation, optional rotation : list, numpy.ndarray, or Rotation, optional
Unit quaternion in positive real hemisphere. Unit quaternion in positive real hemisphere.
Use .from_quaternion to perform a sanity check. Use .from_quaternion to perform a sanity check.
Defaults to no rotation. Defaults to no rotation.
@ -123,7 +123,7 @@ class Orientation(Rotation,Crystal):
""" """
Return repr(self). Return repr(self).
Give short human-readable summary. Give short, human-readable summary.
""" """
return util.srepr([Crystal.__repr__(self), return util.srepr([Crystal.__repr__(self),
@ -467,24 +467,24 @@ class Orientation(Rotation,Crystal):
if self.family == 'cubic': if self.family == 'cubic':
return (np.prod(np.sqrt(2)-1. >= rho_abs,axis=-1) * return (np.prod(np.sqrt(2)-1. >= rho_abs,axis=-1) *
(1. >= np.sum(rho_abs,axis=-1))).astype(bool) (1. >= np.sum(rho_abs,axis=-1))).astype(bool)
elif self.family == 'hexagonal': if self.family == 'hexagonal':
return (np.prod(1. >= rho_abs,axis=-1) * return (np.prod(1. >= rho_abs,axis=-1) *
(2. >= np.sqrt(3)*rho_abs[...,0] + rho_abs[...,1]) * (2. >= np.sqrt(3)*rho_abs[...,0] + rho_abs[...,1]) *
(2. >= np.sqrt(3)*rho_abs[...,1] + rho_abs[...,0]) * (2. >= np.sqrt(3)*rho_abs[...,1] + rho_abs[...,0]) *
(2. >= np.sqrt(3) + rho_abs[...,2])).astype(bool) (2. >= np.sqrt(3) + rho_abs[...,2])).astype(bool)
elif self.family == 'tetragonal': if self.family == 'tetragonal':
return (np.prod(1. >= rho_abs[...,:2],axis=-1) * return (np.prod(1. >= rho_abs[...,:2],axis=-1) *
(np.sqrt(2) >= rho_abs[...,0] + rho_abs[...,1]) * (np.sqrt(2) >= rho_abs[...,0] + rho_abs[...,1]) *
(np.sqrt(2) >= rho_abs[...,2] + 1.)).astype(bool) (np.sqrt(2) >= rho_abs[...,2] + 1.)).astype(bool)
elif self.family == 'orthorhombic': if self.family == 'orthorhombic':
return (np.prod(1. >= rho_abs,axis=-1)).astype(bool) return (np.prod(1. >= rho_abs,axis=-1)).astype(bool)
elif self.family == 'monoclinic': if self.family == 'monoclinic':
return np.logical_or( 1. >= rho_abs[...,1], return np.logical_or( 1. >= rho_abs[...,1],
np.isnan(rho_abs[...,1])) np.isnan(rho_abs[...,1]))
elif self.family == 'triclinic': if self.family == 'triclinic':
return np.ones(rho_abs.shape[:-1]).astype(bool) return np.ones(rho_abs.shape[:-1]).astype(bool)
else:
raise TypeError(f'unknown symmetry "{self.family}"') raise TypeError(f'unknown symmetry "{self.family}"')
@property @property
@ -510,38 +510,40 @@ class Orientation(Rotation,Crystal):
return ((rho[...,0] >= rho[...,1]) & return ((rho[...,0] >= rho[...,1]) &
(rho[...,1] >= rho[...,2]) & (rho[...,1] >= rho[...,2]) &
(rho[...,2] >= 0)).astype(bool) (rho[...,2] >= 0)).astype(bool)
elif self.family == 'hexagonal': if self.family == 'hexagonal':
return ((rho[...,0] >= rho[...,1]*np.sqrt(3)) & return ((rho[...,0] >= rho[...,1]*np.sqrt(3)) &
(rho[...,1] >= 0) & (rho[...,1] >= 0) &
(rho[...,2] >= 0)).astype(bool) (rho[...,2] >= 0)).astype(bool)
elif self.family == 'tetragonal': if self.family == 'tetragonal':
return ((rho[...,0] >= rho[...,1]) & return ((rho[...,0] >= rho[...,1]) &
(rho[...,1] >= 0) & (rho[...,1] >= 0) &
(rho[...,2] >= 0)).astype(bool) (rho[...,2] >= 0)).astype(bool)
elif self.family == 'orthorhombic': if self.family == 'orthorhombic':
return ((rho[...,0] >= 0) & return ((rho[...,0] >= 0) &
(rho[...,1] >= 0) & (rho[...,1] >= 0) &
(rho[...,2] >= 0)).astype(bool) (rho[...,2] >= 0)).astype(bool)
elif self.family == 'monoclinic': if self.family == 'monoclinic':
return ((rho[...,1] >= 0) & return ((rho[...,1] >= 0) &
(rho[...,2] >= 0)).astype(bool) (rho[...,2] >= 0)).astype(bool)
else:
return np.ones_like(rho[...,0],dtype=bool) return np.ones_like(rho[...,0],dtype=bool)
def disorientation(self, def disorientation(self,
other: 'Orientation', other: 'Orientation',
return_operators: bool = False) -> object: return_operators: bool = False) -> object:
""" """
Calculate disorientation between myself and given other orientation. Calculate disorientation between self and given other orientation.
Parameters Parameters
---------- ----------
other : Orientation other : Orientation
Orientation to calculate disorientation for. Orientation to calculate disorientation for.
Shape of other blends with shape of own rotation array. Shape of other blends with shape of own rotation array.
For example, shapes of (2,3) for own rotations and (3,2) for other's result in (2,3,2) disorientations. For example, shapes of (2,3) for own rotations
and (3,2) for other's result in (2,3,2) disorientations.
return_operators : bool, optional return_operators : bool, optional
Return index pair of symmetrically equivalent orientations that result in disorientation axis falling into FZ. Return index pair of symmetrically equivalent orientations
that result in disorientation axis falling into FZ.
Defaults to False. Defaults to False.
Returns Returns
@ -578,8 +580,8 @@ class Orientation(Rotation,Crystal):
>>> N = 10000 >>> N = 10000
>>> a = damask.Orientation.from_random(shape=N,family='cubic') >>> a = damask.Orientation.from_random(shape=N,family='cubic')
>>> b = damask.Orientation.from_random(shape=N,family='cubic') >>> b = damask.Orientation.from_random(shape=N,family='cubic')
>>> d = a.disorientation(b).as_axis_angle(degrees=True,pair=True)[1] >>> n,omega = a.disorientation(b).as_axis_angle(degrees=True,pair=True)
>>> plt.hist(d,25) >>> plt.hist(omega,25)
>>> plt.show() >>> plt.show()
""" """
@ -626,6 +628,7 @@ class Orientation(Rotation,Crystal):
---------- ----------
weights : numpy.ndarray, shape (self.shape), optional weights : numpy.ndarray, shape (self.shape), optional
Relative weights of orientations. Relative weights of orientations.
Defaults to equal weights.
return_cloud : bool, optional return_cloud : bool, optional
Return the specific (symmetrically equivalent) orientations that were averaged. Return the specific (symmetrically equivalent) orientations that were averaged.
Defaults to False. Defaults to False.
@ -895,8 +898,8 @@ class Orientation(Rotation,Crystal):
Schmid matrix (in lab frame) of first octahedral slip system of a face-centered Schmid matrix (in lab frame) of first octahedral slip system of a face-centered
cubic crystal in "Goss" orientation. cubic crystal in "Goss" orientation.
>>> import damask
>>> import numpy as np >>> import numpy as np
>>> import damask
>>> np.set_printoptions(3,suppress=True,floatmode='fixed') >>> np.set_printoptions(3,suppress=True,floatmode='fixed')
>>> O = damask.Orientation.from_Euler_angles(phi=[0,45,0],degrees=True,lattice='cF') >>> O = damask.Orientation.from_Euler_angles(phi=[0,45,0],degrees=True,lattice='cF')
>>> O.Schmid(N_slip=[1]) >>> O.Schmid(N_slip=[1])
@ -936,8 +939,9 @@ class Orientation(Rotation,Crystal):
Returns Returns
------- -------
Orientations related to self following the selected rel : Orientation, shape (:,self.shape)
model for the orientation relationship. Orientations related to self according to the selected
model for the orientation relationship.
Examples Examples
-------- --------

View File

@ -65,9 +65,9 @@ def _empty_like(dataset: np.ma.core.MaskedArray,
class Result: class Result:
""" """
Add data to and export data from a DADF5 file. Add data to and export data from a DADF5 (DAMASK HDF5) file.
A DADF5 (DAMASK HDF5) file contains DAMASK results. A DADF5 file contains DAMASK results.
Its group/folder structure reflects the layout in material.yaml. Its group/folder structure reflects the layout in material.yaml.
This class provides a customizable view on the DADF5 file. This class provides a customizable view on the DADF5 file.
@ -93,7 +93,7 @@ class Result:
def __init__(self, fname: Union[str, Path]): def __init__(self, fname: Union[str, Path]):
""" """
New result view bound to a HDF5 file. New result view bound to a DADF5 file.
Parameters Parameters
---------- ----------
@ -167,7 +167,7 @@ class Result:
""" """
Return repr(self). Return repr(self).
Give short human-readable summary. Give short, human-readable summary.
""" """
with h5py.File(self.fname,'r') as f: with h5py.File(self.fname,'r') as f:
@ -195,7 +195,7 @@ class Result:
homogenizations: Union[None, str, Sequence[str], bool] = None, homogenizations: Union[None, str, Sequence[str], bool] = None,
fields: Union[None, str, Sequence[str], bool] = None) -> "Result": fields: Union[None, str, Sequence[str], bool] = None) -> "Result":
""" """
Manages the visibility of the groups. Manage the visibility of the groups.
Parameters Parameters
---------- ----------
@ -319,15 +319,15 @@ class Result:
Parameters Parameters
---------- ----------
increments: (list of) int, (list of) str, or bool, optional. increments: (list of) int, (list of) str, or bool, optional.
Number(s) of increments to select. Numbers of increments to select.
times: (list of) float, (list of) str, or bool, optional. times: (list of) float, (list of) str, or bool, optional.
Simulation time(s) of increments to select. Simulation times of increments to select.
phases: (list of) str, or bool, optional. phases: (list of) str, or bool, optional.
Name(s) of phases to select. Names of phases to select.
homogenizations: (list of) str, or bool, optional. homogenizations: (list of) str, or bool, optional.
Name(s) of homogenizations to select. Names of homogenizations to select.
fields: (list of) str, or bool, optional. fields: (list of) str, or bool, optional.
Name(s) of fields to select. Names of fields to select.
protected: bool, optional. protected: bool, optional.
Protection status of existing data. Protection status of existing data.
@ -375,15 +375,15 @@ class Result:
Parameters Parameters
---------- ----------
increments: (list of) int, (list of) str, or bool, optional. increments: (list of) int, (list of) str, or bool, optional.
Number(s) of increments to select. Numbers of increments to select.
times: (list of) float, (list of) str, or bool, optional. times: (list of) float, (list of) str, or bool, optional.
Simulation time(s) of increments to select. Simulation times of increments to select.
phases: (list of) str, or bool, optional. phases: (list of) str, or bool, optional.
Name(s) of phases to select. Names of phases to select.
homogenizations: (list of) str, or bool, optional. homogenizations: (list of) str, or bool, optional.
Name(s) of homogenizations to select. Names of homogenizations to select.
fields: (list of) str, or bool, optional. fields: (list of) str, or bool, optional.
Name(s) of fields to select. Names of fields to select.
Returns Returns
------- -------
@ -418,15 +418,15 @@ class Result:
Parameters Parameters
---------- ----------
increments: (list of) int, (list of) str, or bool, optional. increments: (list of) int, (list of) str, or bool, optional.
Number(s) of increments to select. Numbers of increments to select.
times: (list of) float, (list of) str, or bool, optional. times: (list of) float, (list of) str, or bool, optional.
Simulation time(s) of increments to select. Simulation times of increments to select.
phases: (list of) str, or bool, optional. phases: (list of) str, or bool, optional.
Name(s) of phases to select. Names of phases to select.
homogenizations: (list of) str, or bool, optional. homogenizations: (list of) str, or bool, optional.
Name(s) of homogenizations to select. Names of homogenizations to select.
fields: (list of) str, or bool, optional. fields: (list of) str, or bool, optional.
Name(s) of fields to select. Names of fields to select.
Returns Returns
------- -------
@ -721,9 +721,11 @@ class Result:
Parameters Parameters
---------- ----------
P : str, optional P : str, optional
Name 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 F : str, optional
Name 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}) self._add_generic_pointwise(self._add_stress_Cauchy,{'P':P,'F':F})
@ -1023,14 +1025,14 @@ class Result:
x: str, x: str,
ord: Union[None, int, float, Literal['fro', 'nuc']] = None): ord: Union[None, int, float, Literal['fro', 'nuc']] = None):
""" """
Add the norm of vector or tensor. Add the norm of a vector or tensor.
Parameters Parameters
---------- ----------
x : str x : str
Name of vector or tensor dataset. Name of vector or tensor dataset.
ord : {non-zero int, inf, -inf, 'fro', 'nuc'}, optional ord : {non-zero int, inf, -inf, 'fro', 'nuc'}, optional
Order of the norm. inf means NumPys inf object. For details refer to numpy.linalg.norm. Order of the norm. inf means NumPy's inf object. For details refer to numpy.linalg.norm.
""" """
self._add_generic_pointwise(self._add_norm,{'x':x},{'ord':ord}) self._add_generic_pointwise(self._add_norm,{'x':x},{'ord':ord})
@ -1052,7 +1054,7 @@ class Result:
def add_stress_second_Piola_Kirchhoff(self, def add_stress_second_Piola_Kirchhoff(self,
P: str = 'P', P: str = 'P',
F: str = 'F'): F: str = 'F'):
""" r"""
Add second Piola-Kirchhoff stress calculated from first Piola-Kirchhoff stress and deformation gradient. Add second Piola-Kirchhoff stress calculated from first Piola-Kirchhoff stress and deformation gradient.
Parameters Parameters
@ -1064,9 +1066,10 @@ class Result:
Notes Notes
----- -----
The definition of the second Piola-Kirchhoff stress (S = [F^-1 P]_sym) The definition of the second Piola-Kirchhoff stress
:math:`\vb{S} = \left(\vb{F}^{-1} \vb{P}\right)_\text{sym}`
follows the standard definition in nonlinear continuum mechanics. follows the standard definition in nonlinear continuum mechanics.
As such, no intermediate configuration, for instance that reached by F_p, As such, no intermediate configuration, for instance that reached by :math:`\vb{F}_\text{p}`,
is taken into account. is taken into account.
""" """
@ -1240,10 +1243,11 @@ class Result:
Notes Notes
----- -----
The incoporation of rotational parts into the elastic and plastic The presence of rotational parts in the elastic and plastic deformation gradient
deformation gradient requires it to use material/Lagragian strain measures calls for the use of
(based on 'U') for plastic strains and spatial/Eulerian strain measures material/Lagragian strain measures (based on 'U') for plastic strains and
(based on 'V') for elastic strains when calculating averages. spatial/Eulerian strain measures (based on 'V') for elastic strains
when calculating averages.
""" """
self._add_generic_pointwise(self._add_strain,{'F':F},{'t':t,'m':m}) self._add_generic_pointwise(self._add_strain,{'F':F},{'t':t,'m':m})
@ -1302,7 +1306,7 @@ class Result:
Notes Notes
----- -----
This function is only available for structured grids, This function is only available for structured grids,
i.e. results from the grid solver. i.e. fields resulting from the grid solver.
""" """
self._add_generic_grid(self._add_curl,{'f':f},{'size':self.size}) self._add_generic_grid(self._add_curl,{'f':f},{'size':self.size})
@ -1331,7 +1335,7 @@ class Result:
Notes Notes
----- -----
This function is only available for structured grids, This function is only available for structured grids,
i.e. results from the grid solver. i.e. fields resulting from the grid solver.
""" """
self._add_generic_grid(self._add_divergence,{'f':f},{'size':self.size}) self._add_generic_grid(self._add_divergence,{'f':f},{'size':self.size})
@ -1361,7 +1365,7 @@ class Result:
Notes Notes
----- -----
This function is only available for structured grids, This function is only available for structured grids,
i.e. results from the grid solver. i.e. fields resulting from the grid solver.
""" """
self._add_generic_grid(self._add_gradient,{'f':f},{'size':self.size}) self._add_generic_grid(self._add_gradient,{'f':f},{'size':self.size})
@ -1379,10 +1383,10 @@ class Result:
---------- ----------
func : function func : function
Callback function that calculates a new dataset from one or Callback function that calculates a new dataset from one or
more datasets per HDF5 group. more datasets per DADF5 group.
datasets : dictionary datasets : dictionary
Details of the datasets to be used: Details of the datasets to be used:
{arg (name to which the data is passed in func): label (in HDF5 file)}. {arg (name to which the data is passed in func): label (in DADF5 file)}.
args : dictionary, optional args : dictionary, optional
Arguments parsed to func. Arguments parsed to func.
@ -1462,10 +1466,10 @@ class Result:
---------- ----------
callback : function callback : function
Callback function that calculates a new dataset from one or Callback function that calculates a new dataset from one or
more datasets per HDF5 group. more datasets per DADF5 group.
datasets : dictionary datasets : dictionary
Details of the datasets to be used: Details of the datasets to be used:
{arg (name to which the data is passed in func): label (in HDF5 file)}. {arg (name to which the data is passed in func): label (in DADF5 file)}.
args : dictionary, optional args : dictionary, optional
Arguments parsed to func. Arguments parsed to func.
@ -1500,7 +1504,7 @@ class Result:
dataset.attrs['overwritten'] = True dataset.attrs['overwritten'] = True
else: else:
shape = result['data'].shape shape = result['data'].shape
if compress := (result['data'].size >= chunk_size*2): if compress := result['data'].size >= chunk_size*2:
chunks = (chunk_size//np.prod(shape[1:]),)+shape[1:] chunks = (chunk_size//np.prod(shape[1:]),)+shape[1:]
else: else:
chunks = shape chunks = shape
@ -1828,9 +1832,10 @@ class Result:
Export to VTK cell/point data. Export to VTK cell/point data.
One VTK file per visible increment is created. One VTK file per visible increment is created.
For point data, the VTK format is poly data (.vtp). For point data, the VTK format is PolyData (.vtp).
For cell data, either an image (.vti) or unstructured (.vtu) dataset For cell data, the file format is either ImageData (.vti)
is written for grid-based or mesh-based simulations, respectively. or UnstructuredGrid (.vtu) for grid-based or mesh-based simulations,
respectively.
Parameters Parameters
---------- ----------
@ -1961,7 +1966,7 @@ class Result:
for field in _match(self.visible['fields'],f_in['/'.join([inc,ty,label])].keys()): for field in _match(self.visible['fields'],f_in['/'.join([inc,ty,label])].keys()):
p = '/'.join([inc,ty,label,field]) p = '/'.join([inc,ty,label,field])
for out in _match(output,f_in[p].keys()): for out in _match(output,f_in[p].keys()):
f_in[p].copy(out,f_out[p]) f_in[p].copy(out,f_out[p])
def export_simulation_setup(self, def export_simulation_setup(self,

View File

@ -35,8 +35,8 @@ class Rotation:
Rotate vector 'a' (defined in coordinate system 'A') to Rotate vector 'a' (defined in coordinate system 'A') to
coordinates 'b' expressed in system 'B': coordinates 'b' expressed in system 'B':
>>> import damask
>>> import numpy as np >>> import numpy as np
>>> import damask
>>> Q = damask.Rotation.from_random() >>> Q = damask.Rotation.from_random()
>>> a = np.random.rand(3) >>> a = np.random.rand(3)
>>> b = Q @ a >>> b = Q @ a
@ -45,8 +45,8 @@ class Rotation:
Compound rotations R1 (first) and R2 (second): Compound rotations R1 (first) and R2 (second):
>>> import damask
>>> import numpy as np >>> import numpy as np
>>> import damask
>>> R1 = damask.Rotation.from_random() >>> R1 = damask.Rotation.from_random()
>>> R2 = damask.Rotation.from_random() >>> R2 = damask.Rotation.from_random()
>>> R = R2 * R1 >>> R = R2 * R1
@ -69,7 +69,7 @@ class Rotation:
Parameters Parameters
---------- ----------
rotation : list, numpy.ndarray, Rotation, optional rotation : list, numpy.ndarray, or Rotation, optional
Unit quaternion in positive real hemisphere. Unit quaternion in positive real hemisphere.
Use .from_quaternion to perform a sanity check. Use .from_quaternion to perform a sanity check.
Defaults to no rotation. Defaults to no rotation.
@ -88,7 +88,7 @@ class Rotation:
""" """
Return repr(self). Return repr(self).
Give short human-readable summary. Give short, human-readable summary.
""" """
return f'Quaternion{" " if self.quaternion.shape == (4,) else "s of shape "+str(self.quaternion.shape[:-1])+chr(10)}'\ return f'Quaternion{" " if self.quaternion.shape == (4,) else "s of shape "+str(self.quaternion.shape[:-1])+chr(10)}'\

View File

@ -41,7 +41,7 @@ class Table:
""" """
Return repr(self). Return repr(self).
Give short human-readable summary. Give short, human-readable summary.
""" """
self._relabel('shapes') self._relabel('shapes')
@ -255,8 +255,8 @@ class Table:
""" """
Load from ASCII table file. Load from ASCII table file.
Initial comments are marked by '#', the first non-comment line Initial comments are marked by '#'.
containing the column labels. The first non-comment line contains the column labels.
- Vector data column labels are indicated by '1_v, 2_v, ..., n_v'. - Vector data column labels are indicated by '1_v, 2_v, ..., n_v'.
- Tensor data column labels are indicated by '3x3:1_T, 3x3:2_T, ..., 3x3:9_T'. - Tensor data column labels are indicated by '3x3:1_T, 3x3:2_T, ..., 3x3:9_T'.
@ -264,7 +264,7 @@ class Table:
Parameters Parameters
---------- ----------
fname : file, str, or pathlib.Path fname : file, str, or pathlib.Path
Filename or file for reading. Filename or file to read.
Returns Returns
------- -------
@ -458,9 +458,9 @@ class Table:
Parameters Parameters
---------- ----------
label_old : (iterable of) str label_old : (iterable of) str
Old column label(s). Old column labels.
label_new : (iterable of) str label_new : (iterable of) str
New column label(s). New column labels.
Returns Returns
------- -------
@ -488,7 +488,7 @@ class Table:
label : str or list label : str or list
Column labels for sorting. Column labels for sorting.
ascending : bool or list, optional ascending : bool or list, optional
Set sort order. Set sort order. Defaults to True.
Returns Returns
------- -------
@ -574,7 +574,7 @@ class Table:
Parameters Parameters
---------- ----------
fname : file, str, or pathlib.Path fname : file, str, or pathlib.Path
Filename or file for writing. Filename or file to write.
with_labels : bool, optional with_labels : bool, optional
Write column labels. Defaults to True. Write column labels. Defaults to True.

View File

@ -42,7 +42,7 @@ class VTK:
""" """
Return repr(self). Return repr(self).
Give short human-readable summary. Give short, human-readable summary.
""" """
info = [self.vtk_data.__vtkname__] info = [self.vtk_data.__vtkname__]
@ -163,7 +163,7 @@ class VTK:
cells : sequence of int, len (3) cells : sequence of int, len (3)
Number of cells along each dimension. Number of cells along each dimension.
size : sequence of float, len (3) size : sequence of float, len (3)
Physical length along each dimension. Edge length along each dimension.
origin : sequence of float, len (3), optional origin : sequence of float, len (3), optional
Coordinates of grid origin. Coordinates of grid origin.
@ -293,7 +293,7 @@ class VTK:
Parameters Parameters
---------- ----------
fname : str or pathlib.Path fname : str or pathlib.Path
Filename for reading. Filename to read.
Valid extensions are .vti, .vtu, .vtp, .vtr, and .vtk. Valid extensions are .vti, .vtu, .vtp, .vtr, and .vtk.
dataset_type : {'ImageData', 'UnstructuredGrid', 'PolyData', 'RectilinearGrid'}, optional dataset_type : {'ImageData', 'UnstructuredGrid', 'PolyData', 'RectilinearGrid'}, optional
Name of the vtk.vtkDataSet subclass when opening a .vtk file. Name of the vtk.vtkDataSet subclass when opening a .vtk file.
@ -370,7 +370,7 @@ class VTK:
Parameters Parameters
---------- ----------
fname : str or pathlib.Path fname : str or pathlib.Path
Filename for writing. Filename to write.
parallel : bool, optional parallel : bool, optional
Write data in parallel background process. Defaults to True. Write data in parallel background process. Defaults to True.
compress : bool, optional compress : bool, optional
@ -433,6 +433,11 @@ class VTK:
Data to add or replace. Each table label is individually considered. Data to add or replace. Each table label is individually considered.
Number of rows needs to match either number of cells or number of points. Number of rows needs to match either number of cells or number of points.
Returns
-------
updated : damask.VTK
Updated VTK-based geometry.
Notes Notes
----- -----
If the number of cells equals the number of points, the data is added to both. If the number of cells equals the number of points, the data is added to both.
@ -548,7 +553,7 @@ class VTK:
Notes Notes
----- -----
The first component is shown when visualizing vector datasets The first component is shown when visualizing vector datasets
(this includes tensor datasets because they are flattened). (this includes tensor datasets as they are flattened).
""" """
# See http://compilatrix.com/article/vtk-1 for possible improvements. # See http://compilatrix.com/article/vtk-1 for possible improvements.

View File

@ -402,7 +402,7 @@ def displacement_node(size: _FloatSequence,
Returns Returns
------- -------
u_p : numpy.ndarray, shape (:,:,:,3) u_n : numpy.ndarray, shape (:,:,:,3)
Nodal displacements. Nodal displacements.
""" """

View File

@ -5,7 +5,7 @@ All routines operate on numpy.ndarrays of shape (...,3,3).
""" """
from typing import Sequence as _Sequence from typing import Sequence as _Sequence#, Literal as _Literal
import numpy as _np import numpy as _np
@ -81,7 +81,7 @@ def equivalent_strain_Mises(epsilon: _np.ndarray) -> _np.ndarray:
.. math:: .. math::
\epsilon_\text{vM} = \sqrt{2/3 \epsilon^\prime_{ij} \epsilon^\prime_{ij}} \epsilon_\text{vM} = \sqrt{\frac{2}{3}\,\epsilon^\prime_{ij} \epsilon^\prime_{ij}}
where :math:`\vb*{\epsilon}^\prime` is the deviatoric part where :math:`\vb*{\epsilon}^\prime` is the deviatoric part
of the strain tensor. of the strain tensor.
@ -110,7 +110,7 @@ def equivalent_stress_Mises(sigma: _np.ndarray) -> _np.ndarray:
.. math:: .. math::
\sigma_\text{vM} = \sqrt{3/2 \sigma^\prime_{ij} \sigma^\prime_{ij}} \sigma_\text{vM} = \sqrt{\frac{3}{2}\,\sigma^\prime_{ij} \sigma^\prime_{ij}}
where :math:`\vb*{\sigma}^\prime` is the deviatoric part where :math:`\vb*{\sigma}^\prime` is the deviatoric part
of the stress tensor. of the stress tensor.
@ -168,9 +168,10 @@ def rotation(T: _np.ndarray) -> _rotation.Rotation:
def strain(F: _np.ndarray, def strain(F: _np.ndarray,
#t: _Literal['V', 'U'], should work, but rejected by SC
t: str, t: str,
m: float) -> _np.ndarray: m: float) -> _np.ndarray:
""" r"""
Calculate strain tensor (SethHill family). Calculate strain tensor (SethHill family).
Parameters Parameters
@ -188,10 +189,19 @@ def strain(F: _np.ndarray,
epsilon : numpy.ndarray, shape (...,3,3) epsilon : numpy.ndarray, shape (...,3,3)
Strain of F. Strain of F.
Notes
-----
The strain is defined as:
.. math::
\vb*{\epsilon}_V^{(m)} = \frac{1}{2m} (\vb{V}^{2m} - \vb{I}) \\\\
\vb*{\epsilon}_U^{(m)} = \frac{1}{2m} (\vb{U}^{2m} - \vb{I})
References References
---------- ----------
https://en.wikipedia.org/wiki/Finite_strain_theory | https://en.wikipedia.org/wiki/Finite_strain_theory
https://de.wikipedia.org/wiki/Verzerrungstensor | https://de.wikipedia.org/wiki/Verzerrungstensor
""" """
if t not in ['V', 'U']: raise ValueError('polar decomposition type not in {V, U}') if t not in ['V', 'U']: raise ValueError('polar decomposition type not in {V, U}')
@ -315,8 +325,8 @@ def _polar_decomposition(T: _np.ndarray,
T : numpy.ndarray, shape (...,3,3) T : numpy.ndarray, shape (...,3,3)
Tensor of which the singular values are computed. Tensor of which the singular values are computed.
requested : sequence of {'R', 'U', 'V'} requested : sequence of {'R', 'U', 'V'}
Requested outputs: R for the rotation tensor, Requested outputs: 'R' for the rotation tensor,
V for left stretch tensor, and U for right stretch tensor. 'V' for left stretch tensor, and 'U' for right stretch tensor.
Returns Returns
------- -------

View File

@ -21,7 +21,7 @@ def from_random(size: _FloatSequence,
Parameters Parameters
---------- ----------
size : sequence of float, len (3) size : sequence of float, len (3)
Physical size of the seeding domain. Edge lengths of the seeding domain.
N_seeds : int N_seeds : int
Number of seeds. Number of seeds.
cells : sequence of int, len (3), optional. cells : sequence of int, len (3), optional.
@ -56,12 +56,12 @@ def from_Poisson_disc(size: _FloatSequence,
periodic: bool = True, periodic: bool = True,
rng_seed: _Optional[_NumpyRngSeed] = None) -> _np.ndarray: rng_seed: _Optional[_NumpyRngSeed] = None) -> _np.ndarray:
""" """
Place seeds according to a Poisson disc distribution. Place seeds following a Poisson disc distribution.
Parameters Parameters
---------- ----------
size : sequence of float, len (3) size : sequence of float, len (3)
Physical size of the seeding domain. Edge lengths of the seeding domain.
N_seeds : int N_seeds : int
Number of seeds. Number of seeds.
N_candidates : int N_candidates : int
@ -70,6 +70,7 @@ def from_Poisson_disc(size: _FloatSequence,
Minimum acceptable distance to other seeds. Minimum acceptable distance to other seeds.
periodic : bool, optional periodic : bool, optional
Calculate minimum distance for periodically repeated grid. Calculate minimum distance for periodically repeated grid.
Defaults to True.
rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. Defaults to None. A seed to initialize the BitGenerator. Defaults to None.
If None, then fresh, unpredictable entropy will be pulled from the OS. If None, then fresh, unpredictable entropy will be pulled from the OS.
@ -123,14 +124,36 @@ def from_grid(grid,
Consider all material IDs except those in selection. Defaults to False. Consider all material IDs except those in selection. Defaults to False.
average : bool, optional average : bool, optional
Seed corresponds to center of gravity of material ID cloud. Seed corresponds to center of gravity of material ID cloud.
Defaults to False.
periodic : bool, optional periodic : bool, optional
Center of gravity accounts for periodic boundaries. Center of gravity accounts for periodic boundaries.
Defaults to True.
Returns Returns
------- -------
coords, materials : numpy.ndarray, shape (:,3); numpy.ndarray, shape (:) coords, materials : numpy.ndarray, shape (:,3); numpy.ndarray, shape (:)
Seed coordinates in 3D space, material IDs. Seed coordinates in 3D space, material IDs.
Examples
--------
Recreate seeds from Voronoi tessellation.
>>> import numpy as np
>>> import scipy.spatial
>>> import damask
>>> seeds = damask.seeds.from_random(np.ones(3),29,[128]*3)
>>> (g := damask.Grid.from_Voronoi_tessellation([128]*3,np.ones(3),seeds))
cells: 128 × 128 × 128
size: 1.0 × 1.0 × 1.0
origin: 0.0 0.0 0.0 m
# materials: 29
>>> COG,matID = damask.seeds.from_grid(g,average=True)
>>> distance,ID = scipy.spatial.KDTree(COG,boxsize=g.size).query(seeds)
>>> np.max(distance) / np.linalg.norm(g.size/g.cells)
7.8057356746350415
>>> (ID == matID).all()
True
""" """
material = grid.material.reshape((-1,1),order='F') material = grid.material.reshape((-1,1),order='F')
mask = _np.full(grid.cells.prod(),True,dtype=bool) if selection is None else \ mask = _np.full(grid.cells.prod(),True,dtype=bool) if selection is None else \

View File

@ -2,6 +2,7 @@ import subprocess
import shlex import shlex
import re import re
from pathlib import Path from pathlib import Path
from typing import Literal
_marc_version = '2022.4' _marc_version = '2022.4'
_marc_root = '/opt/msc' _marc_root = '/opt/msc'
@ -54,7 +55,7 @@ class Marc:
def submit_job(self, model: str, job: str, def submit_job(self, model: str, job: str,
compile: bool = False, compile: bool = False,
optimization: str = '', optimization: Literal['', 'l', 'h'] = '',
env = None): env = None):
""" """
Assemble command line arguments and call Marc executable. Assemble command line arguments and call Marc executable.
@ -68,8 +69,8 @@ class Marc:
compile : bool, optional compile : bool, optional
Compile DAMASK_Marc user subroutine (and save for future use). Compile DAMASK_Marc user subroutine (and save for future use).
Defaults to False. Defaults to False.
optimization : str, optional optimization : {'', 'l', 'h'}, optional
Optimization level '' (-O0), 'l' (-O1), or 'h' (-O3). Optimization level '': -O0, 'l': -O1, or 'h': -O3.
Defaults to ''. Defaults to ''.
env : dict, optional env : dict, optional
Environment for execution. Environment for execution.

View File

@ -98,20 +98,17 @@ class TestGrid:
size=np.ones(3), size=np.ones(3),
origin=np.ones(4)) origin=np.ones(4))
def test_invalid_materials_shape(self,default): def test_invalid_materials_shape(self,default):
material = np.ones((3,3)) material = np.ones((3,3))
with pytest.raises(ValueError): with pytest.raises(ValueError):
Grid(material, Grid(material,
size=np.ones(3)) size=np.ones(3))
def test_invalid_materials_type(self,default): def test_invalid_materials_type(self,default):
material = np.random.randint(1,300,(3,4,5))==1 material = np.random.randint(1,300,(3,4,5))==1
with pytest.raises(TypeError): with pytest.raises(TypeError):
Grid(material) Grid(material)
@pytest.mark.parametrize('directions,reflect',[ @pytest.mark.parametrize('directions,reflect',[
(['x'], False), (['x'], False),
(['x','y','z'],True), (['x','y','z'],True),
@ -126,12 +123,16 @@ class TestGrid:
if update: modified.save(reference) if update: modified.save(reference)
assert Grid.load(reference) == modified assert Grid.load(reference) == modified
@pytest.mark.parametrize('directions',[(1,2,'y'),('a','b','x'),[1]]) @pytest.mark.parametrize('directions',[(1,2,'y'),('a','b','x'),[1]])
def test_mirror_invalid(self,default,directions): def test_mirror_invalid(self,default,directions):
with pytest.raises(ValueError): with pytest.raises(ValueError):
default.mirror(directions) default.mirror(directions)
@pytest.mark.parametrize('reflect',[True,False])
def test_mirror_order_invariant(self,default,reflect):
direction = np.array(['x','y','z'])
assert default.mirror(np.random.permutation(direction),reflect=reflect) \
== default.mirror(np.random.permutation(direction),reflect=reflect)
@pytest.mark.parametrize('directions',[ @pytest.mark.parametrize('directions',[
['x'], ['x'],
@ -147,22 +148,30 @@ class TestGrid:
if update: modified.save(reference) if update: modified.save(reference)
assert Grid.load(reference) == modified assert Grid.load(reference) == modified
def test_flip_order_invariant(self,default):
direction = np.array(['x','y','z'])
assert default.flip(np.random.permutation(direction)) \
== default.flip(np.random.permutation(direction))
def test_flip_invariant(self,default): def test_flip_mirrored_invariant(self,default):
assert default == default.flip([]) direction = np.random.permutation(['x','y','z'])
assert default.mirror(direction,True) == default.mirror(direction,True).flip(direction)
def test_flip_equal_halfspin(self,default):
direction = ['x','y','z']
i = np.random.choice(3)
assert default.rotate(Rotation.from_axis_angle(np.hstack((np.identity(3)[i],180)),degrees=True)) \
== default.flip(direction[:i]+direction[i+1:])
@pytest.mark.parametrize('direction',[['x'],['x','y']]) @pytest.mark.parametrize('direction',[['x'],['x','y']])
def test_flip_double(self,default,direction): def test_flip_double(self,default,direction):
assert default == default.flip(direction).flip(direction) assert default == default.flip(direction).flip(direction)
@pytest.mark.parametrize('directions',[(1,2,'y'),('a','b','x'),[1]]) @pytest.mark.parametrize('directions',[(1,2,'y'),('a','b','x'),[1]])
def test_flip_invalid(self,default,directions): def test_flip_invalid(self,default,directions):
with pytest.raises(ValueError): with pytest.raises(ValueError):
default.flip(directions) default.flip(directions)
@pytest.mark.parametrize('distance',[1.,np.sqrt(3)]) @pytest.mark.parametrize('distance',[1.,np.sqrt(3)])
@pytest.mark.parametrize('selection',[None,1,[1],[1,2,3]]) @pytest.mark.parametrize('selection',[None,1,[1],[1,2,3]])
@pytest.mark.parametrize('periodic',[True,False]) @pytest.mark.parametrize('periodic',[True,False])
@ -184,7 +193,6 @@ class TestGrid:
assert random.clean(selection=None,invert_selection=True,rng_seed=0) == random.clean(rng_seed=0) and \ assert random.clean(selection=None,invert_selection=True,rng_seed=0) == random.clean(rng_seed=0) and \
random.clean(selection=None,invert_selection=False,rng_seed=0) == random.clean(rng_seed=0) random.clean(selection=None,invert_selection=False,rng_seed=0) == random.clean(rng_seed=0)
@pytest.mark.parametrize('cells',[ @pytest.mark.parametrize('cells',[
(10,11,10), (10,11,10),
[10,13,10], [10,13,10],
@ -201,7 +209,6 @@ class TestGrid:
if update: modified.save(reference) if update: modified.save(reference)
assert Grid.load(reference) == modified assert Grid.load(reference) == modified
def test_renumber(self,default): def test_renumber(self,default):
material = default.material.copy() material = default.material.copy()
for m in np.unique(material): for m in np.unique(material):
@ -213,7 +220,6 @@ class TestGrid:
assert not default == modified assert not default == modified
assert default == modified.renumber() assert default == modified.renumber()
def test_assemble(self): def test_assemble(self):
cells = np.random.randint(8,16,3) cells = np.random.randint(8,16,3)
N = cells.prod() N = cells.prod()
@ -221,7 +227,6 @@ class TestGrid:
idx = np.random.randint(0,N,N).reshape(cells) idx = np.random.randint(0,N,N).reshape(cells)
assert (idx == g.assemble(idx).material).all assert (idx == g.assemble(idx).material).all
def test_substitute(self,default): def test_substitute(self,default):
offset = np.random.randint(1,500) offset = np.random.randint(1,500)
modified = Grid(default.material + offset, modified = Grid(default.material + offset,
@ -257,7 +262,6 @@ class TestGrid:
modified.rotate(Rotation.from_axis_angle(axis_angle,degrees=True)) modified.rotate(Rotation.from_axis_angle(axis_angle,degrees=True))
assert default == modified assert default == modified
@pytest.mark.parametrize('Eulers',[[32.0,68.0,21.0], @pytest.mark.parametrize('Eulers',[[32.0,68.0,21.0],
[0.0,32.0,240.0]]) [0.0,32.0,240.0]])
def test_rotate(self,default,update,ref_path,Eulers): def test_rotate(self,default,update,ref_path,Eulers):
@ -267,7 +271,6 @@ class TestGrid:
if update: modified.save(reference) if update: modified.save(reference)
assert Grid.load(reference) == modified assert Grid.load(reference) == modified
def test_canvas_extend(self,default): def test_canvas_extend(self,default):
cells = default.cells cells = default.cells
cells_add = np.random.randint(0,30,(3)) cells_add = np.random.randint(0,30,(3))
@ -364,14 +367,12 @@ class TestGrid:
assert random.vicinity_offset(selection=None,invert_selection=False) == random.vicinity_offset() and \ assert random.vicinity_offset(selection=None,invert_selection=False) == random.vicinity_offset() and \
random.vicinity_offset(selection=None,invert_selection=True ) == random.vicinity_offset() random.vicinity_offset(selection=None,invert_selection=True ) == random.vicinity_offset()
@pytest.mark.parametrize('periodic',[True,False]) @pytest.mark.parametrize('periodic',[True,False])
def test_vicinity_offset_invariant(self,default,periodic): def test_vicinity_offset_invariant(self,default,periodic):
offset = default.vicinity_offset(selection=[default.material.max()+1, offset = default.vicinity_offset(selection=[default.material.max()+1,
default.material.min()-1]) default.material.min()-1])
assert np.all(offset.material==default.material) assert np.all(offset.material==default.material)
@pytest.mark.parametrize('periodic',[True,False]) @pytest.mark.parametrize('periodic',[True,False])
def test_tessellation_approaches(self,periodic): def test_tessellation_approaches(self,periodic):
cells = np.random.randint(10,20,3) cells = np.random.randint(10,20,3)
@ -382,7 +383,6 @@ class TestGrid:
Laguerre = Grid.from_Laguerre_tessellation(cells,size,seeds,np.ones(N_seeds),np.arange(N_seeds)+5,periodic) Laguerre = Grid.from_Laguerre_tessellation(cells,size,seeds,np.ones(N_seeds),np.arange(N_seeds)+5,periodic)
assert Laguerre == Voronoi assert Laguerre == Voronoi
def test_Laguerre_weights(self): def test_Laguerre_weights(self):
cells = np.random.randint(10,20,3) cells = np.random.randint(10,20,3)
size = np.random.random(3) + 1.0 size = np.random.random(3) + 1.0
@ -394,7 +394,6 @@ class TestGrid:
Laguerre = Grid.from_Laguerre_tessellation(cells,size,seeds,weights,periodic=np.random.random()>0.5) Laguerre = Grid.from_Laguerre_tessellation(cells,size,seeds,weights,periodic=np.random.random()>0.5)
assert np.all(Laguerre.material == ms) assert np.all(Laguerre.material == ms)
@pytest.mark.parametrize('approach',['Laguerre','Voronoi']) @pytest.mark.parametrize('approach',['Laguerre','Voronoi'])
def test_tessellate_bicrystal(self,approach): def test_tessellate_bicrystal(self,approach):
cells = np.random.randint(5,10,3)*2 cells = np.random.randint(5,10,3)*2
@ -408,7 +407,6 @@ class TestGrid:
grid = Grid.from_Voronoi_tessellation(cells,size,seeds, periodic=np.random.random()>0.5) grid = Grid.from_Voronoi_tessellation(cells,size,seeds, periodic=np.random.random()>0.5)
assert np.all(grid.material == material) assert np.all(grid.material == material)
@pytest.mark.parametrize('surface',['Schwarz P', @pytest.mark.parametrize('surface',['Schwarz P',
'Double Primitive', 'Double Primitive',
'Schwarz D', 'Schwarz D',
@ -450,7 +448,6 @@ class TestGrid:
grid = Grid.from_minimal_surface(cells,np.ones(3),surface,threshold) grid = Grid.from_minimal_surface(cells,np.ones(3),surface,threshold)
assert np.isclose(np.count_nonzero(grid.material==1)/np.prod(grid.cells),.5,rtol=1e-3) assert np.isclose(np.count_nonzero(grid.material==1)/np.prod(grid.cells),.5,rtol=1e-3)
def test_from_table(self): def test_from_table(self):
cells = np.random.randint(60,100,3) cells = np.random.randint(60,100,3)
size = np.ones(3)+np.random.rand(3) size = np.ones(3)+np.random.rand(3)
@ -462,7 +459,6 @@ class TestGrid:
g = Grid.from_table(t,'coords',['indicator','z']) g = Grid.from_table(t,'coords',['indicator','z'])
assert g.N_materials == g.cells[0]*2 and (g.material[:,:,-1]-g.material[:,:,0] == cells[0]).all() assert g.N_materials == g.cells[0]*2 and (g.material[:,:,-1]-g.material[:,:,0] == cells[0]).all()
def test_from_table_recover(self,tmp_path): def test_from_table_recover(self,tmp_path):
cells = np.random.randint(60,100,3) cells = np.random.randint(60,100,3)
size = np.ones(3)+np.random.rand(3) size = np.ones(3)+np.random.rand(3)
@ -472,7 +468,6 @@ class TestGrid:
t = Table({'c':3,'m':1},np.column_stack((coords.reshape(-1,3,order='F'),grid.material.flatten(order='F')))) t = Table({'c':3,'m':1},np.column_stack((coords.reshape(-1,3,order='F'),grid.material.flatten(order='F'))))
assert grid.sort().renumber() == Grid.from_table(t,'c',['m']) assert grid.sort().renumber() == Grid.from_table(t,'c',['m'])
@pytest.mark.parametrize('periodic',[True,False]) @pytest.mark.parametrize('periodic',[True,False])
@pytest.mark.parametrize('direction',['x','y','z',['x','y'],'zy','xz',['x','y','z']]) @pytest.mark.parametrize('direction',['x','y','z',['x','y'],'zy','xz',['x','y','z']])
@pytest.mark.xfail(int(vtk.vtkVersion.GetVTKVersion().split('.')[0])<8, reason='missing METADATA') @pytest.mark.xfail(int(vtk.vtkVersion.GetVTKVersion().split('.')[0])<8, reason='missing METADATA')
@ -497,7 +492,6 @@ class TestGrid:
np.allclose(grain.size,point.size) and \ np.allclose(grain.size,point.size) and \
(grain.sort().material == point.material+1).all() (grain.sort().material == point.material+1).all()
def test_load_DREAM3D_reference(self,ref_path,update): def test_load_DREAM3D_reference(self,ref_path,update):
current = Grid.load_DREAM3D(ref_path/'measured.dream3d') current = Grid.load_DREAM3D(ref_path/'measured.dream3d')
reference = Grid.load(ref_path/'measured.vti') reference = Grid.load(ref_path/'measured.vti')