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):
"""
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.
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.
References
@ -153,12 +153,12 @@ class Colormap(mpl.colors.ListedColormap):
- 'hsl': Hue Saturation Luminance.
- 'xyz': CIE Xyz.
- 'lab': CIE Lab.
- 'msh': Msh (for perceptual uniform interpolation).
- 'msh': Msh (for perceptually uniform interpolation).
Returns
-------
new : damask.Colormap
Colormap within given bounds.
Colormap spanning given bounds.
Examples
--------
@ -288,6 +288,7 @@ class Colormap(mpl.colors.ListedColormap):
Value range (left,right) spanned by colormap.
gap : field.dtype, optional
Transparent value. NaN will always be rendered transparent.
Defaults to None.
Returns
-------
@ -334,6 +335,7 @@ class Colormap(mpl.colors.ListedColormap):
--------
>>> import damask
>>> damask.Colormap.from_predefined('stress').reversed()
Colormap: stress_r
"""
rev = super().reversed(name)
@ -353,6 +355,7 @@ class Colormap(mpl.colors.ListedColormap):
If None, colormap name + suffix.
suffix: str, optional
Extension to use for colormap file.
Defaults to empty.
Returns
-------
@ -452,8 +455,8 @@ class Colormap(mpl.colors.ListedColormap):
References
----------
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
https://www.kennethmoreland.com/color-maps/diverging_map.py
| https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
| https://www.kennethmoreland.com/color-maps/diverging_map.py
"""
def rad_diff(a,b):
@ -735,8 +738,8 @@ class Colormap(mpl.colors.ListedColormap):
References
----------
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
https://www.kennethmoreland.com/color-maps/diverging_map.py
| https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
| https://www.kennethmoreland.com/color-maps/diverging_map.py
"""
M = np.linalg.norm(lab)
@ -763,8 +766,8 @@ class Colormap(mpl.colors.ListedColormap):
References
----------
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
https://www.kennethmoreland.com/color-maps/diverging_map.py
| https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
| https://www.kennethmoreland.com/color-maps/diverging_map.py
"""
return np.array([

View File

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

View File

@ -32,10 +32,10 @@ class Grid:
"""
Geometry definition for grid solvers.
Create and manipulate geometry definitions for storage as VTK
image data files ('.vti' extension). A grid contains the
material ID (referring to the entry in 'material.yaml') and
the physical size.
Create and manipulate geometry definitions for storage as VTK ImageData
files ('.vti' extension). A grid has a physical size, a coordinate origin,
and contains the material ID (indexing an entry in 'material.yaml')
as well as initial condition fields.
"""
def __init__(self,
@ -57,7 +57,7 @@ class Grid:
origin : sequence of float, len (3), optional
Coordinates of grid origin in meter. Defaults to [0.0,0.0,0.0].
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
Additional, human-readable information, e.g. history of operations.
@ -74,7 +74,7 @@ class Grid:
"""
Return repr(self).
Give short human-readable summary.
Give short, human-readable summary.
"""
mat_min = np.nanmin(self.material)
@ -144,7 +144,7 @@ class Grid:
@property
def size(self) -> np.ndarray:
"""Physical size of grid in meter."""
"""Edge lengths of grid in meter."""
return self._size
@size.setter
@ -157,7 +157,7 @@ class Grid:
@property
def origin(self) -> np.ndarray:
"""Coordinates of grid origin in meter."""
"""Vector to grid origin in meter."""
return self._origin
@origin.setter
@ -186,7 +186,7 @@ class Grid:
@property
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)
@ -199,7 +199,7 @@ class Grid:
@staticmethod
def load(fname: Union[str, Path]) -> 'Grid':
"""
Load from VTK image data file.
Load from VTK ImageData file.
Parameters
----------
@ -470,9 +470,9 @@ class Grid:
Parameters
----------
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)
Physical size of the grid in meter.
Edge lengths of the grid in meter.
seeds : numpy.ndarray of float, shape (:,3)
Position of the seed points in meter. All points need to lay within the box.
weights : sequence of float, len (seeds.shape[0])
@ -527,9 +527,9 @@ class Grid:
Parameters
----------
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)
Physical size of the grid in meter.
Edge lengths of the grid in meter.
seeds : numpy.ndarray of float, shape (:,3)
Position of the seed points in meter. All points need to lay within the box.
material : sequence of int, len (seeds.shape[0]), optional
@ -608,14 +608,14 @@ class Grid:
periods: int = 1,
materials: IntSequence = (0,1)) -> 'Grid':
"""
Create grid from definition of triply periodic minimal surface.
Create grid from definition of triply-periodic minimal surface.
Parameters
----------
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)
Physical size of the grid in meter.
Edge lengths of the grid in meter.
surface : str
Type of the minimal surface. See notes for details.
threshold : float, optional.
@ -664,19 +664,19 @@ class Grid:
>>> import numpy as np
>>> import damask
>>> damask.Grid.from_minimal_surface([64]*3,np.ones(3)*1.e-4,'Gyroid')
cells : 64 x 64 x 64
size : 0.0001 x 0.0001 x 0.0001
cells : 64 × 64 × 64
size : 0.0001 × 0.0001 × 0.0001
origin: 0.0 0.0 0.0 m
# 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 damask
>>> damask.Grid.from_minimal_surface([80]*3,np.ones(3)*5.e-4,
... 'Neovius',materials=(1,5))
cells : 80 x 80 x 80
size : 0.0005 x 0.0005 x 0.0005
cells : 80 × 80 × 80
size : 0.0005 × 0.0005 × 0.0005
origin: 0.0 0.0 0.0 m
# materials: 2 (min: 1, max: 5)
@ -695,12 +695,13 @@ class Grid:
fname: Union[str, Path],
compress: bool = True):
"""
Save as VTK image data file.
Save as VTK ImageData file.
Parameters
----------
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 with zlib algorithm. Defaults to True.
@ -727,7 +728,7 @@ class Grid:
fname : str or file handle
Geometry file to write with extension '.geom'.
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)
@ -771,13 +772,13 @@ class Grid:
Parameters
----------
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 (measured in cells) from old to new grid.
Defaults to [0,0,0].
fill : int, optional
Material ID to fill the background.
Defaults to material.max() + 1.
Defaults to material.max()+1.
Returns
-------
@ -790,11 +791,11 @@ class Grid:
>>> import numpy as np
>>> 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])
cells : 33 x 32 x 16
size : 0.0001 x 0.0001 x 5e-05
origin: 0.0 0.0 5e-05 m
cells: 32 × 32 × 16
size: 0.001 × 0.001 × 0.0005
origin: 0.0 0.0 0.0005 m
# materials: 1
"""
@ -837,16 +838,33 @@ class Grid:
Examples
--------
Mirror along x- and y-direction.
Mirror along y-direction.
>>> import numpy as np
>>> import damask
>>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4)
>>> g.mirror('xy',True)
cells : 64 x 64 x 32
size : 0.0002 x 0.0002 x 0.0001
>>> (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: 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']):
@ -884,11 +902,29 @@ class Grid:
updated : damask.Grid
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']):
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])
return Grid(material = mat,
@ -902,7 +938,7 @@ class Grid:
R: Rotation,
fill: Optional[int] = None) -> 'Grid':
"""
Rotate grid (and pad if required).
Rotate grid (possibly extending its bounding box).
Parameters
----------
@ -910,13 +946,27 @@ class Grid:
Rotation to apply to the grid.
fill : int, optional
Material ID to fill enlarged bounding box.
Defaults to material.max() + 1.
Defaults to material.max()+1.
Returns
-------
updated : damask.Grid
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
# 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,
cells: IntSequence) -> 'Grid':
"""
Scale grid to new cell count.
Scale grid to new cell counts.
Parameters
----------
cells : sequence of int, len (3)
Number of cells in x,y,z direction.
Cell counts along x,y,z direction.
Returns
-------
@ -955,7 +1005,7 @@ class Grid:
Examples
--------
Double resolution.
Double grid resolution.
>>> import numpy as np
>>> import damask
@ -965,8 +1015,8 @@ class Grid:
origin: 0.0 0.0 0.0 m
# materials: 1
>>> g.scale(g.cells*2)
cells : 64 x 64 x 64
size : 0.0001 x 0.0001 x 0.0001
cells : 64 × 64 × 64
size : 0.0001 × 0.0001 × 0.0001
origin: 0.0 0.0 0.0 m
# materials: 1
@ -1069,7 +1119,7 @@ class 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
-------
@ -1186,7 +1236,7 @@ class Grid:
fill : int, optional
Fill value for primitive. Defaults to material.max()+1.
R : damask.Rotation, optional
Rotation of primitive. Defaults to no rotation.
Rotation of the primitive. Defaults to no rotation.
inverse : bool, optional
Retain original materials within primitive and fill outside.
Defaults to False.
@ -1206,8 +1256,8 @@ class Grid:
>>> import damask
>>> 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)
cells : 64 x 64 x 64
size : 0.0001 x 0.0001 x 0.0001
cells : 64 × 64 × 64
size : 0.0001 × 0.0001 × 0.0001
origin: 0.0 0.0 0.0 m
# materials: 2
@ -1217,8 +1267,8 @@ class Grid:
>>> import damask
>>> 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)
cells : 64 x 64 x 64
size : 0.0001 x 0.0001 x 0.0001
cells : 64 × 64 × 64
size : 0.0001 × 0.0001 × 0.0001
origin: 0.0 0.0 0.0 m
# materials: 2

View File

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

View File

@ -65,9 +65,9 @@ def _empty_like(dataset: np.ma.core.MaskedArray,
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.
This class provides a customizable view on the DADF5 file.
@ -93,7 +93,7 @@ class Result:
def __init__(self, fname: Union[str, Path]):
"""
New result view bound to a HDF5 file.
New result view bound to a DADF5 file.
Parameters
----------
@ -167,7 +167,7 @@ class Result:
"""
Return repr(self).
Give short human-readable summary.
Give short, human-readable summary.
"""
with h5py.File(self.fname,'r') as f:
@ -195,7 +195,7 @@ class Result:
homogenizations: Union[None, str, Sequence[str], bool] = None,
fields: Union[None, str, Sequence[str], bool] = None) -> "Result":
"""
Manages the visibility of the groups.
Manage the visibility of the groups.
Parameters
----------
@ -319,15 +319,15 @@ class Result:
Parameters
----------
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.
Simulation time(s) of increments to select.
Simulation times of increments to select.
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.
Name(s) of homogenizations to select.
Names of homogenizations to select.
fields: (list of) str, or bool, optional.
Name(s) of fields to select.
Names of fields to select.
protected: bool, optional.
Protection status of existing data.
@ -375,15 +375,15 @@ class Result:
Parameters
----------
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.
Simulation time(s) of increments to select.
Simulation times of increments to select.
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.
Name(s) of homogenizations to select.
Names of homogenizations to select.
fields: (list of) str, or bool, optional.
Name(s) of fields to select.
Names of fields to select.
Returns
-------
@ -418,15 +418,15 @@ class Result:
Parameters
----------
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.
Simulation time(s) of increments to select.
Simulation times of increments to select.
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.
Name(s) of homogenizations to select.
Names of homogenizations to select.
fields: (list of) str, or bool, optional.
Name(s) of fields to select.
Names of fields to select.
Returns
-------
@ -721,9 +721,11 @@ class Result:
Parameters
----------
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
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})
@ -1023,14 +1025,14 @@ class Result:
x: str,
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
----------
x : str
Name of vector or tensor dataset.
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})
@ -1052,7 +1054,7 @@ class Result:
def add_stress_second_Piola_Kirchhoff(self,
P: str = 'P',
F: str = 'F'):
"""
r"""
Add second Piola-Kirchhoff stress calculated from first Piola-Kirchhoff stress and deformation gradient.
Parameters
@ -1064,9 +1066,10 @@ class Result:
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.
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.
"""
@ -1240,10 +1243,11 @@ class Result:
Notes
-----
The incoporation of rotational parts into the elastic and plastic
deformation gradient requires it to use material/Lagragian strain measures
(based on 'U') for plastic strains and spatial/Eulerian strain measures
(based on 'V') for elastic strains when calculating averages.
The presence of rotational parts in the elastic and plastic deformation gradient
calls for the use of
material/Lagragian strain measures (based on 'U') for plastic strains and
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})
@ -1302,7 +1306,7 @@ class Result:
Notes
-----
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})
@ -1331,7 +1335,7 @@ class Result:
Notes
-----
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})
@ -1361,7 +1365,7 @@ class Result:
Notes
-----
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})
@ -1379,10 +1383,10 @@ class Result:
----------
func : function
Callback function that calculates a new dataset from one or
more datasets per HDF5 group.
more datasets per DADF5 group.
datasets : dictionary
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
Arguments parsed to func.
@ -1462,10 +1466,10 @@ class Result:
----------
callback : function
Callback function that calculates a new dataset from one or
more datasets per HDF5 group.
more datasets per DADF5 group.
datasets : dictionary
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
Arguments parsed to func.
@ -1500,7 +1504,7 @@ class Result:
dataset.attrs['overwritten'] = True
else:
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:]
else:
chunks = shape
@ -1828,9 +1832,10 @@ class Result:
Export to VTK cell/point data.
One VTK file per visible increment is created.
For point data, the VTK format is poly data (.vtp).
For cell data, either an image (.vti) or unstructured (.vtu) dataset
is written for grid-based or mesh-based simulations, respectively.
For point data, the VTK format is PolyData (.vtp).
For cell data, the file format is either ImageData (.vti)
or UnstructuredGrid (.vtu) for grid-based or mesh-based simulations,
respectively.
Parameters
----------

