Merge branch 'development' into export_DAMASK_to_DREAM3D

This commit is contained in:
Vitesh Shah 2023-07-05 15:04:08 +02:00
commit e4b793cf77
293 changed files with 6462 additions and 5792 deletions

8
.gitattributes vendored
View File

@ -12,12 +12,12 @@
*.pbz2 binary
# ignore files from MSC.Marc in language statistics
install/MarcMentat/** linguist-vendored
src/Marc/include/* linguist-vendored
install/MarcMentat/** linguist-vendored
src/Marc/include/* linguist-vendored
install/MarcMentat/MSC_modifications.py linguist-vendored=false
# ignore reference files for tests in language statistics
python/tests/reference/** linguist-vendored
python/tests/resources/** linguist-vendored
# ignore deprecated scripts
processing/legacy/** linguist-vendored
processing/legacy/** linguist-vendored

View File

@ -2,7 +2,7 @@ name: Grid and Mesh Solver
on: [push]
env:
PETSC_VERSION: '3.18.2'
PETSC_VERSION: '3.18.4'
HOMEBREW_NO_ANALYTICS: 'ON' # Make Homebrew installation a little quicker
HOMEBREW_NO_AUTO_UPDATE: 'ON'
HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK: 'ON'
@ -82,7 +82,8 @@ jobs:
- name: DAMASK - Run
run: |
./bin/DAMASK_grid -l tensionX.yaml -g 20grains16x16x16.vti -w examples/grid
./bin/DAMASK_grid -l tensionX.yaml -g 20grains16x16x16.vti -m material.yaml -w examples/grid
./bin/DAMASK_mesh -h
intel:
@ -129,7 +130,7 @@ jobs:
- name: PETSc - Prepare
run: |
tar -xf download/petsc-${PETSC_VERSION}.tar.gz -C .
sed -i "1718s/if not os.path.isfile(os.path.join(self.packageDir,'configure')):/if True:/g" \
sed -i "1719s/if not os.path.isfile(os.path.join(self.packageDir,'configure')):/if True:/g" \
./petsc-${PETSC_VERSION}/config/BuildSystem/config/package.py
export PETSC_DIR=${PWD}/petsc-${PETSC_VERSION}
export PETSC_ARCH=intel-${INTEL_V}
@ -165,6 +166,7 @@ jobs:
make all
- name: DAMASK - Compile
if: contains( matrix.intel_v, 'classic')
run: |
cmake -B build/grid -DDAMASK_SOLVER=grid -DCMAKE_INSTALL_PREFIX=${PWD}
cmake --build build/grid --parallel
@ -173,6 +175,19 @@ jobs:
cmake --build build/mesh --parallel
cmake --install build/mesh
# ifx has issue with openMP
# https://community.intel.com/t5/Intel-Fortran-Compiler/ifx-ICE-and-SEGFAULT/m-p/1459877
- name: DAMASK - Compile
if: contains( matrix.intel_v, 'llvm')
run: |
cmake -B build/grid -DDAMASK_SOLVER=grid -DCMAKE_INSTALL_PREFIX=${PWD} -DOPENMP=OFF
cmake --build build/grid --parallel
cmake --install build/grid
cmake -B build/mesh -DDAMASK_SOLVER=mesh -DCMAKE_INSTALL_PREFIX=${PWD} -DOPENMP=OFF
cmake --build build/mesh --parallel
cmake --install build/mesh
- name: DAMASK - Run
run: |
./bin/DAMASK_grid -l tensionX.yaml -g 20grains16x16x16.vti -w examples/grid
./bin/DAMASK_grid -l tensionX.yaml -g 20grains16x16x16.vti -m material.yaml -w examples/grid
./bin/DAMASK_mesh -h

View File

@ -9,7 +9,7 @@ jobs:
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10']
python-version: ['3.8', '3.9', '3.10', '3.11']
os: [ubuntu-latest, macos-latest, windows-latest]
fail-fast: false
@ -75,7 +75,7 @@ jobs:
run: >
sudo apt-get update &&
sudo apt-get remove mysql* &&
sudo apt-get install python3-pandas python3-scipy python3-h5py python3-vtk7 python3-matplotlib python3-yaml -y
sudo apt-get install python3-pandas python3-scipy python3-h5py python3-vtk9 python3-matplotlib python3-yaml -y
- name: Run unit tests
run: |

View File

@ -11,7 +11,7 @@ endif()
project(Prerequisites LANGUAGES)
set(ENV{PKG_CONFIG_PATH} "$ENV{PETSC_DIR}/$ENV{PETSC_ARCH}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")
pkg_check_modules(PETSC_MIN REQUIRED PETSc>=3.12.0 QUIET) #CMake does not support version range
pkg_check_modules(PETSC REQUIRED PETSc<3.19.0)
pkg_check_modules(PETSC REQUIRED PETSc<3.20.0)
pkg_get_variable(CMAKE_Fortran_COMPILER PETSc fcompiler)
pkg_get_variable(CMAKE_C_COMPILER PETSc ccompiler)
@ -22,6 +22,8 @@ if(DAMASK_SOLVER STREQUAL "GRID")
project(damask-grid HOMEPAGE_URL https://damask.mpie.de LANGUAGES Fortran C)
elseif(DAMASK_SOLVER STREQUAL "MESH")
project(damask-mesh HOMEPAGE_URL https://damask.mpie.de LANGUAGES Fortran C)
elseif(DAMASK_SOLVER STREQUAL "TEST")
project(damask-test HOMEPAGE_URL https://damask.mpie.de LANGUAGES Fortran C)
else()
message(FATAL_ERROR "Invalid solver: DAMASK_SOLVER=${DAMASK_SOLVER}")
endif()

View File

@ -17,6 +17,11 @@ mesh:
@cmake -B build/mesh -DDAMASK_SOLVER=mesh -DCMAKE_INSTALL_PREFIX=${PWD} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILDCMD_POST=${BUILDCMD_POST} -DBUILDCMD_PRE=${BUILDCMD_PRE} -DOPTIMIZATION=${OPTIMIZATION} -DOPENMP=${OPENMP}
@cmake --build build/mesh --parallel --target install
.PHONY: test
test:
@cmake -B build/test -DDAMASK_SOLVER=test -DCMAKE_INSTALL_PREFIX=${PWD} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILDCMD_POST=${BUILDCMD_POST} -DBUILDCMD_PRE=${BUILDCMD_PRE} -DOPTIMIZATION=${OPTIMIZATION} -DOPENMP=${OPENMP}
@cmake --build build/test --parallel --target install
.PHONY: clean
clean:
@rm -rf build

@ -1 +1 @@
Subproject commit fc04b9ef621161e60a2f8b72bfa8c99e77687c71
Subproject commit 9f4ffce8b2df951191a14dc3229de1aee6e544e6

View File

@ -1 +1 @@
3.0.0-alpha7-389-g4ae3274ac
3.0.0-alpha7-604-g65fa62b3f

View File

@ -93,8 +93,6 @@ set (DEBUG_FLAGS "${DEBUG_FLAGS},pointers")
# ... for certain disassociated or uninitialized pointers or unallocated allocatable objects.
set (DEBUG_FLAGS "${DEBUG_FLAGS},uninit")
# ... for uninitialized variables.
set (DEBUG_FLAGS "${DEBUG_FLAGS} -ftrapuv")
# ... initializes stack local variables to an unusual value to aid error detection
set (DEBUG_FLAGS "${DEBUG_FLAGS} -fpe-all=0 -ftz")
# ... capture all floating-point exceptions, need to overwrite -no-ftz

View File

@ -6,7 +6,7 @@ if (CMAKE_Fortran_COMPILER_VERSION VERSION_LESS 18.0)
endif ()
if (OPENMP)
set (OPENMP_FLAGS "-qopenmp")
set (OPENMP_FLAGS "-fiopenmp")
endif ()
if (OPTIMIZATION STREQUAL "OFF" OR OPTIMIZATION STREQUAL "DEBUG")
@ -23,6 +23,8 @@ endif ()
set (STANDARD_CHECK "-stand f18 -assume nostd_mod_proc_name")
set (LINKER_FLAGS "${LINKER_FLAGS} -shared-intel")
# Link against shared Intel libraries instead of static ones
set (LINKER_FLAGS "${LINKER_FLAGS} -shared-intel -fc=ifx")
# enforce use of ifx for MPI wrapper
#------------------------------------------------------------------------------------------------
# Fine tuning compilation options
@ -93,8 +95,6 @@ set (DEBUG_FLAGS "${DEBUG_FLAGS},pointers")
# ... for certain disassociated or uninitialized pointers or unallocated allocatable objects.
set (DEBUG_FLAGS "${DEBUG_FLAGS},uninit")
# ... for uninitialized variables.
set (DEBUG_FLAGS "${DEBUG_FLAGS} -ftrapuv")
# ... initializes stack local variables to an unusual value to aid error detection
set (DEBUG_FLAGS "${DEBUG_FLAGS} -fpe-all=0 -ftz")
# ... capture all floating-point exceptions, need to overwrite -no-ftz

View File

@ -1,12 +0,0 @@
phase: [basic, extensive, selective]
materialpoint: [basic, extensive, selective]
# options for selective debugging
element: 1
integrationpoint: 1
constituent: 1
# solver-specific
mesh: [PETSc]
grid: [basic, rotation, PETSc]
Marc: [basic]

View File

@ -6,8 +6,8 @@ N_cl: [3]
g_crit: [0.5e+7]
s_crit: [0.006666]
dot_o: 1.e-3
q: 20
dot_o_0: 1.e-3
p: 20
l_c: 1.0
mu: 0.001

View File

@ -3,7 +3,7 @@ type: Hooke
references:
- D.J. Dever,
Journal of Applied Physics 43(8):3293-3301, 1972,
https://doi.org/10.1063/1.1661710
https://doi.org/10.1063/1.1661710,
fit to Tab. II (T_min=25ºC, T_max=880ºC)
C_11: 232.2e+9

View File

@ -3,8 +3,9 @@ type: Hooke
references:
- S.A. Kim and W.L. Johnson,
Materials Science & Engineering A 452-453:633-639, 2007,
https://doi.org/10.1016/j.msea.2006.11.147
https://doi.org/10.1016/j.msea.2006.11.147,
Tab. 1 (averaged for bcc)
C_11: 268.1e+9
C_12: 111.2e+9
C_44: 79.06e+9
C_11: 267.9e+9
C_12: 110.8e+9
C_44: 78.9e+9

View File

@ -3,7 +3,7 @@ type: dislotungsten
references:
- D. Cereceda et al.,
International Journal of Plasticity 78:242-265, 2016,
http://dx.doi.org/10.1016/j.ijplas.2015.09.002
https://doi.org/10.1016/j.ijplas.2015.09.002
- R. Gröger et al.,
Acta Materialia 56(19):5412-5425, 2008,
https://doi.org/10.1016/j.actamat.2008.07.037

View File

@ -12,6 +12,7 @@ output: [rho_dip, rho_mob]
N_sl: [12, 12]
f_edge: [1.0, 1.0]
b_sl: [2.49e-10, 2.49e-10]
rho_mob_0: [2.81e+12, 2.8e+12]
rho_dip_0: [1.0, 1.0] # not given

View File

@ -15,6 +15,8 @@ output: [rho_mob, rho_dip, gamma_sl, Lambda_sl, tau_pass, f_tw, Lambda_tw, f_tr]
# Glide
N_sl: [12]
f_edge: [1.0]
b_sl: [2.56e-10] # a/sqrt(2)
Q_sl: [3.5e-19]
p_sl: [0.325]

View File

@ -0,0 +1,23 @@
type: kinehardening
references:
- J.A. Wollmershauser et al.,
International Journal of Fatigue 36(1):181-193, 2012,
https://doi.org/10.1016/j.ijfatigue.2011.07.008
output: [xi, chi, chi_flip, gamma_flip, gamma, sgn(gamma)]
N_sl: [12]
xi_0: [0.070e+9] # τ_0,for
xi_inf: [0.015e+9] # τ_1,for
h_0_xi: [0.065e+9] # θ_0,for
h_inf_xi: [0.045e+9] # θ_1,for
chi_inf: [0.027e+9] # τ_1,bs
h_0_chi: [55e+9] # θ_0,bs
h_inf_chi: [1.3e+9] # θ_1,bs
n: 20 # not mentioned in the reference
dot_gamma_0: 1e-4 # not mentioned in the reference
h_sl-sl: [1, 1, 1, 1, 1, 1, 1]

View File

@ -25,5 +25,6 @@ from ._colormap import Colormap # noqa
from ._vtk import VTK # noqa
from ._config import Config # noqa
from ._configmaterial import ConfigMaterial # noqa
from ._loadcasegrid import LoadcaseGrid # noqa
from ._grid import Grid # noqa
from ._result import Result # noqa

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
--------
@ -276,7 +276,7 @@ class Colormap(mpl.colors.ListedColormap):
def shade(self,
field: np.ndarray,
bounds: Optional[FloatSequence] = None,
gap: Optional[float] = None) -> Image:
gap: Optional[float] = None) -> Image.Image:
"""
Generate PIL image of 2D field using colormap.
@ -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
----------
@ -317,6 +317,11 @@ class Grid:
loaded : damask.Grid
Grid-based geometry from file.
Notes
-----
Material indices in Neper usually start at 1 unless
a buffer material with index 0 is added.
Examples
--------
Read a periodic polycrystal generated with Neper.
@ -325,7 +330,7 @@ class Grid:
>>> N_grains = 20
>>> cells = (32,32,32)
>>> damask.util.run(f'neper -T -n {N_grains} -tesrsize {cells[0]}:{cells[1]}:{cells[2]} -periodicity all -format vtk')
>>> damask.Grid.load_Neper(f'n{N_grains}-id1.vtk')
>>> damask.Grid.load_Neper(f'n{N_grains}-id1.vtk').renumber()
cells: 32 × 32 × 32
size: 1.0 × 1.0 × 1.0
origin: 0.0 0.0 0.0 m
@ -336,7 +341,7 @@ class Grid:
cells = np.array(v.vtk_data.GetDimensions())-1
bbox = np.array(v.vtk_data.GetBounds()).reshape(3,2).T
return Grid(material = v.get('MaterialId').reshape(cells,order='F').astype('int32',casting='unsafe') - 1,
return Grid(material = v.get('MaterialId').reshape(cells,order='F').astype('int32',casting='unsafe'),
size = bbox[1] - bbox[0],
origin = bbox[0],
comments = util.execution_stamp('Grid','load_Neper'),
@ -470,9 +475,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 +532,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 +613,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 +669,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 +700,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 +733,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 +777,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 +796,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 +843,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 +907,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 +943,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 +951,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 +996,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 +1010,7 @@ class Grid:
Examples
--------
Double resolution.
Double grid resolution.
>>> import numpy as np
>>> import damask
@ -965,8 +1020,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
@ -994,7 +1049,7 @@ class Grid:
Parameters
----------
idx : numpy.ndarray of int, shape (:,:,:) or (:,:,:,3)
Grid of flat indices or coordinate indices.
Grid of flat indices or coordinate indices.
Returns
-------
@ -1069,7 +1124,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 +1241,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 +1261,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 +1272,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

@ -0,0 +1,78 @@
from typing import Optional, Union, Dict, Any, List
from numpy import ma
import yaml
from ._typehints import FileHandle
from ._config import NiceDumper
from . import util
from . import Config
class MaskedMatrixDumper(NiceDumper):
"""Format masked matrices."""
def represent_data(self, data: Any):
return super().represent_data(data.astype(object).filled('x') if isinstance(data, ma.core.MaskedArray) else data) # type: ignore[attr-defined]
class LoadcaseGrid(Config):
"""Load case for grid solver."""
def __init__(self,
config: Optional[Union[str,Dict[str,Any]]] = None,
*,
solver: Optional[Dict[str,str]] = None,
loadstep: Optional[List[Dict[str,Any]]] = None):
"""
New grid solver load case.
Parameters
----------
config : dict or str, optional
Grid solver load case. String needs to be valid YAML.
solver : dict, optional
Solver configuration.
Defaults to an empty dict if 'config' is not given.
loadstep : list of dict, optional
Load step configuration.
Defaults to an empty list if 'config' is not given.
"""
kwargs: Dict[str,Union[Dict[str,str],List[Dict[str,Any]]]] = {}
default: Union[List,Dict]
for arg,value,default in [('solver',solver,{}),('loadstep',loadstep,[])]: # type: ignore[assignment]
if value is not None:
kwargs[arg] = value
elif config is None:
kwargs[arg] = default
super().__init__(config,**kwargs)
def save(self,
fname: FileHandle,
**kwargs):
"""
Save to YAML file.
Parameters
----------
fname : file, str, or pathlib.Path
Filename or file to write.
**kwargs : dict
Keyword arguments parsed to yaml.dump.
"""
for key,default in dict(width=256,
default_flow_style=None,
sort_keys=False).items():
if key not in kwargs:
kwargs[key] = default
fhandle = util.open_text(fname,'w')
try:
fhandle.write(yaml.dump(self,Dumper=MaskedMatrixDumper,**kwargs))
except TypeError: # compatibility with old pyyaml
del kwargs['sort_keys']
fhandle.write(yaml.dump(self,Dumper=MaskedMatrixDumper,**kwargs))

View File

@ -1,6 +1,5 @@
import inspect
import copy
from typing import Optional, Union, Callable, Dict, Any, Tuple, TypeVar
from typing import Optional, Union, TypeVar
import numpy as np
@ -10,31 +9,6 @@ from . import Crystal
from . import util
from . import tensor
_parameter_doc = \
"""
family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'}, optional.
Name of the crystal family.
Family will be inferred if 'lattice' is given.
lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}, optional.
Name of the Bravais lattice in Pearson notation.
a : float, optional
Length of lattice parameter 'a'.
b : float, optional
Length of lattice parameter 'b'.
c : float, optional
Length of lattice parameter 'c'.
alpha : float, optional
Angle between b and c lattice basis.
beta : float, optional
Angle between c and a lattice basis.
gamma : float, optional
Angle between a and b lattice basis.
degrees : bool, optional
Angles are given in degrees. Defaults to False.
"""
MyType = TypeVar('MyType', bound='Orientation')
class Orientation(Rotation,Crystal):
@ -94,7 +68,7 @@ class Orientation(Rotation,Crystal):
"""
@util.extend_docstring(extra_parameters=_parameter_doc)
@util.extend_docstring(adopted_parameters=Crystal.__init__)
def __init__(self,
rotation: Union[FloatSequence, Rotation] = np.array([1.,0.,0.,0.]),
*,
@ -108,7 +82,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 +97,7 @@ class Orientation(Rotation,Crystal):
"""
Return repr(self).
Give short human-readable summary.
Give short, human-readable summary.
"""
return util.srepr([Crystal.__repr__(self),
@ -261,134 +235,91 @@ class Orientation(Rotation,Crystal):
Compound rotation self*other, i.e. first other then self rotation.
"""
if isinstance(other, (Orientation,Rotation)):
return self.copy(Rotation(self.quaternion)*Rotation(other.quaternion))
else:
if not isinstance(other, (Orientation,Rotation)):
raise TypeError('use "O@b", i.e. matmul, to apply Orientation "O" to object "b"')
@staticmethod
def _split_kwargs(kwargs: Dict[str, Any],
target: Callable) -> Tuple[Dict[str, Any], ...]:
"""
Separate keyword arguments in 'kwargs' targeted at 'target' from general keyword arguments of Orientation objects.
Parameters
----------
kwargs : dictionary
Contains all **kwargs.
target: method
Function to scan for kwarg signature.
Returns
-------
rot_kwargs: dictionary
Valid keyword arguments of 'target' function of Rotation class.
ori_kwargs: dictionary
Valid keyword arguments of Orientation object.
"""
kws: Tuple[Dict[str, Any], ...] = ()
for t in (target,Orientation.__init__):
kws += ({key: kwargs[key] for key in set(inspect.signature(t).parameters) & set(kwargs)},)
invalid_keys = set(kwargs)-(set(kws[0])|set(kws[1]))
if invalid_keys:
raise TypeError(f"{inspect.stack()[1][3]}() got an unexpected keyword argument '{invalid_keys.pop()}'")
return kws
return self.copy(Rotation(self.quaternion)*Rotation(other.quaternion))
@classmethod
@util.extend_docstring(Rotation.from_random,
extra_parameters=_parameter_doc)
adopted_parameters=Crystal.__init__)
@util.pass_on('rotation', Rotation.from_random, wrapped=__init__)
def from_random(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_random)
return cls(rotation=Rotation.from_random(**kwargs_rot),**kwargs_ori)
return cls(**kwargs)
@classmethod
@util.extend_docstring(Rotation.from_quaternion,
extra_parameters=_parameter_doc)
adopted_parameters=Crystal.__init__)
@util.pass_on('rotation', Rotation.from_quaternion, wrapped=__init__)
def from_quaternion(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_quaternion)
return cls(rotation=Rotation.from_quaternion(**kwargs_rot),**kwargs_ori)
return cls(**kwargs)
@classmethod
@util.extend_docstring(Rotation.from_Euler_angles,
extra_parameters=_parameter_doc)
adopted_parameters=Crystal.__init__)
@util.pass_on('rotation', Rotation.from_Euler_angles, wrapped=__init__)
def from_Euler_angles(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_Euler_angles)
return cls(rotation=Rotation.from_Euler_angles(**kwargs_rot),**kwargs_ori)
return cls(**kwargs)
@classmethod
@util.extend_docstring(Rotation.from_axis_angle,
extra_parameters=_parameter_doc)
adopted_parameters=Crystal.__init__)
@util.pass_on('rotation', Rotation.from_axis_angle, wrapped=__init__)
def from_axis_angle(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_axis_angle)
return cls(rotation=Rotation.from_axis_angle(**kwargs_rot),**kwargs_ori)
return cls(**kwargs)
@classmethod
@util.extend_docstring(Rotation.from_basis,
extra_parameters=_parameter_doc)
adopted_parameters=Crystal.__init__)
@util.pass_on('rotation', Rotation.from_basis, wrapped=__init__)
def from_basis(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_basis)
return cls(rotation=Rotation.from_basis(**kwargs_rot),**kwargs_ori)
return cls(**kwargs)
@classmethod
@util.extend_docstring(Rotation.from_matrix,
extra_parameters=_parameter_doc)
adopted_parameters=Crystal.__init__)
@util.pass_on('rotation', Rotation.from_matrix, wrapped=__init__)
def from_matrix(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_matrix)
return cls(rotation=Rotation.from_matrix(**kwargs_rot),**kwargs_ori)
return cls(**kwargs)
@classmethod
@util.extend_docstring(Rotation.from_Rodrigues_vector,
extra_parameters=_parameter_doc)
adopted_parameters=Crystal.__init__)
@util.pass_on('rotation', Rotation.from_Rodrigues_vector, wrapped=__init__)
def from_Rodrigues_vector(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_Rodrigues_vector)
return cls(rotation=Rotation.from_Rodrigues_vector(**kwargs_rot),**kwargs_ori)
return cls(**kwargs)
@classmethod
@util.extend_docstring(Rotation.from_homochoric,
extra_parameters=_parameter_doc)
adopted_parameters=Crystal.__init__)
@util.pass_on('rotation', Rotation.from_homochoric, wrapped=__init__)
def from_homochoric(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_homochoric)
return cls(rotation=Rotation.from_homochoric(**kwargs_rot),**kwargs_ori)
return cls(**kwargs)
@classmethod
@util.extend_docstring(Rotation.from_cubochoric,
extra_parameters=_parameter_doc)
adopted_parameters=Crystal.__init__)
@util.pass_on('rotation', Rotation.from_cubochoric, wrapped=__init__)
def from_cubochoric(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_cubochoric)
return cls(rotation=Rotation.from_cubochoric(**kwargs_rot),**kwargs_ori)
return cls(**kwargs)
@classmethod
@util.extend_docstring(Rotation.from_spherical_component,
extra_parameters=_parameter_doc)
adopted_parameters=Crystal.__init__)
@util.pass_on('rotation', Rotation.from_spherical_component, wrapped=__init__)
def from_spherical_component(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_spherical_component)
return cls(rotation=Rotation.from_spherical_component(**kwargs_rot),**kwargs_ori)
return cls(**kwargs)
@classmethod
@util.extend_docstring(Rotation.from_fiber_component,
extra_parameters=_parameter_doc)
adopted_parameters=Crystal.__init__)
@util.pass_on('rotation', Rotation.from_fiber_component, wrapped=__init__)
def from_fiber_component(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_fiber_component)
return cls(rotation=Rotation.from_fiber_component(**kwargs_rot),**kwargs_ori)
return cls(**kwargs)
@classmethod
@util.extend_docstring(extra_parameters=_parameter_doc)
@util.extend_docstring(adopted_parameters=Crystal.__init__)
def from_directions(cls,
uvw: FloatSequence,
hkl: FloatSequence,
@ -467,24 +398,24 @@ 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}"')
raise TypeError(f'unknown symmetry "{self.family}"')
@property
@ -510,38 +441,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)
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 +511,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 +559,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 +829,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,8 +870,9 @@ class Orientation(Rotation,Crystal):
Returns
-------
Orientations related to self following the selected
model for the orientation relationship.
rel : Orientation, shape (:,self.shape)
Orientations related to self according to the selected
model for the orientation relationship.
Examples
--------

View File

@ -15,7 +15,7 @@ from typing import Optional, Union, Callable, Any, Sequence, Literal, Dict, List
import h5py
import numpy as np
import numpy.ma as ma
from numpy import ma
import damask
from . import VTK
@ -90,9 +90,9 @@ h5py._hl.attrs.AttributeManager = AttributeManagerNullterm # 'Monkey patch'
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.
@ -118,7 +118,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
----------
@ -131,10 +131,8 @@ class Result:
self.version_major = f.attrs['DADF5_version_major']
self.version_minor = f.attrs['DADF5_version_minor']
if (self.version_major != 0 or not 12 <= self.version_minor <= 14) and self.version_major != 1:
if (self.version_major != 0 or not 14 <= self.version_minor <= 14) and self.version_major != 1:
raise TypeError(f'unsupported DADF5 version "{self.version_major}.{self.version_minor}"')
if self.version_major == 0 and self.version_minor < 14:
self.export_simulation_setup = None # type: ignore
self.structured = 'cells' in f['geometry'].attrs.keys()
@ -192,7 +190,7 @@ class Result:
"""
Return repr(self).
Give short human-readable summary.
Give short, human-readable summary.
"""
with h5py.File(self.fname,'r') as f:
@ -220,7 +218,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
----------
@ -344,15 +342,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.
@ -400,15 +398,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
-------
@ -443,15 +441,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
-------
@ -703,7 +701,7 @@ class Result:
... '1/m²','total mobile dislocation density')
>>> r.add_calculation('np.sum(#rho_dip#,axis=1)','rho_dip_total',
... '1/m²','total dislocation dipole density')
>>> r.add_calculation('#rho_dip_total#+#rho_mob_total','rho_total',
>>> r.add_calculation('#rho_dip_total#+#rho_mob_total#','rho_total',
... '1/m²','total dislocation density')
Add Mises equivalent of the Cauchy stress without storage of
@ -746,9 +744,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})
@ -1048,14 +1048,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})
@ -1077,7 +1077,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
@ -1089,9 +1089,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.
"""
@ -1265,10 +1266,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})
@ -1327,7 +1329,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})
@ -1356,7 +1358,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})
@ -1386,7 +1388,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})
@ -1404,10 +1406,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.
@ -1487,10 +1489,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.
@ -1525,7 +1527,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
@ -1853,9 +1855,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
----------
@ -2068,7 +2071,8 @@ class Result:
def export_DADF5(self,
fname,
output: Union[str, List[str]] = '*'):
output: Union[str, List[str]] = '*',
mapping = None):
"""
Export visible components into a new DADF5 file.
@ -2082,20 +2086,61 @@ class Result:
output : (list of) str, optional
Names of the datasets to export.
Defaults to '*', in which case all visible datasets are exported.
mapping : numpy.ndarray of int, shape (:,:,:), optional
Indices for regridding.
"""
if Path(fname).expanduser().absolute() == self.fname:
raise PermissionError(f'cannot overwrite {self.fname}')
def cp(path_in,path_out,label,mapping):
if mapping is None:
path_in.copy(label,path_out)
else:
path_out.create_dataset(label,data=path_in[label][()][mapping])
path_out[label].attrs.update(path_in[label].attrs)
with h5py.File(self.fname,'r') as f_in, h5py.File(fname,'w') as f_out:
for k,v in f_in.attrs.items():
f_out.attrs.create(k,v)
for g in ['setup','geometry','cell_to']:
f_out.attrs.update(f_in.attrs)
for g in ['setup','geometry'] + (['cell_to'] if mapping is None else []):
f_in.copy(g,f_out)
if mapping is not None:
cells = mapping.shape
mapping_flat = mapping.flatten(order='F')
f_out['geometry'].attrs['cells'] = cells
f_out.create_group('cell_to') # ToDo: attribute missing
mappings = {'phase':{},'homogenization':{}} # type: ignore
mapping_phase = f_in['cell_to']['phase'][()][mapping_flat]
for p in np.unique(mapping_phase['label']):
m = mapping_phase['label'] == p
mappings['phase'][p] = mapping_phase[m]['entry']
c = np.count_nonzero(m)
mapping_phase[m] = list(zip((p,)*c,tuple(np.arange(c))))
f_out['cell_to'].create_dataset('phase',data=mapping_phase.reshape(np.prod(mapping_flat.shape),-1))
mapping_homog = f_in['cell_to']['homogenization'][()][mapping]
for h in np.unique(mapping_homog['label']):
m = mapping_homog['label'] == h
mappings['homogenization'][h] = mapping_homog[m]['entry']
c = np.count_nonzero(m)
mapping_homog[mapping_homog['label'] == h] = list(zip((h,)*c,tuple(np.arange(c))))
f_out['cell_to'].create_dataset('homogenization',data=mapping_homog.flatten())
for inc in util.show_progress(self.visible['increments']):
f_in.copy(inc,f_out,shallow=True)
for out in _match(output,f_in['/'.join([inc,'geometry'])].keys()):
f_in[inc]['geometry'].copy(out,f_out[inc]['geometry'])
if mapping is None:
for label in ['u_p','u_n']:
f_in[inc]['geometry'].copy(label,f_out[inc]['geometry'])
else:
u_p = f_in[inc]['geometry']['u_p'][()][mapping_flat]
f_out[inc]['geometry'].create_dataset('u_p',data=u_p)
u_n = np.zeros((len(mapping_flat),3)) # ToDo: needs implementation
f_out[inc]['geometry'].create_dataset('u_n',data=u_n)
for label in self.homogenizations:
f_in[inc]['homogenization'].copy(label,f_out[inc]['homogenization'],shallow=True)
@ -2107,7 +2152,7 @@ class Result:
for field in _match(self.visible['fields'],f_in['/'.join([inc,ty,label])].keys()):
p = '/'.join([inc,ty,label,field])
for out in _match(output,f_in[p].keys()):
f_in[p].copy(out,f_out[p])
cp(f_in[p],f_out[p],out,None if mapping is None else mappings[ty][label.encode()])
def export_simulation_setup(self,

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
-------
@ -299,11 +299,18 @@ class Table:
@staticmethod
def load_ang(fname: FileHandle) -> 'Table':
def load_ang(fname: FileHandle,
shapes = {'eu':3,
'pos':2,
'IQ':1,
'CI':1,
'ID':1,
'intensity':1,
'fit':1}) -> 'Table':
"""
Load from ang file.
Load from ANG file.
A valid TSL ang file has to have the following columns:
Regular ANG files feature the following columns:
- Euler angles (Bunge notation) in radians, 3 floats, label 'eu'.
- Spatial position in meters, 2 floats, label 'pos'.
@ -316,7 +323,10 @@ class Table:
Parameters
----------
fname : file, str, or pathlib.Path
Filename or file for reading.
Filename or file to read.
shapes : dict with str:int pairs, optional
Column labels and their width.
Defaults to standard TSL ANG format.
Returns
-------
@ -338,7 +348,6 @@ class Table:
data = np.loadtxt(content)
shapes = {'eu':3, 'pos':2, 'IQ':1, 'CI':1, 'ID':1, 'intensity':1, 'fit':1}
if (remainder := data.shape[1]-sum(shapes.values())) > 0:
shapes['unknown'] = remainder
@ -458,9 +467,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 +497,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 +583,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.
@ -594,4 +603,7 @@ class Table:
f = util.open_text(fname,'w')
f.write('\n'.join([f'# {c}' for c in self.comments] + [' '.join(labels)])+('\n' if labels else ''))
self.data.to_csv(f,sep=' ',na_rep='nan',index=False,header=False,line_terminator='\n')
try: # backward compatibility
self.data.to_csv(f,sep=' ',na_rep='nan',index=False,header=False,lineterminator='\n')
except TypeError:
self.data.to_csv(f,sep=' ',na_rep='nan',index=False,header=False,line_terminator='\n')

View File

@ -4,10 +4,54 @@ from pathlib import Path
from typing import Optional, Union, Literal, List, Sequence
import numpy as np
import vtk
from vtk.util.numpy_support import numpy_to_vtk as np_to_vtk
from vtk.util.numpy_support import numpy_to_vtkIdTypeArray as np_to_vtkIdTypeArray
from vtk.util.numpy_support import vtk_to_numpy as vtk_to_np
from vtkmodules.vtkCommonCore import (
vtkPoints,
vtkStringArray,
vtkLookupTable,
)
from vtkmodules.vtkCommonDataModel import (
vtkDataSet,
vtkCellArray,
vtkImageData,
vtkRectilinearGrid,
vtkUnstructuredGrid,
vtkPolyData,
)
from vtkmodules.vtkIOLegacy import (
vtkGenericDataObjectReader,
vtkDataSetWriter,
)
from vtkmodules.vtkIOXML import (
vtkXMLImageDataReader,
vtkXMLImageDataWriter,
vtkXMLRectilinearGridReader,
vtkXMLRectilinearGridWriter,
vtkXMLUnstructuredGridReader,
vtkXMLUnstructuredGridWriter,
vtkXMLPolyDataReader,
vtkXMLPolyDataWriter,
)
from vtkmodules.vtkRenderingCore import (
vtkDataSetMapper,
vtkActor,
vtkRenderer,
vtkRenderWindow,
vtkRenderWindowInteractor,
)
from vtkmodules.vtkRenderingAnnotation import (
vtkScalarBarActor,
)
from vtkmodules.util.vtkConstants import (
VTK_TRIANGLE,
VTK_QUAD,
VTK_TETRA,
VTK_HEXAHEDRON,
)
from vtkmodules.util.numpy_support import (
numpy_to_vtk,
numpy_to_vtkIdTypeArray,
vtk_to_numpy,
)
from ._typehints import FloatSequence, IntSequence
from . import util
@ -23,16 +67,16 @@ class VTK:
"""
def __init__(self,
vtk_data: vtk.vtkDataSet):
vtk_data: vtkDataSet):
"""
New spatial visualization.
Parameters
----------
vtk_data : subclass of vtk.vtkDataSet
vtk_data : subclass of vtkDataSet
Description of geometry and topology, optionally with attached data.
Valid types are vtk.vtkImageData, vtk.vtkUnstructuredGrid,
vtk.vtkPolyData, and vtk.vtkRectilinearGrid.
Valid types are vtkImageData, vtkUnstructuredGrid,
vtkPolyData, and vtkRectilinearGrid.
"""
self.vtk_data = vtk_data
@ -42,7 +86,7 @@ class VTK:
"""
Return repr(self).
Give short human-readable summary.
Give short, human-readable summary.
"""
info = [self.vtk_data.__vtkname__]
@ -76,14 +120,14 @@ class VTK:
def copy(self):
if isinstance(self.vtk_data,vtk.vtkImageData):
dup = vtk.vtkImageData()
elif isinstance(self.vtk_data,vtk.vtkUnstructuredGrid):
dup = vtk.vtkUnstructuredGrid()
elif isinstance(self.vtk_data,vtk.vtkPolyData):
dup = vtk.vtkPolyData()
elif isinstance(self.vtk_data,vtk.vtkRectilinearGrid):
dup = vtk.vtkRectilinearGrid()
if isinstance(self.vtk_data,vtkImageData):
dup = vtkImageData()
elif isinstance(self.vtk_data,vtkUnstructuredGrid):
dup = vtkUnstructuredGrid()
elif isinstance(self.vtk_data,vtkPolyData):
dup = vtkPolyData()
elif isinstance(self.vtk_data,vtkRectilinearGrid):
dup = vtkRectilinearGrid()
else:
raise TypeError
@ -114,7 +158,7 @@ class VTK:
Comments.
"""
s = vtk.vtkStringArray()
s = vtkStringArray()
s.SetName('comments')
for c in comments:
s.InsertNextValue(c)
@ -154,7 +198,7 @@ class VTK:
size: FloatSequence,
origin: FloatSequence = np.zeros(3)) -> 'VTK':
"""
Create VTK of type vtk.vtkImageData.
Create VTK of type vtkImageData.
This is the common type for grid solver results.
@ -163,7 +207,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.
@ -173,7 +217,7 @@ class VTK:
VTK-based geometry without nodal or cell data.
"""
vtk_data = vtk.vtkImageData()
vtk_data = vtkImageData()
vtk_data.SetDimensions(*(np.array(cells)+1))
vtk_data.SetOrigin(*(np.array(origin)))
vtk_data.SetSpacing(*(np.array(size)/np.array(cells)))
@ -186,7 +230,7 @@ class VTK:
connectivity: np.ndarray,
cell_type: str) -> 'VTK':
"""
Create VTK of type vtk.vtkUnstructuredGrid.
Create VTK of type vtkUnstructuredGrid.
This is the common type for mesh solver results.
@ -198,7 +242,7 @@ class VTK:
Cell connectivity (0-based), first dimension determines #Cells,
second dimension determines #Nodes/Cell.
cell_type : str
Name of the vtk.vtkCell subclass. Tested for TRIANGLE, QUAD, TETRA, and HEXAHEDRON.
Name of the vtkCell subclass. Tested for TRIANGLE, QUAD, TETRA, and HEXAHEDRON.
Returns
-------
@ -206,18 +250,18 @@ class VTK:
VTK-based geometry without nodal or cell data.
"""
vtk_nodes = vtk.vtkPoints()
vtk_nodes.SetData(np_to_vtk(np.ascontiguousarray(nodes)))
cells = vtk.vtkCellArray()
vtk_nodes = vtkPoints()
vtk_nodes.SetData(numpy_to_vtk(np.ascontiguousarray(nodes)))
cells = vtkCellArray()
cells.SetNumberOfCells(connectivity.shape[0])
T = np.concatenate((np.ones((connectivity.shape[0],1),dtype=np.int64)*connectivity.shape[1],
connectivity),axis=1).ravel()
cells.SetCells(connectivity.shape[0],np_to_vtkIdTypeArray(T,deep=True))
cells.SetCells(connectivity.shape[0],numpy_to_vtkIdTypeArray(T,deep=True))
vtk_data = vtk.vtkUnstructuredGrid()
vtk_data = vtkUnstructuredGrid()
vtk_data.SetPoints(vtk_nodes)
cell_types = {'TRIANGLE':vtk.VTK_TRIANGLE, 'QUAD':vtk.VTK_QUAD,
'TETRA' :vtk.VTK_TETRA, 'HEXAHEDRON':vtk.VTK_HEXAHEDRON}
cell_types = {'TRIANGLE':VTK_TRIANGLE, 'QUAD':VTK_QUAD,
'TETRA' :VTK_TETRA, 'HEXAHEDRON':VTK_HEXAHEDRON}
vtk_data.SetCells(cell_types[cell_type.split("_",1)[-1].upper()],cells)
return VTK(vtk_data)
@ -226,7 +270,7 @@ class VTK:
@staticmethod
def from_poly_data(points: np.ndarray) -> 'VTK':
"""
Create VTK of type vtk.polyData.
Create VTK of type polyData.
This is the common type for point-wise data.
@ -242,15 +286,15 @@ class VTK:
"""
N = points.shape[0]
vtk_points = vtk.vtkPoints()
vtk_points.SetData(np_to_vtk(np.ascontiguousarray(points)))
vtk_points = vtkPoints()
vtk_points.SetData(numpy_to_vtk(np.ascontiguousarray(points)))
vtk_cells = vtk.vtkCellArray()
vtk_cells = vtkCellArray()
vtk_cells.SetNumberOfCells(N)
vtk_cells.SetCells(N,np_to_vtkIdTypeArray(np.stack((np.ones (N,dtype=np.int64),
vtk_cells.SetCells(N,numpy_to_vtkIdTypeArray(np.stack((np.ones (N,dtype=np.int64),
np.arange(N,dtype=np.int64)),axis=1).ravel(),deep=True))
vtk_data = vtk.vtkPolyData()
vtk_data = vtkPolyData()
vtk_data.SetPoints(vtk_points)
vtk_data.SetVerts(vtk_cells)
@ -260,7 +304,7 @@ class VTK:
@staticmethod
def from_rectilinear_grid(grid: FloatSequence) -> 'VTK':
"""
Create VTK of type vtk.vtkRectilinearGrid.
Create VTK of type vtkRectilinearGrid.
Parameters
----------
@ -273,9 +317,9 @@ class VTK:
VTK-based geometry without nodal or cell data.
"""
vtk_data = vtk.vtkRectilinearGrid()
vtk_data = vtkRectilinearGrid()
vtk_data.SetDimensions(*map(len,grid))
coord = [np_to_vtk(np.array(grid[i]),deep=True) for i in [0,1,2]]
coord = [numpy_to_vtk(np.array(grid[i]),deep=True) for i in [0,1,2]]
[coord[i].SetName(n) for i,n in enumerate(['x','y','z'])]
vtk_data.SetXCoordinates(coord[0])
vtk_data.SetYCoordinates(coord[1])
@ -293,10 +337,10 @@ 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.
Name of the vtkDataSet subclass when opening a .vtk file.
Returns
-------
@ -307,7 +351,7 @@ class VTK:
if not Path(fname).expanduser().is_file(): # vtk has a strange error handling
raise FileNotFoundError(f'file "{fname}" not found')
if (ext := Path(fname).suffix) == '.vtk' or dataset_type is not None:
reader = vtk.vtkGenericDataObjectReader()
reader = vtkGenericDataObjectReader()
reader.SetFileName(str(Path(fname).expanduser()))
if dataset_type is None:
raise TypeError('dataset type for *.vtk file not given')
@ -327,13 +371,13 @@ class VTK:
raise TypeError(f'unknown dataset type "{dataset_type}" for vtk file')
else:
if ext == '.vti':
reader = vtk.vtkXMLImageDataReader()
reader = vtkXMLImageDataReader()
elif ext == '.vtu':
reader = vtk.vtkXMLUnstructuredGridReader()
reader = vtkXMLUnstructuredGridReader()
elif ext == '.vtp':
reader = vtk.vtkXMLPolyDataReader()
reader = vtkXMLPolyDataReader()
elif ext == '.vtr':
reader = vtk.vtkXMLRectilinearGridReader()
reader = vtkXMLRectilinearGridReader()
else:
raise TypeError(f'unknown file extension "{ext}"')
@ -352,7 +396,7 @@ class VTK:
def as_ASCII(self) -> str:
"""ASCII representation of the VTK data."""
writer = vtk.vtkDataSetWriter()
writer = vtkDataSetWriter()
writer.SetHeader(f'# {util.execution_stamp("VTK")}')
writer.WriteToOutputStringOn()
writer.SetInputData(self.vtk_data)
@ -370,21 +414,21 @@ 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
Compress with zlib algorithm. Defaults to True.
"""
if isinstance(self.vtk_data,vtk.vtkImageData):
writer = vtk.vtkXMLImageDataWriter()
elif isinstance(self.vtk_data,vtk.vtkUnstructuredGrid):
writer = vtk.vtkXMLUnstructuredGridWriter()
elif isinstance(self.vtk_data,vtk.vtkPolyData):
writer = vtk.vtkXMLPolyDataWriter()
elif isinstance(self.vtk_data,vtk.vtkRectilinearGrid):
writer = vtk.vtkXMLRectilinearGridWriter()
if isinstance(self.vtk_data,vtkImageData):
writer = vtkXMLImageDataWriter()
elif isinstance(self.vtk_data,vtkUnstructuredGrid):
writer = vtkXMLUnstructuredGridWriter()
elif isinstance(self.vtk_data,vtkPolyData):
writer = vtkXMLPolyDataWriter()
elif isinstance(self.vtk_data,vtkRectilinearGrid):
writer = vtkXMLRectilinearGridWriter()
default_ext = '.'+writer.GetDefaultFileExtension()
ext = Path(fname).suffix
@ -433,6 +477,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.
@ -451,11 +500,11 @@ class VTK:
.astype(np.single if data.dtype in [np.double,np.longdouble] else data.dtype)
if data.dtype.type is np.str_:
d = vtk.vtkStringArray()
d = vtkStringArray()
for s in np.squeeze(data_):
d.InsertNextValue(s)
else:
d = np_to_vtk(data_,deep=True)
d = numpy_to_vtk(data_,deep=True)
d.SetName(label)
@ -513,7 +562,7 @@ class VTK:
for a in range(cell_data.GetNumberOfArrays()):
if cell_data.GetArrayName(a) == label:
try:
return vtk_to_np(cell_data.GetArray(a))
return vtk_to_numpy(cell_data.GetArray(a))
except AttributeError:
vtk_array = cell_data.GetAbstractArray(a) # string array
@ -521,7 +570,7 @@ class VTK:
for a in range(point_data.GetNumberOfArrays()):
if point_data.GetArrayName(a) == label:
try:
return vtk_to_np(point_data.GetArray(a))
return vtk_to_numpy(point_data.GetArray(a))
except AttributeError:
vtk_array = point_data.GetAbstractArray(a) # string array
@ -548,7 +597,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.
@ -567,7 +616,7 @@ class VTK:
width = 1024
height = 768
lut = vtk.vtkLookupTable()
lut = vtkLookupTable()
colormap_ = Colormap.from_predefined(colormap) if isinstance(colormap,str) else \
colormap
lut.SetNumberOfTableValues(len(colormap_.colors))
@ -576,33 +625,33 @@ class VTK:
lut.Build()
self.vtk_data.GetCellData().SetActiveScalars(label)
mapper = vtk.vtkDataSetMapper()
mapper = vtkDataSetMapper()
mapper.SetInputData(self.vtk_data)
mapper.SetLookupTable(lut)
mapper.SetScalarRange(self.vtk_data.GetScalarRange())
actor = vtk.vtkActor()
actor = vtkActor()
actor.SetMapper(mapper)
actor.GetProperty().SetColor(230/255,150/255,68/255)
ren = vtk.vtkRenderer()
ren = vtkRenderer()
ren.AddActor(actor)
if label is None:
ren.SetBackground(67/255,128/255,208/255)
else:
colormap_vtk = vtk.vtkScalarBarActor()
colormap_vtk = vtkScalarBarActor()
colormap_vtk.SetLookupTable(lut)
colormap_vtk.SetTitle(label)
colormap_vtk.SetMaximumWidthInPixels(width//100)
ren.AddActor2D(colormap_vtk)
ren.SetBackground(0.3,0.3,0.3)
window = vtk.vtkRenderWindow()
window = vtkRenderWindow()
window.AddRenderer(ren)
window.SetSize(width,height)
window.SetWindowName(util.execution_stamp('VTK','show'))
iren = vtk.vtkRenderWindowInteractor()
iren = vtkRenderWindowInteractor()
iren.SetRenderWindow(window)
if os.name == 'posix' and 'DISPLAY' not in os.environ:
print('Found no rendering device')

View File

@ -186,8 +186,6 @@ def displacement_fluct_point(size: _FloatSequence,
Fluctuating part of the cell center displacements.
"""
integrator = 0.5j*_np.array(size,float)/_np.pi
k_s = _ks(size,F.shape[:3],False)
k_s_squared = _np.einsum('...l,...l',k_s,k_s)
k_s_squared[0,0,0] = 1.0
@ -195,8 +193,8 @@ def displacement_fluct_point(size: _FloatSequence,
displacement = -_np.einsum('ijkml,ijkl,l->ijkm',
_np.fft.rfftn(F,axes=(0,1,2)),
k_s,
integrator,
) / k_s_squared[...,_np.newaxis]
_np.array([0.5j/_np.pi]*3),
) / k_s_squared[...,_np.newaxis]
return _np.fft.irfftn(displacement,axes=(0,1,2),s=F.shape[:3])
@ -402,7 +400,7 @@ def displacement_node(size: _FloatSequence,
Returns
-------
u_p : numpy.ndarray, shape (:,:,:,3)
u_n : numpy.ndarray, shape (:,:,:,3)
Nodal displacements.
"""
@ -564,17 +562,10 @@ def unravel_index(idx: _np.ndarray) -> _np.ndarray:
>>> seq = np.arange(6).reshape((3,2,1),order='F')
>>> (coord_idx := damask.grid_filters.unravel_index(seq))
array([[[[0, 0, 0]],
[[0, 1, 0]]],
[[[1, 0, 0]],
[[1, 1, 0]]],
[[[2, 0, 0]],
[[2, 1, 0]]]])
>>> coord_idx[1,1,0]
array([1, 1, 0])
@ -608,17 +599,12 @@ def ravel_index(idx: _np.ndarray) -> _np.ndarray:
>>> import damask
>>> (rev := np.array([[1,1,0],[0,1,0],[1,0,0],[0,0,0]]).reshape((2,2,1,3)))
array([[[[1, 1, 0]],
[[0, 1, 0]]],
[[[1, 0, 0]],
[[0, 0, 0]]]])
>>> (flat_idx := damask.grid_filters.ravel_index(rev))
array([[[3],
[2]],
[[1],
[0]]])

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
@ -14,7 +14,7 @@ from . import _rotation
def deformation_Cauchy_Green_left(F: _np.ndarray) -> _np.ndarray:
"""
r"""
Calculate left Cauchy-Green deformation tensor (Finger deformation tensor).
Parameters
@ -27,12 +27,18 @@ def deformation_Cauchy_Green_left(F: _np.ndarray) -> _np.ndarray:
B : numpy.ndarray, shape (...,3,3)
Left Cauchy-Green deformation tensor.
Notes
-----
.. math::
\vb{B} = \vb{F} \vb{F}^\text{T}
"""
return _np.matmul(F,_tensor.transpose(F))
def deformation_Cauchy_Green_right(F: _np.ndarray) -> _np.ndarray:
"""
r"""
Calculate right Cauchy-Green deformation tensor.
Parameters
@ -45,12 +51,18 @@ def deformation_Cauchy_Green_right(F: _np.ndarray) -> _np.ndarray:
C : numpy.ndarray, shape (...,3,3)
Right Cauchy-Green deformation tensor.
Notes
-----
.. math::
\vb{C} = \vb{F}^\text{T} \vb{F}
"""
return _np.matmul(_tensor.transpose(F),F)
def equivalent_strain_Mises(epsilon: _np.ndarray) -> _np.ndarray:
"""
r"""
Calculate the Mises equivalent of a strain tensor.
Parameters
@ -63,12 +75,23 @@ def equivalent_strain_Mises(epsilon: _np.ndarray) -> _np.ndarray:
epsilon_vM : numpy.ndarray, shape (...)
Von Mises equivalent strain of epsilon.
Notes
-----
The von Mises equivalent of a strain tensor is defined as:
.. math::
\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.
"""
return _equivalent_Mises(epsilon,2.0/3.0)
def equivalent_stress_Mises(sigma: _np.ndarray) -> _np.ndarray:
"""
r"""
Calculate the Mises equivalent of a stress tensor.
Parameters
@ -81,6 +104,17 @@ def equivalent_stress_Mises(sigma: _np.ndarray) -> _np.ndarray:
sigma_vM : numpy.ndarray, shape (...)
Von Mises equivalent stress of sigma.
Notes
-----
The von Mises equivalent of a stress tensor is defined as:
.. math::
\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.
"""
return _equivalent_Mises(sigma,3.0/2.0)
@ -105,7 +139,7 @@ def maximum_shear(T_sym: _np.ndarray) -> _np.ndarray:
def rotation(T: _np.ndarray) -> _rotation.Rotation:
"""
r"""
Calculate the rotational part of a tensor.
Parameters
@ -118,23 +152,35 @@ def rotation(T: _np.ndarray) -> _rotation.Rotation:
R : damask.Rotation, shape (...)
Rotational part of the vector.
Notes
-----
The rotational part is calculated from the polar decomposition:
.. math::
\vb{R} = \vb{T} \vb{U}^{-1} = \vb{V}^{-1} \vb{T}
where :math:`\vb{V}` and :math:`\vb{U}` are the left
and right stretch tensor, respectively.
"""
return _rotation.Rotation.from_matrix(_polar_decomposition(T,'R')[0])
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
----------
F : numpy.ndarray, shape (...,3,3)
Deformation gradient.
t : {V, U}
Type of the polar decomposition, V for left stretch tensor
and U for right stretch tensor.
t : {'V', 'U'}
Type of the polar decomposition, 'V' for left stretch tensor
or 'U' for right stretch tensor.
m : float
Order of the strain.
@ -143,25 +189,25 @@ 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 == 'V':
w,n = _np.linalg.eigh(deformation_Cauchy_Green_left(F))
elif t == 'U':
w,n = _np.linalg.eigh(deformation_Cauchy_Green_right(F))
if m > 0.0:
eps = 1.0/(2.0*abs(m)) * (+ _np.einsum('...j,...kj,...lj',w**m,n,n) - _np.eye(3))
elif m < 0.0:
eps = 1.0/(2.0*abs(m)) * (- _np.einsum('...j,...kj,...lj',w**m,n,n) + _np.eye(3))
else:
eps = _np.einsum('...j,...kj,...lj',0.5*_np.log(w),n,n)
return eps
if t not in ['V', 'U']: raise ValueError('polar decomposition type not in {V, U}')
w,n = _np.linalg.eigh(deformation_Cauchy_Green_left(F) if t=='V' else deformation_Cauchy_Green_right(F))
return 0.5 * _np.einsum('...j,...kj,...lj',_np.log(w),n,n) if m == 0.0 \
else 0.5/m * (_np.einsum('...j,...kj,...lj', w**m,n,n) - _np.eye(3))
def stress_Cauchy(P: _np.ndarray,
@ -212,7 +258,7 @@ def stress_second_Piola_Kirchhoff(P: _np.ndarray,
def stretch_left(T: _np.ndarray) -> _np.ndarray:
"""
r"""
Calculate left stretch of a tensor.
Parameters
@ -225,12 +271,23 @@ def stretch_left(T: _np.ndarray) -> _np.ndarray:
V : numpy.ndarray, shape (...,3,3)
Left stretch tensor from Polar decomposition of T.
Notes
-----
The left stretch tensor is calculated from the
polar decomposition:
.. math::
\vb{V} = \vb{T} \vb{R}^\text{T}
where :math:`\vb{R}` is a rotation.
"""
return _polar_decomposition(T,'V')[0]
def stretch_right(T: _np.ndarray) -> _np.ndarray:
"""
r"""
Calculate right stretch of a tensor.
Parameters
@ -243,6 +300,17 @@ def stretch_right(T: _np.ndarray) -> _np.ndarray:
U : numpy.ndarray, shape (...,3,3)
Left stretch tensor from Polar decomposition of T.
Notes
-----
The right stretch tensor is calculated from the
polar decomposition:
.. math::
\vb{U} = \vb{R}^\text{T} \vb{T}
where :math:`\vb{R}` is a rotation.
"""
return _polar_decomposition(T,'U')[0]
@ -257,8 +325,13 @@ 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
-------
VRU : tuple of numpy.ndarray, shape (...,3,3)
Requested components of the singular value decomposition.
"""
u, _, vh = _np.linalg.svd(T)
@ -290,6 +363,11 @@ def _equivalent_Mises(T_sym: _np.ndarray,
s : float
Scaling factor (2/3 for strain, 3/2 for stress).
Returns
-------
eq : numpy.ndarray, shape (...)
Scaled second invariant of the deviatoric part of T_sym.
"""
d = _tensor.deviatoric(T_sym)
return _np.sqrt(s*_np.sum(d**2.0,axis=(-1,-2)))

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

@ -8,8 +8,9 @@ import shlex as _shlex
import re as _re
import signal as _signal
import fractions as _fractions
from collections import abc as _abc
from functools import reduce as _reduce, partial as _partial
from collections import abc as _abc, OrderedDict as _OrderedDict
from functools import reduce as _reduce, partial as _partial, wraps as _wraps
import inspect
from typing import Optional as _Optional, Callable as _Callable, Union as _Union, Iterable as _Iterable, \
Dict as _Dict, List as _List, Tuple as _Tuple, Literal as _Literal, \
Any as _Any, TextIO as _TextIO
@ -540,10 +541,11 @@ def shapeblender(a: _Tuple[int, ...],
def _docstringer(docstring: _Union[str, _Callable],
extra_parameters: _Optional[str] = None,
# extra_examples: _Optional[str] = None,
# extra_notes: _Optional[str] = None,
return_type: _Union[None, str, _Callable] = None) -> str:
adopted_parameters: _Union[None, str, _Callable] = None,
adopted_return: _Union[None, str, _Callable] = None,
adopted_notes: _Union[None, str, _Callable] = None,
adopted_examples: _Union[None, str, _Callable] = None,
adopted_references: _Union[None, str, _Callable] = None) -> str:
"""
Extend a docstring.
@ -551,50 +553,85 @@ def _docstringer(docstring: _Union[str, _Callable],
----------
docstring : str or callable, optional
Docstring (of callable) to extend.
extra_parameters : str, optional
Additional information to append to Parameters section.
return_type : str or callable, optional
Type of return variable.
adopted_* : str or callable, optional
Additional information to insert into/append to respective section.
Notes
-----
adopted_return fetches the typehint of a passed function instead of the docstring
"""
docstring_ = str( docstring if isinstance(docstring,str)
else docstring.__doc__ if hasattr(docstring,'__doc__')
else '')
d = dict(Parameters=extra_parameters,
# Examples=extra_examples,
# Notes=extra_notes,
)
for key,extra in [(k,v) for (k,v) in d.items() if v is not None]:
if not (heading := _re.search(fr'^([ ]*){key}\s*\n\1{"-"*len(key)}',
docstring_,flags=_re.MULTILINE)):
raise RuntimeError(f"Docstring {docstring_} lacks a correctly formatted {key} section to insert values into")
content = [line for line in extra.split('\n') if line.strip()]
indent = len(heading.group(1))
shift = min([len(line)-len(line.lstrip(' '))-indent for line in content])
extra = '\n'.join([(line[shift:] if shift > 0 else
f'{" "*-shift}{line}') for line in content])
docstring_ = _re.sub(fr'(^([ ]*){key}\s*\n\2{"-"*len(key)}[\n ]*[A-Za-z0-9_ ]*: ([^\n]+\n)*)',
fr'\1{extra}\n',
docstring_,flags=_re.MULTILINE)
docstring_: str = str( docstring if isinstance(docstring,str)
else docstring.__doc__ if callable(docstring) and docstring.__doc__
else '').rstrip()+'\n'
sections = _OrderedDict(
Parameters=adopted_parameters,
Returns=adopted_return,
Examples=adopted_examples,
Notes=adopted_notes,
References=adopted_references)
if return_type is None:
return docstring_
else:
if isinstance(return_type,str):
return_type_ = return_type
for i, (key, adopted) in [(i,(k,v)) for (i,(k,v)) in enumerate(sections.items()) if v is not None]:
section_regex = fr'^([ ]*){key}\s*\n\1*{"-"*len(key)}\s*\n'
if key=='Returns':
if callable(adopted):
return_class = adopted.__annotations__.get('return','')
return_type_ = (_sys.modules[adopted.__module__].__name__.split('.')[0]
+'.'
+(return_class.__name__ if not isinstance(return_class,str) else return_class))
else:
return_type_ = adopted
docstring_ = _re.sub(fr'(^[ ]*{key}\s*\n\s*{"-"*len(key)}\s*\n[ ]*[A-Za-z0-9_ ]*: )(.*)\n',
fr'\1{return_type_}\n',
docstring_,flags=_re.MULTILINE)
else:
return_class = return_type.__annotations__.get('return','')
return_type_ = (_sys.modules[return_type.__module__].__name__.split('.')[0]
+'.'
+(return_class.__name__ if not isinstance(return_class,str) else return_class)
)
section_content_regex = fr'{section_regex}(?P<content>.*?)\n *(\n|\Z)'
adopted_: str = adopted.__doc__ if callable(adopted) else adopted #type: ignore
try:
if _re.search(fr'{section_regex}', adopted_, flags=_re.MULTILINE):
adopted_ = _re.search(section_content_regex, #type: ignore
adopted_,
flags=_re.MULTILINE|_re.DOTALL).group('content')
except AttributeError:
raise RuntimeError(f"Function docstring passed for docstring section '{key}' is invalid:\n{docstring}")
docstring_indent, adopted_indent = (min([len(line)-len(line.lstrip()) for line in section.split('\n') if line.strip()])
for section in [docstring_, adopted_])
shift = adopted_indent - docstring_indent
adopted_content = '\n'.join([(line[shift:] if shift > 0 else
f'{" "*-shift}{line}') for line in adopted_.split('\n') if line.strip()])
if _re.search(section_regex, docstring_, flags=_re.MULTILINE):
docstring_section_content = _re.search(section_content_regex, # type: ignore
docstring_,
flags=_re.MULTILINE|_re.DOTALL).group('content')
a_items, d_items = (_re.findall('^[ ]*([A-Za-z0-9_ ]*?)[ ]*:',content,flags=_re.MULTILINE)
for content in [adopted_content,docstring_section_content])
for item in a_items:
if item in d_items:
adopted_content = _re.sub(fr'^([ ]*){item}.*?(?:(\n)\1([A-Za-z0-9_])|([ ]*\Z))',
r'\1\3',
adopted_content,
flags=_re.MULTILINE|_re.DOTALL).rstrip(' \n')
docstring_ = _re.sub(fr'(^[ ]*{key}\s*\n\s*{"-"*len(key)}\s*\n.*?)\n *(\Z|\n)',
fr'\1\n{adopted_content}\n\2',
docstring_,
flags=_re.MULTILINE|_re.DOTALL)
else:
section_title = f'{" "*(shift+docstring_indent)}{key}\n{" "*(shift+docstring_indent)}{"-"*len(key)}\n'
section_matches = [_re.search(
fr'[ ]*{list(sections.keys())[index]}\s*\n\s*{"-"*len(list(sections.keys())[index])}\s*', docstring_)
for index in range(i,len(sections))]
subsequent_section = '\\Z' if not any(section_matches) else \
'\n'+next(item for item in section_matches if item is not None).group(0)
docstring_ = _re.sub(fr'({subsequent_section})',
fr'\n{section_title}{adopted_content}\n\1',
docstring_)
return docstring_
return _re.sub(r'(^([ ]*)Returns\s*\n\2-------\s*\n[ ]*[A-Za-z0-9_ ]*: )(.*)\n',
fr'\1{return_type_}\n',
docstring_,flags=_re.MULTILINE)
def extend_docstring(docstring: _Union[None, str, _Callable] = None,
extra_parameters: _Optional[str] = None) -> _Callable:
**kwargs) -> _Callable:
"""
Decorator: Extend the function's docstring.
@ -602,8 +639,8 @@ def extend_docstring(docstring: _Union[None, str, _Callable] = None,
----------
docstring : str or callable, optional
Docstring to extend. Defaults to that of decorated function.
extra_parameters : str, optional
Additional information to append to Parameters section.
adopted_* : str or callable, optional
Additional information to insert into/append to respective section.
Notes
-----
@ -611,13 +648,55 @@ def extend_docstring(docstring: _Union[None, str, _Callable] = None,
"""
def _decorator(func):
if 'adopted_return' not in kwargs: kwargs['adopted_return'] = func
func.__doc__ = _docstringer(func.__doc__ if docstring is None else docstring,
extra_parameters,
func if isinstance(docstring,_Callable) else None,
)
**kwargs)
return func
return _decorator
def pass_on(keyword: str,
target: _Callable,
wrapped: _Callable = None) -> _Callable: # type: ignore
"""
Decorator: Combine signatures of 'wrapped' and 'target' functions and pass on output of 'target' as 'keyword' argument.
Parameters
----------
keyword : str
Keyword added to **kwargs of the decorated function
passing on the result of 'target'.
target : callable
The output of this function is passed to the
decorated function as 'keyword' argument.
wrapped: callable, optional
Signature of 'wrapped' function combined with
that of 'target' yields the overall signature of decorated function.
Notes
-----
The keywords used by 'target' will be prioritized
if they overlap with those of the decorated function.
Functions 'target' and 'wrapped' are assumed to only have keyword arguments.
"""
def decorator(func):
@_wraps(func)
def wrapper(*args, **kwargs):
kw_wrapped = set(kwargs.keys()) - set(inspect.getfullargspec(target).args)
kwargs_wrapped = {kw: kwargs.pop(kw) for kw in kw_wrapped}
kwargs_wrapped[keyword] = target(**kwargs)
return func(*args, **kwargs_wrapped)
args_ = [] if wrapped is None or 'self' not in inspect.signature(wrapped).parameters \
else [inspect.signature(wrapped).parameters['self']]
for f in [target] if wrapped is None else [target,wrapped]:
for param in inspect.signature(f).parameters.values():
if param.name != keyword \
and param.name not in [p.name for p in args_]+['self','cls', 'args', 'kwargs']:
args_.append(param.replace(kind=inspect._ParameterKind.KEYWORD_ONLY))
wrapper.__signature__ = inspect.Signature(parameters=args_,return_annotation=inspect.signature(func).return_annotation)
return wrapper
return decorator
def DREAM3D_base_group(fname: _Union[str, _Path]) -> str:
"""

View File

@ -4,7 +4,7 @@ warn_redundant_casts = True
ignore_missing_imports = True
[mypy-h5py.*]
ignore_missing_imports = True
[mypy-vtk.*]
[mypy-vtkmodules.*]
ignore_missing_imports = True
[mypy-PIL.*]
ignore_missing_imports = True

View File

@ -26,4 +26,3 @@ install_requires =
vtk>=8.1
matplotlib>=3.0 # requires numpy, pillow
pyyaml>=3.12
setup_requires = setuptools

View File

@ -60,9 +60,9 @@ def update(request):
@pytest.fixture
def ref_path_base():
"""Directory containing reference results."""
return Path(__file__).parent/'reference'
def res_path_base():
"""Directory containing testing resources."""
return Path(__file__).parent/'resources'
@pytest.fixture

View File

Before

Width:  |  Height:  |  Size: 147 B

After

Width:  |  Height:  |  Size: 147 B

View File

Before

Width:  |  Height:  |  Size: 138 B

After

Width:  |  Height:  |  Size: 138 B

View File

@ -0,0 +1 @@
n10-id1_scaled.vtk binary

Some files were not shown because too many files have changed in this diff Show More