View File

@ -35,8 +35,8 @@ class Rotation:
Rotate vector 'a' (defined in coordinate system 'A') to
coordinates 'b' expressed in system 'B':
>>> import damask
>>> import numpy as np
>>> import damask
>>> Q = damask.Rotation.from_random()
>>> a = np.random.rand(3)
>>> b = Q @ a
@ -45,8 +45,8 @@ class Rotation:
Compound rotations R1 (first) and R2 (second):
>>> import damask
>>> import numpy as np
>>> import damask
>>> R1 = damask.Rotation.from_random()
>>> R2 = damask.Rotation.from_random()
>>> R = R2 * R1
@ -69,7 +69,7 @@ class Rotation:
Parameters
----------
rotation : list, numpy.ndarray, Rotation, optional
rotation : list, numpy.ndarray, or Rotation, optional
Unit quaternion in positive real hemisphere.
Use .from_quaternion to perform a sanity check.
Defaults to no rotation.
@ -88,7 +88,7 @@ class Rotation:
"""
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)}'\

View File

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

View File

@ -42,7 +42,7 @@ class VTK:
"""
Return repr(self).
Give short human-readable summary.
Give short, human-readable summary.
"""
info = [self.vtk_data.__vtkname__]
@ -163,7 +163,7 @@ class VTK:
cells : sequence of int, len (3)
Number of cells along each dimension.
size : sequence of float, len (3)
Physical length along each dimension.
Edge length along each dimension.
origin : sequence of float, len (3), optional
Coordinates of grid origin.
@ -293,7 +293,7 @@ class VTK:
Parameters
----------
fname : str or pathlib.Path
Filename for reading.
Filename to read.
Valid extensions are .vti, .vtu, .vtp, .vtr, and .vtk.
dataset_type : {'ImageData', 'UnstructuredGrid', 'PolyData', 'RectilinearGrid'}, optional
Name of the vtk.vtkDataSet subclass when opening a .vtk file.
@ -370,7 +370,7 @@ class VTK:
Parameters
----------
fname : str or pathlib.Path
Filename for writing.
Filename to write.
parallel : bool, optional
Write data in parallel background process. Defaults to True.
compress : bool, optional
@ -433,6 +433,11 @@ class VTK:
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.
Returns
-------
updated : damask.VTK
Updated VTK-based geometry.
Notes
-----
If the number of cells equals the number of points, the data is added to both.
@ -548,7 +553,7 @@ class VTK:
Notes
-----
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.

View File

@ -402,7 +402,7 @@ def displacement_node(size: _FloatSequence,
Returns
-------
u_p : numpy.ndarray, shape (:,:,:,3)
u_n : numpy.ndarray, shape (:,:,:,3)
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
@ -81,7 +81,7 @@ def equivalent_strain_Mises(epsilon: _np.ndarray) -> _np.ndarray:
.. 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
of the strain tensor.
@ -110,7 +110,7 @@ def equivalent_stress_Mises(sigma: _np.ndarray) -> _np.ndarray:
.. 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
of the stress tensor.
@ -168,9 +168,10 @@ def rotation(T: _np.ndarray) -> _rotation.Rotation:
def strain(F: _np.ndarray,
#t: _Literal['V', 'U'], should work, but rejected by SC
t: str,
m: float) -> _np.ndarray:
"""
r"""
Calculate strain tensor (SethHill family).
Parameters
@ -188,10 +189,19 @@ def strain(F: _np.ndarray,
epsilon : numpy.ndarray, shape (...,3,3)
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
----------
https://en.wikipedia.org/wiki/Finite_strain_theory
https://de.wikipedia.org/wiki/Verzerrungstensor
| https://en.wikipedia.org/wiki/Finite_strain_theory
| https://de.wikipedia.org/wiki/Verzerrungstensor
"""
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)
Tensor of which the singular values are computed.
requested : sequence of {'R', 'U', 'V'}
Requested outputs: R for the rotation tensor,
V for left stretch tensor, and U for right stretch tensor.
Requested outputs: 'R' for the rotation tensor,
'V' for left stretch tensor, and 'U' for right stretch tensor.
Returns
-------

View File

@ -21,7 +21,7 @@ def from_random(size: _FloatSequence,
Parameters
----------
size : sequence of float, len (3)
Physical size of the seeding domain.
Edge lengths of the seeding domain.
N_seeds : int
Number of seeds.
cells : sequence of int, len (3), optional.
@ -56,12 +56,12 @@ def from_Poisson_disc(size: _FloatSequence,
periodic: bool = True,
rng_seed: _Optional[_NumpyRngSeed] = None) -> _np.ndarray:
"""
Place seeds according to a Poisson disc distribution.
Place seeds following a Poisson disc distribution.
Parameters
----------
size : sequence of float, len (3)
Physical size of the seeding domain.
Edge lengths of the seeding domain.
N_seeds : int
Number of seeds.
N_candidates : int
@ -70,6 +70,7 @@ def from_Poisson_disc(size: _FloatSequence,
Minimum acceptable distance to other seeds.
periodic : bool, optional
Calculate minimum distance for periodically repeated grid.
Defaults to True.
rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
A seed to initialize the BitGenerator. Defaults to None.
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.
average : bool, optional
Seed corresponds to center of gravity of material ID cloud.
Defaults to False.
periodic : bool, optional
Center of gravity accounts for periodic boundaries.
Defaults to True.
Returns
-------
coords, materials : numpy.ndarray, shape (:,3); numpy.ndarray, shape (:)
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')
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 re
from pathlib import Path
from typing import Literal
_marc_version = '2022.4'
_marc_root = '/opt/msc'
@ -54,7 +55,7 @@ class Marc:
def submit_job(self, model: str, job: str,
compile: bool = False,
optimization: str = '',
optimization: Literal['', 'l', 'h'] = '',
env = None):
"""
Assemble command line arguments and call Marc executable.
@ -68,8 +69,8 @@ class Marc:
compile : bool, optional
Compile DAMASK_Marc user subroutine (and save for future use).
Defaults to False.
optimization : str, optional
Optimization level '' (-O0), 'l' (-O1), or 'h' (-O3).
optimization : {'', 'l', 'h'}, optional
Optimization level '': -O0, 'l': -O1, or 'h': -O3.
Defaults to ''.
env : dict, optional
Environment for execution.

View File

@ -98,20 +98,17 @@ class TestGrid:
size=np.ones(3),
origin=np.ones(4))
def test_invalid_materials_shape(self,default):
material = np.ones((3,3))
with pytest.raises(ValueError):
Grid(material,
size=np.ones(3))
def test_invalid_materials_type(self,default):
material = np.random.randint(1,300,(3,4,5))==1
with pytest.raises(TypeError):
Grid(material)
@pytest.mark.parametrize('directions,reflect',[
(['x'], False),
(['x','y','z'],True),
@ -126,12 +123,16 @@ class TestGrid:
if update: modified.save(reference)
assert Grid.load(reference) == modified
@pytest.mark.parametrize('directions',[(1,2,'y'),('a','b','x'),[1]])
def test_mirror_invalid(self,default,directions):
with pytest.raises(ValueError):
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',[
['x'],
@ -147,22 +148,30 @@ class TestGrid:
if update: modified.save(reference)
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):
assert default == default.flip([])
def test_flip_mirrored_invariant(self,default):
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']])
def test_flip_double(self,default,direction):
assert default == default.flip(direction).flip(direction)
@pytest.mark.parametrize('directions',[(1,2,'y'),('a','b','x'),[1]])
def test_flip_invalid(self,default,directions):
with pytest.raises(ValueError):
default.flip(directions)
@pytest.mark.parametrize('distance',[1.,np.sqrt(3)])
@pytest.mark.parametrize('selection',[None,1,[1],[1,2,3]])
@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 \
random.clean(selection=None,invert_selection=False,rng_seed=0) == random.clean(rng_seed=0)
@pytest.mark.parametrize('cells',[
(10,11,10),
[10,13,10],
@ -201,7 +209,6 @@ class TestGrid:
if update: modified.save(reference)
assert Grid.load(reference) == modified
def test_renumber(self,default):
material = default.material.copy()
for m in np.unique(material):
@ -213,7 +220,6 @@ class TestGrid:
assert not default == modified
assert default == modified.renumber()
def test_assemble(self):
cells = np.random.randint(8,16,3)
N = cells.prod()
@ -221,7 +227,6 @@ class TestGrid:
idx = np.random.randint(0,N,N).reshape(cells)
assert (idx == g.assemble(idx).material).all
def test_substitute(self,default):
offset = np.random.randint(1,500)
modified = Grid(default.material + offset,
@ -257,7 +262,6 @@ class TestGrid:
modified.rotate(Rotation.from_axis_angle(axis_angle,degrees=True))
assert default == modified
@pytest.mark.parametrize('Eulers',[[32.0,68.0,21.0],
[0.0,32.0,240.0]])
def test_rotate(self,default,update,ref_path,Eulers):
@ -267,7 +271,6 @@ class TestGrid:
if update: modified.save(reference)
assert Grid.load(reference) == modified
def test_canvas_extend(self,default):
cells = default.cells
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 \
random.vicinity_offset(selection=None,invert_selection=True ) == random.vicinity_offset()
@pytest.mark.parametrize('periodic',[True,False])
def test_vicinity_offset_invariant(self,default,periodic):
offset = default.vicinity_offset(selection=[default.material.max()+1,
default.material.min()-1])
assert np.all(offset.material==default.material)
@pytest.mark.parametrize('periodic',[True,False])
def test_tessellation_approaches(self,periodic):
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)
assert Laguerre == Voronoi
def test_Laguerre_weights(self):
cells = np.random.randint(10,20,3)
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)
assert np.all(Laguerre.material == ms)
@pytest.mark.parametrize('approach',['Laguerre','Voronoi'])
def test_tessellate_bicrystal(self,approach):
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)
assert np.all(grid.material == material)
@pytest.mark.parametrize('surface',['Schwarz P',
'Double Primitive',
'Schwarz D',
@ -450,7 +448,6 @@ class TestGrid:
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)
def test_from_table(self):
cells = np.random.randint(60,100,3)
size = np.ones(3)+np.random.rand(3)
@ -462,7 +459,6 @@ class TestGrid:
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()
def test_from_table_recover(self,tmp_path):
cells = np.random.randint(60,100,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'))))
assert grid.sort().renumber() == Grid.from_table(t,'c',['m'])
@pytest.mark.parametrize('periodic',[True,False])
@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')
@ -497,7 +492,6 @@ class TestGrid:
np.allclose(grain.size,point.size) and \
(grain.sort().material == point.material+1).all()
def test_load_DREAM3D_reference(self,ref_path,update):
current = Grid.load_DREAM3D(ref_path/'measured.dream3d')
reference = Grid.load(ref_path/'measured.vti')