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 *.pbz2 binary
# ignore files from MSC.Marc in language statistics # ignore files from MSC.Marc in language statistics
install/MarcMentat/** linguist-vendored install/MarcMentat/** linguist-vendored
src/Marc/include/* linguist-vendored src/Marc/include/* linguist-vendored
install/MarcMentat/MSC_modifications.py linguist-vendored=false install/MarcMentat/MSC_modifications.py linguist-vendored=false
# ignore reference files for tests in language statistics # ignore reference files for tests in language statistics
python/tests/reference/** linguist-vendored python/tests/resources/** linguist-vendored
# ignore deprecated scripts # ignore deprecated scripts
processing/legacy/** linguist-vendored processing/legacy/** linguist-vendored

View File

@ -2,7 +2,7 @@ name: Grid and Mesh Solver
on: [push] on: [push]
env: env:
PETSC_VERSION: '3.18.2' PETSC_VERSION: '3.18.4'
HOMEBREW_NO_ANALYTICS: 'ON' # Make Homebrew installation a little quicker HOMEBREW_NO_ANALYTICS: 'ON' # Make Homebrew installation a little quicker
HOMEBREW_NO_AUTO_UPDATE: 'ON' HOMEBREW_NO_AUTO_UPDATE: 'ON'
HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK: 'ON' HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK: 'ON'
@ -82,7 +82,8 @@ jobs:
- name: DAMASK - Run - name: DAMASK - Run
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: intel:
@ -129,7 +130,7 @@ jobs:
- name: PETSc - Prepare - name: PETSc - Prepare
run: | run: |
tar -xf download/petsc-${PETSC_VERSION}.tar.gz -C . 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 ./petsc-${PETSC_VERSION}/config/BuildSystem/config/package.py
export PETSC_DIR=${PWD}/petsc-${PETSC_VERSION} export PETSC_DIR=${PWD}/petsc-${PETSC_VERSION}
export PETSC_ARCH=intel-${INTEL_V} export PETSC_ARCH=intel-${INTEL_V}
@ -165,6 +166,7 @@ jobs:
make all make all
- name: DAMASK - Compile - name: DAMASK - Compile
if: contains( matrix.intel_v, 'classic')
run: | run: |
cmake -B build/grid -DDAMASK_SOLVER=grid -DCMAKE_INSTALL_PREFIX=${PWD} cmake -B build/grid -DDAMASK_SOLVER=grid -DCMAKE_INSTALL_PREFIX=${PWD}
cmake --build build/grid --parallel cmake --build build/grid --parallel
@ -173,6 +175,19 @@ jobs:
cmake --build build/mesh --parallel cmake --build build/mesh --parallel
cmake --install build/mesh 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 - name: DAMASK - Run
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: strategy:
matrix: 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] os: [ubuntu-latest, macos-latest, windows-latest]
fail-fast: false fail-fast: false
@ -75,7 +75,7 @@ jobs:
run: > run: >
sudo apt-get update && sudo apt-get update &&
sudo apt-get remove mysql* && 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 - name: Run unit tests
run: | run: |

View File

@ -11,7 +11,7 @@ endif()
project(Prerequisites LANGUAGES) project(Prerequisites LANGUAGES)
set(ENV{PKG_CONFIG_PATH} "$ENV{PETSC_DIR}/$ENV{PETSC_ARCH}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}") 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_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_Fortran_COMPILER PETSc fcompiler)
pkg_get_variable(CMAKE_C_COMPILER PETSc ccompiler) 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) project(damask-grid HOMEPAGE_URL https://damask.mpie.de LANGUAGES Fortran C)
elseif(DAMASK_SOLVER STREQUAL "MESH") elseif(DAMASK_SOLVER STREQUAL "MESH")
project(damask-mesh HOMEPAGE_URL https://damask.mpie.de LANGUAGES Fortran C) 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() else()
message(FATAL_ERROR "Invalid solver: DAMASK_SOLVER=${DAMASK_SOLVER}") message(FATAL_ERROR "Invalid solver: DAMASK_SOLVER=${DAMASK_SOLVER}")
endif() 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 -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 @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 .PHONY: clean
clean: clean:
@rm -rf build @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. # ... for certain disassociated or uninitialized pointers or unallocated allocatable objects.
set (DEBUG_FLAGS "${DEBUG_FLAGS},uninit") set (DEBUG_FLAGS "${DEBUG_FLAGS},uninit")
# ... for uninitialized variables. # ... 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") set (DEBUG_FLAGS "${DEBUG_FLAGS} -fpe-all=0 -ftz")
# ... capture all floating-point exceptions, need to overwrite -no-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 () endif ()
if (OPENMP) if (OPENMP)
set (OPENMP_FLAGS "-qopenmp") set (OPENMP_FLAGS "-fiopenmp")
endif () endif ()
if (OPTIMIZATION STREQUAL "OFF" OR OPTIMIZATION STREQUAL "DEBUG") if (OPTIMIZATION STREQUAL "OFF" OR OPTIMIZATION STREQUAL "DEBUG")
@ -23,6 +23,8 @@ endif ()
set (STANDARD_CHECK "-stand f18 -assume nostd_mod_proc_name") set (STANDARD_CHECK "-stand f18 -assume nostd_mod_proc_name")
set (LINKER_FLAGS "${LINKER_FLAGS} -shared-intel") set (LINKER_FLAGS "${LINKER_FLAGS} -shared-intel")
# Link against shared Intel libraries instead of static ones # 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 # Fine tuning compilation options
@ -93,8 +95,6 @@ set (DEBUG_FLAGS "${DEBUG_FLAGS},pointers")
# ... for certain disassociated or uninitialized pointers or unallocated allocatable objects. # ... for certain disassociated or uninitialized pointers or unallocated allocatable objects.
set (DEBUG_FLAGS "${DEBUG_FLAGS},uninit") set (DEBUG_FLAGS "${DEBUG_FLAGS},uninit")
# ... for uninitialized variables. # ... 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") set (DEBUG_FLAGS "${DEBUG_FLAGS} -fpe-all=0 -ftz")
# ... capture all floating-point exceptions, need to overwrite -no-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] g_crit: [0.5e+7]
s_crit: [0.006666] s_crit: [0.006666]
dot_o: 1.e-3 dot_o_0: 1.e-3
q: 20 p: 20
l_c: 1.0 l_c: 1.0
mu: 0.001 mu: 0.001

View File

@ -3,7 +3,7 @@ type: Hooke
references: references:
- D.J. Dever, - D.J. Dever,
Journal of Applied Physics 43(8):3293-3301, 1972, 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) fit to Tab. II (T_min=25ºC, T_max=880ºC)
C_11: 232.2e+9 C_11: 232.2e+9

View File

@ -3,8 +3,9 @@ type: Hooke
references: references:
- S.A. Kim and W.L. Johnson, - S.A. Kim and W.L. Johnson,
Materials Science & Engineering A 452-453:633-639, 2007, 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_11: 267.9e+9
C_12: 111.2e+9 C_12: 110.8e+9
C_44: 79.06e+9 C_44: 78.9e+9

View File

@ -3,7 +3,7 @@ type: dislotungsten
references: references:
- D. Cereceda et al., - D. Cereceda et al.,
International Journal of Plasticity 78:242-265, 2016, 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., - R. Gröger et al.,
Acta Materialia 56(19):5412-5425, 2008, Acta Materialia 56(19):5412-5425, 2008,
https://doi.org/10.1016/j.actamat.2008.07.037 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] N_sl: [12, 12]
f_edge: [1.0, 1.0]
b_sl: [2.49e-10, 2.49e-10] b_sl: [2.49e-10, 2.49e-10]
rho_mob_0: [2.81e+12, 2.8e+12] rho_mob_0: [2.81e+12, 2.8e+12]
rho_dip_0: [1.0, 1.0] # not given 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 # Glide
N_sl: [12] N_sl: [12]
f_edge: [1.0]
b_sl: [2.56e-10] # a/sqrt(2) b_sl: [2.56e-10] # a/sqrt(2)
Q_sl: [3.5e-19] Q_sl: [3.5e-19]
p_sl: [0.325] 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 ._vtk import VTK # noqa
from ._config import Config # noqa from ._config import Config # noqa
from ._configmaterial import ConfigMaterial # noqa from ._configmaterial import ConfigMaterial # noqa
from ._loadcasegrid import LoadcaseGrid # noqa
from ._grid import Grid # noqa from ._grid import Grid # noqa
from ._result import Result # 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): class Colormap(mpl.colors.ListedColormap):
""" """
Enhance matplotlib colormap functionality to be used within DAMASK. Enhance matplotlib colormap functionality for use within DAMASK.
Colors are internally stored as R(ed) G(green) B(lue) values. Colors are internally stored as R(ed) G(green) B(lue) values.
The colormap can be used in matplotlib, seaborn, etc., or can A colormap can be used in matplotlib, seaborn, etc., or can be
exported to file for external use. exported to file for external use.
References References
@ -153,12 +153,12 @@ class Colormap(mpl.colors.ListedColormap):
- 'hsl': Hue Saturation Luminance. - 'hsl': Hue Saturation Luminance.
- 'xyz': CIE Xyz. - 'xyz': CIE Xyz.
- 'lab': CIE Lab. - 'lab': CIE Lab.
- 'msh': Msh (for perceptual uniform interpolation). - 'msh': Msh (for perceptually uniform interpolation).
Returns Returns
------- -------
new : damask.Colormap new : damask.Colormap
Colormap within given bounds. Colormap spanning given bounds.
Examples Examples
-------- --------
@ -276,7 +276,7 @@ class Colormap(mpl.colors.ListedColormap):
def shade(self, def shade(self,
field: np.ndarray, field: np.ndarray,
bounds: Optional[FloatSequence] = None, bounds: Optional[FloatSequence] = None,
gap: Optional[float] = None) -> Image: gap: Optional[float] = None) -> Image.Image:
""" """
Generate PIL image of 2D field using colormap. Generate PIL image of 2D field using colormap.
@ -288,6 +288,7 @@ class Colormap(mpl.colors.ListedColormap):
Value range (left,right) spanned by colormap. Value range (left,right) spanned by colormap.
gap : field.dtype, optional gap : field.dtype, optional
Transparent value. NaN will always be rendered transparent. Transparent value. NaN will always be rendered transparent.
Defaults to None.
Returns Returns
------- -------
@ -334,6 +335,7 @@ class Colormap(mpl.colors.ListedColormap):
-------- --------
>>> import damask >>> import damask
>>> damask.Colormap.from_predefined('stress').reversed() >>> damask.Colormap.from_predefined('stress').reversed()
Colormap: stress_r
""" """
rev = super().reversed(name) rev = super().reversed(name)
@ -353,6 +355,7 @@ class Colormap(mpl.colors.ListedColormap):
If None, colormap name + suffix. If None, colormap name + suffix.
suffix: str, optional suffix: str, optional
Extension to use for colormap file. Extension to use for colormap file.
Defaults to empty.
Returns Returns
------- -------
@ -452,8 +455,8 @@ class Colormap(mpl.colors.ListedColormap):
References References
---------- ----------
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf | https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
https://www.kennethmoreland.com/color-maps/diverging_map.py | https://www.kennethmoreland.com/color-maps/diverging_map.py
""" """
def rad_diff(a,b): def rad_diff(a,b):
@ -735,8 +738,8 @@ class Colormap(mpl.colors.ListedColormap):
References References
---------- ----------
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf | https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
https://www.kennethmoreland.com/color-maps/diverging_map.py | https://www.kennethmoreland.com/color-maps/diverging_map.py
""" """
M = np.linalg.norm(lab) M = np.linalg.norm(lab)
@ -763,8 +766,8 @@ class Colormap(mpl.colors.ListedColormap):
References References
---------- ----------
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf | https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
https://www.kennethmoreland.com/color-maps/diverging_map.py | https://www.kennethmoreland.com/color-maps/diverging_map.py
""" """
return np.array([ return np.array([

View File

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

View File

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

View File

@ -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 import copy
from typing import Optional, Union, Callable, Dict, Any, Tuple, TypeVar from typing import Optional, Union, TypeVar
import numpy as np import numpy as np
@ -10,31 +9,6 @@ from . import Crystal
from . import util from . import util
from . import tensor 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') MyType = TypeVar('MyType', bound='Orientation')
class Orientation(Rotation,Crystal): 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, def __init__(self,
rotation: Union[FloatSequence, Rotation] = np.array([1.,0.,0.,0.]), rotation: Union[FloatSequence, Rotation] = np.array([1.,0.,0.,0.]),
*, *,
@ -108,7 +82,7 @@ class Orientation(Rotation,Crystal):
Parameters Parameters
---------- ----------
rotation : list, numpy.ndarray, Rotation, optional rotation : list, numpy.ndarray, or Rotation, optional
Unit quaternion in positive real hemisphere. Unit quaternion in positive real hemisphere.
Use .from_quaternion to perform a sanity check. Use .from_quaternion to perform a sanity check.
Defaults to no rotation. Defaults to no rotation.
@ -123,7 +97,7 @@ class Orientation(Rotation,Crystal):
""" """
Return repr(self). Return repr(self).
Give short human-readable summary. Give short, human-readable summary.
""" """
return util.srepr([Crystal.__repr__(self), return util.srepr([Crystal.__repr__(self),
@ -261,134 +235,91 @@ class Orientation(Rotation,Crystal):
Compound rotation self*other, i.e. first other then self rotation. Compound rotation self*other, i.e. first other then self rotation.
""" """
if isinstance(other, (Orientation,Rotation)): if not isinstance(other, (Orientation,Rotation)):
return self.copy(Rotation(self.quaternion)*Rotation(other.quaternion))
else:
raise TypeError('use "O@b", i.e. matmul, to apply Orientation "O" to object "b"') raise TypeError('use "O@b", i.e. matmul, to apply Orientation "O" to object "b"')
return self.copy(Rotation(self.quaternion)*Rotation(other.quaternion))
@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
@classmethod @classmethod
@util.extend_docstring(Rotation.from_random, @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': def from_random(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_random) return cls(**kwargs)
return cls(rotation=Rotation.from_random(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extend_docstring(Rotation.from_quaternion, @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': def from_quaternion(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_quaternion) return cls(**kwargs)
return cls(rotation=Rotation.from_quaternion(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extend_docstring(Rotation.from_Euler_angles, @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': def from_Euler_angles(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_Euler_angles) return cls(**kwargs)
return cls(rotation=Rotation.from_Euler_angles(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extend_docstring(Rotation.from_axis_angle, @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': def from_axis_angle(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_axis_angle) return cls(**kwargs)
return cls(rotation=Rotation.from_axis_angle(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extend_docstring(Rotation.from_basis, @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': def from_basis(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_basis) return cls(**kwargs)
return cls(rotation=Rotation.from_basis(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extend_docstring(Rotation.from_matrix, @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': def from_matrix(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_matrix) return cls(**kwargs)
return cls(rotation=Rotation.from_matrix(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extend_docstring(Rotation.from_Rodrigues_vector, @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': def from_Rodrigues_vector(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_Rodrigues_vector) return cls(**kwargs)
return cls(rotation=Rotation.from_Rodrigues_vector(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extend_docstring(Rotation.from_homochoric, @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': def from_homochoric(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_homochoric) return cls(**kwargs)
return cls(rotation=Rotation.from_homochoric(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extend_docstring(Rotation.from_cubochoric, @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': def from_cubochoric(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_cubochoric) return cls(**kwargs)
return cls(rotation=Rotation.from_cubochoric(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extend_docstring(Rotation.from_spherical_component, @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': def from_spherical_component(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_spherical_component) return cls(**kwargs)
return cls(rotation=Rotation.from_spherical_component(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extend_docstring(Rotation.from_fiber_component, @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': def from_fiber_component(cls, **kwargs) -> 'Orientation':
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_fiber_component) return cls(**kwargs)
return cls(rotation=Rotation.from_fiber_component(**kwargs_rot),**kwargs_ori)
@classmethod @classmethod
@util.extend_docstring(extra_parameters=_parameter_doc) @util.extend_docstring(adopted_parameters=Crystal.__init__)
def from_directions(cls, def from_directions(cls,
uvw: FloatSequence, uvw: FloatSequence,
hkl: FloatSequence, hkl: FloatSequence,
@ -467,24 +398,24 @@ class Orientation(Rotation,Crystal):
if self.family == 'cubic': if self.family == 'cubic':
return (np.prod(np.sqrt(2)-1. >= rho_abs,axis=-1) * return (np.prod(np.sqrt(2)-1. >= rho_abs,axis=-1) *
(1. >= np.sum(rho_abs,axis=-1))).astype(bool) (1. >= np.sum(rho_abs,axis=-1))).astype(bool)
elif self.family == 'hexagonal': if self.family == 'hexagonal':
return (np.prod(1. >= rho_abs,axis=-1) * return (np.prod(1. >= rho_abs,axis=-1) *
(2. >= np.sqrt(3)*rho_abs[...,0] + rho_abs[...,1]) * (2. >= np.sqrt(3)*rho_abs[...,0] + rho_abs[...,1]) *
(2. >= np.sqrt(3)*rho_abs[...,1] + rho_abs[...,0]) * (2. >= np.sqrt(3)*rho_abs[...,1] + rho_abs[...,0]) *
(2. >= np.sqrt(3) + rho_abs[...,2])).astype(bool) (2. >= np.sqrt(3) + rho_abs[...,2])).astype(bool)
elif self.family == 'tetragonal': if self.family == 'tetragonal':
return (np.prod(1. >= rho_abs[...,:2],axis=-1) * return (np.prod(1. >= rho_abs[...,:2],axis=-1) *
(np.sqrt(2) >= rho_abs[...,0] + rho_abs[...,1]) * (np.sqrt(2) >= rho_abs[...,0] + rho_abs[...,1]) *
(np.sqrt(2) >= rho_abs[...,2] + 1.)).astype(bool) (np.sqrt(2) >= rho_abs[...,2] + 1.)).astype(bool)
elif self.family == 'orthorhombic': if self.family == 'orthorhombic':
return (np.prod(1. >= rho_abs,axis=-1)).astype(bool) return (np.prod(1. >= rho_abs,axis=-1)).astype(bool)
elif self.family == 'monoclinic': if self.family == 'monoclinic':
return np.logical_or( 1. >= rho_abs[...,1], return np.logical_or( 1. >= rho_abs[...,1],
np.isnan(rho_abs[...,1])) np.isnan(rho_abs[...,1]))
elif self.family == 'triclinic': if self.family == 'triclinic':
return np.ones(rho_abs.shape[:-1]).astype(bool) return np.ones(rho_abs.shape[:-1]).astype(bool)
else:
raise TypeError(f'unknown symmetry "{self.family}"') raise TypeError(f'unknown symmetry "{self.family}"')
@property @property
@ -510,38 +441,40 @@ class Orientation(Rotation,Crystal):
return ((rho[...,0] >= rho[...,1]) & return ((rho[...,0] >= rho[...,1]) &
(rho[...,1] >= rho[...,2]) & (rho[...,1] >= rho[...,2]) &
(rho[...,2] >= 0)).astype(bool) (rho[...,2] >= 0)).astype(bool)
elif self.family == 'hexagonal': if self.family == 'hexagonal':
return ((rho[...,0] >= rho[...,1]*np.sqrt(3)) & return ((rho[...,0] >= rho[...,1]*np.sqrt(3)) &
(rho[...,1] >= 0) & (rho[...,1] >= 0) &
(rho[...,2] >= 0)).astype(bool) (rho[...,2] >= 0)).astype(bool)
elif self.family == 'tetragonal': if self.family == 'tetragonal':
return ((rho[...,0] >= rho[...,1]) & return ((rho[...,0] >= rho[...,1]) &
(rho[...,1] >= 0) & (rho[...,1] >= 0) &
(rho[...,2] >= 0)).astype(bool) (rho[...,2] >= 0)).astype(bool)
elif self.family == 'orthorhombic': if self.family == 'orthorhombic':
return ((rho[...,0] >= 0) & return ((rho[...,0] >= 0) &
(rho[...,1] >= 0) & (rho[...,1] >= 0) &
(rho[...,2] >= 0)).astype(bool) (rho[...,2] >= 0)).astype(bool)
elif self.family == 'monoclinic': if self.family == 'monoclinic':
return ((rho[...,1] >= 0) & return ((rho[...,1] >= 0) &
(rho[...,2] >= 0)).astype(bool) (rho[...,2] >= 0)).astype(bool)
else:
return np.ones_like(rho[...,0],dtype=bool) return np.ones_like(rho[...,0],dtype=bool)
def disorientation(self, def disorientation(self,
other: 'Orientation', other: 'Orientation',
return_operators: bool = False) -> object: return_operators: bool = False) -> object:
""" """
Calculate disorientation between myself and given other orientation. Calculate disorientation between self and given other orientation.
Parameters Parameters
---------- ----------
other : Orientation other : Orientation
Orientation to calculate disorientation for. Orientation to calculate disorientation for.
Shape of other blends with shape of own rotation array. Shape of other blends with shape of own rotation array.
For example, shapes of (2,3) for own rotations and (3,2) for other's result in (2,3,2) disorientations. For example, shapes of (2,3) for own rotations
and (3,2) for other's result in (2,3,2) disorientations.
return_operators : bool, optional return_operators : bool, optional
Return index pair of symmetrically equivalent orientations that result in disorientation axis falling into FZ. Return index pair of symmetrically equivalent orientations
that result in disorientation axis falling into FZ.
Defaults to False. Defaults to False.
Returns Returns
@ -578,8 +511,8 @@ class Orientation(Rotation,Crystal):
>>> N = 10000 >>> N = 10000
>>> a = damask.Orientation.from_random(shape=N,family='cubic') >>> a = damask.Orientation.from_random(shape=N,family='cubic')
>>> b = damask.Orientation.from_random(shape=N,family='cubic') >>> b = damask.Orientation.from_random(shape=N,family='cubic')
>>> d = a.disorientation(b).as_axis_angle(degrees=True,pair=True)[1] >>> n,omega = a.disorientation(b).as_axis_angle(degrees=True,pair=True)
>>> plt.hist(d,25) >>> plt.hist(omega,25)
>>> plt.show() >>> plt.show()
""" """
@ -626,6 +559,7 @@ class Orientation(Rotation,Crystal):
---------- ----------
weights : numpy.ndarray, shape (self.shape), optional weights : numpy.ndarray, shape (self.shape), optional
Relative weights of orientations. Relative weights of orientations.
Defaults to equal weights.
return_cloud : bool, optional return_cloud : bool, optional
Return the specific (symmetrically equivalent) orientations that were averaged. Return the specific (symmetrically equivalent) orientations that were averaged.
Defaults to False. Defaults to False.
@ -895,8 +829,8 @@ class Orientation(Rotation,Crystal):
Schmid matrix (in lab frame) of first octahedral slip system of a face-centered Schmid matrix (in lab frame) of first octahedral slip system of a face-centered
cubic crystal in "Goss" orientation. cubic crystal in "Goss" orientation.
>>> import damask
>>> import numpy as np >>> import numpy as np
>>> import damask
>>> np.set_printoptions(3,suppress=True,floatmode='fixed') >>> np.set_printoptions(3,suppress=True,floatmode='fixed')
>>> O = damask.Orientation.from_Euler_angles(phi=[0,45,0],degrees=True,lattice='cF') >>> O = damask.Orientation.from_Euler_angles(phi=[0,45,0],degrees=True,lattice='cF')
>>> O.Schmid(N_slip=[1]) >>> O.Schmid(N_slip=[1])
@ -936,8 +870,9 @@ class Orientation(Rotation,Crystal):
Returns Returns
------- -------
Orientations related to self following the selected rel : Orientation, shape (:,self.shape)
model for the orientation relationship. Orientations related to self according to the selected
model for the orientation relationship.
Examples Examples
-------- --------

View File

@ -15,7 +15,7 @@ from typing import Optional, Union, Callable, Any, Sequence, Literal, Dict, List
import h5py import h5py
import numpy as np import numpy as np
import numpy.ma as ma from numpy import ma
import damask import damask
from . import VTK from . import VTK
@ -90,9 +90,9 @@ h5py._hl.attrs.AttributeManager = AttributeManagerNullterm # 'Monkey patch'
class Result: class Result:
""" """
Add data to and export data from a DADF5 file. Add data to and export data from a DADF5 (DAMASK HDF5) file.
A DADF5 (DAMASK HDF5) file contains DAMASK results. A DADF5 file contains DAMASK results.
Its group/folder structure reflects the layout in material.yaml. Its group/folder structure reflects the layout in material.yaml.
This class provides a customizable view on the DADF5 file. This class provides a customizable view on the DADF5 file.
@ -118,7 +118,7 @@ class Result:
def __init__(self, fname: Union[str, Path]): def __init__(self, fname: Union[str, Path]):
""" """
New result view bound to a HDF5 file. New result view bound to a DADF5 file.
Parameters Parameters
---------- ----------
@ -131,10 +131,8 @@ class Result:
self.version_major = f.attrs['DADF5_version_major'] self.version_major = f.attrs['DADF5_version_major']
self.version_minor = f.attrs['DADF5_version_minor'] 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}"') 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() self.structured = 'cells' in f['geometry'].attrs.keys()
@ -192,7 +190,7 @@ class Result:
""" """
Return repr(self). Return repr(self).
Give short human-readable summary. Give short, human-readable summary.
""" """
with h5py.File(self.fname,'r') as f: with h5py.File(self.fname,'r') as f:
@ -220,7 +218,7 @@ class Result:
homogenizations: Union[None, str, Sequence[str], bool] = None, homogenizations: Union[None, str, Sequence[str], bool] = None,
fields: Union[None, str, Sequence[str], bool] = None) -> "Result": fields: Union[None, str, Sequence[str], bool] = None) -> "Result":
""" """
Manages the visibility of the groups. Manage the visibility of the groups.
Parameters Parameters
---------- ----------
@ -344,15 +342,15 @@ class Result:
Parameters Parameters
---------- ----------
increments: (list of) int, (list of) str, or bool, optional. increments: (list of) int, (list of) str, or bool, optional.
Number(s) of increments to select. Numbers of increments to select.
times: (list of) float, (list of) str, or bool, optional. times: (list of) float, (list of) str, or bool, optional.
Simulation time(s) of increments to select. Simulation times of increments to select.
phases: (list of) str, or bool, optional. phases: (list of) str, or bool, optional.
Name(s) of phases to select. Names of phases to select.
homogenizations: (list of) str, or bool, optional. homogenizations: (list of) str, or bool, optional.
Name(s) of homogenizations to select. Names of homogenizations to select.
fields: (list of) str, or bool, optional. fields: (list of) str, or bool, optional.
Name(s) of fields to select. Names of fields to select.
protected: bool, optional. protected: bool, optional.
Protection status of existing data. Protection status of existing data.
@ -400,15 +398,15 @@ class Result:
Parameters Parameters
---------- ----------
increments: (list of) int, (list of) str, or bool, optional. increments: (list of) int, (list of) str, or bool, optional.
Number(s) of increments to select. Numbers of increments to select.
times: (list of) float, (list of) str, or bool, optional. times: (list of) float, (list of) str, or bool, optional.
Simulation time(s) of increments to select. Simulation times of increments to select.
phases: (list of) str, or bool, optional. phases: (list of) str, or bool, optional.
Name(s) of phases to select. Names of phases to select.
homogenizations: (list of) str, or bool, optional. homogenizations: (list of) str, or bool, optional.
Name(s) of homogenizations to select. Names of homogenizations to select.
fields: (list of) str, or bool, optional. fields: (list of) str, or bool, optional.
Name(s) of fields to select. Names of fields to select.
Returns Returns
------- -------
@ -443,15 +441,15 @@ class Result:
Parameters Parameters
---------- ----------
increments: (list of) int, (list of) str, or bool, optional. increments: (list of) int, (list of) str, or bool, optional.
Number(s) of increments to select. Numbers of increments to select.
times: (list of) float, (list of) str, or bool, optional. times: (list of) float, (list of) str, or bool, optional.
Simulation time(s) of increments to select. Simulation times of increments to select.
phases: (list of) str, or bool, optional. phases: (list of) str, or bool, optional.
Name(s) of phases to select. Names of phases to select.
homogenizations: (list of) str, or bool, optional. homogenizations: (list of) str, or bool, optional.
Name(s) of homogenizations to select. Names of homogenizations to select.
fields: (list of) str, or bool, optional. fields: (list of) str, or bool, optional.
Name(s) of fields to select. Names of fields to select.
Returns Returns
------- -------
@ -703,7 +701,7 @@ class Result:
... '1/m²','total mobile dislocation density') ... '1/m²','total mobile dislocation density')
>>> r.add_calculation('np.sum(#rho_dip#,axis=1)','rho_dip_total', >>> r.add_calculation('np.sum(#rho_dip#,axis=1)','rho_dip_total',
... '1/m²','total dislocation dipole density') ... '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') ... '1/m²','total dislocation density')
Add Mises equivalent of the Cauchy stress without storage of Add Mises equivalent of the Cauchy stress without storage of
@ -746,9 +744,11 @@ class Result:
Parameters Parameters
---------- ----------
P : str, optional P : str, optional
Name of the dataset containing the first Piola-Kirchhoff stress. Defaults to 'P'. Name of the dataset containing the first Piola-Kirchhoff stress.
Defaults to 'P'.
F : str, optional F : str, optional
Name of the dataset containing the deformation gradient. Defaults to 'F'. Name of the dataset containing the deformation gradient.
Defaults to 'F'.
""" """
self._add_generic_pointwise(self._add_stress_Cauchy,{'P':P,'F':F}) self._add_generic_pointwise(self._add_stress_Cauchy,{'P':P,'F':F})
@ -1048,14 +1048,14 @@ class Result:
x: str, x: str,
ord: Union[None, int, float, Literal['fro', 'nuc']] = None): ord: Union[None, int, float, Literal['fro', 'nuc']] = None):
""" """
Add the norm of vector or tensor. Add the norm of a vector or tensor.
Parameters Parameters
---------- ----------
x : str x : str
Name of vector or tensor dataset. Name of vector or tensor dataset.
ord : {non-zero int, inf, -inf, 'fro', 'nuc'}, optional ord : {non-zero int, inf, -inf, 'fro', 'nuc'}, optional
Order of the norm. inf means NumPys inf object. For details refer to numpy.linalg.norm. Order of the norm. inf means NumPy's inf object. For details refer to numpy.linalg.norm.
""" """
self._add_generic_pointwise(self._add_norm,{'x':x},{'ord':ord}) self._add_generic_pointwise(self._add_norm,{'x':x},{'ord':ord})
@ -1077,7 +1077,7 @@ class Result:
def add_stress_second_Piola_Kirchhoff(self, def add_stress_second_Piola_Kirchhoff(self,
P: str = 'P', P: str = 'P',
F: str = 'F'): F: str = 'F'):
""" r"""
Add second Piola-Kirchhoff stress calculated from first Piola-Kirchhoff stress and deformation gradient. Add second Piola-Kirchhoff stress calculated from first Piola-Kirchhoff stress and deformation gradient.
Parameters Parameters
@ -1089,9 +1089,10 @@ class Result:
Notes Notes
----- -----
The definition of the second Piola-Kirchhoff stress (S = [F^-1 P]_sym) The definition of the second Piola-Kirchhoff stress
:math:`\vb{S} = \left(\vb{F}^{-1} \vb{P}\right)_\text{sym}`
follows the standard definition in nonlinear continuum mechanics. follows the standard definition in nonlinear continuum mechanics.
As such, no intermediate configuration, for instance that reached by F_p, As such, no intermediate configuration, for instance that reached by :math:`\vb{F}_\text{p}`,
is taken into account. is taken into account.
""" """
@ -1265,10 +1266,11 @@ class Result:
Notes Notes
----- -----
The incoporation of rotational parts into the elastic and plastic The presence of rotational parts in the elastic and plastic deformation gradient
deformation gradient requires it to use material/Lagragian strain measures calls for the use of
(based on 'U') for plastic strains and spatial/Eulerian strain measures material/Lagragian strain measures (based on 'U') for plastic strains and
(based on 'V') for elastic strains when calculating averages. spatial/Eulerian strain measures (based on 'V') for elastic strains
when calculating averages.
""" """
self._add_generic_pointwise(self._add_strain,{'F':F},{'t':t,'m':m}) self._add_generic_pointwise(self._add_strain,{'F':F},{'t':t,'m':m})
@ -1327,7 +1329,7 @@ class Result:
Notes Notes
----- -----
This function is only available for structured grids, This function is only available for structured grids,
i.e. results from the grid solver. i.e. fields resulting from the grid solver.
""" """
self._add_generic_grid(self._add_curl,{'f':f},{'size':self.size}) self._add_generic_grid(self._add_curl,{'f':f},{'size':self.size})
@ -1356,7 +1358,7 @@ class Result:
Notes Notes
----- -----
This function is only available for structured grids, This function is only available for structured grids,
i.e. results from the grid solver. i.e. fields resulting from the grid solver.
""" """
self._add_generic_grid(self._add_divergence,{'f':f},{'size':self.size}) self._add_generic_grid(self._add_divergence,{'f':f},{'size':self.size})
@ -1386,7 +1388,7 @@ class Result:
Notes Notes
----- -----
This function is only available for structured grids, This function is only available for structured grids,
i.e. results from the grid solver. i.e. fields resulting from the grid solver.
""" """
self._add_generic_grid(self._add_gradient,{'f':f},{'size':self.size}) self._add_generic_grid(self._add_gradient,{'f':f},{'size':self.size})
@ -1404,10 +1406,10 @@ class Result:
---------- ----------
func : function func : function
Callback function that calculates a new dataset from one or Callback function that calculates a new dataset from one or
more datasets per HDF5 group. more datasets per DADF5 group.
datasets : dictionary datasets : dictionary
Details of the datasets to be used: Details of the datasets to be used:
{arg (name to which the data is passed in func): label (in HDF5 file)}. {arg (name to which the data is passed in func): label (in DADF5 file)}.
args : dictionary, optional args : dictionary, optional
Arguments parsed to func. Arguments parsed to func.
@ -1487,10 +1489,10 @@ class Result:
---------- ----------
callback : function callback : function
Callback function that calculates a new dataset from one or Callback function that calculates a new dataset from one or
more datasets per HDF5 group. more datasets per DADF5 group.
datasets : dictionary datasets : dictionary
Details of the datasets to be used: Details of the datasets to be used:
{arg (name to which the data is passed in func): label (in HDF5 file)}. {arg (name to which the data is passed in func): label (in DADF5 file)}.
args : dictionary, optional args : dictionary, optional
Arguments parsed to func. Arguments parsed to func.
@ -1525,7 +1527,7 @@ class Result:
dataset.attrs['overwritten'] = True dataset.attrs['overwritten'] = True
else: else:
shape = result['data'].shape shape = result['data'].shape
if compress := (result['data'].size >= chunk_size*2): if compress := result['data'].size >= chunk_size*2:
chunks = (chunk_size//np.prod(shape[1:]),)+shape[1:] chunks = (chunk_size//np.prod(shape[1:]),)+shape[1:]
else: else:
chunks = shape chunks = shape
@ -1853,9 +1855,10 @@ class Result:
Export to VTK cell/point data. Export to VTK cell/point data.
One VTK file per visible increment is created. One VTK file per visible increment is created.
For point data, the VTK format is poly data (.vtp). For point data, the VTK format is PolyData (.vtp).
For cell data, either an image (.vti) or unstructured (.vtu) dataset For cell data, the file format is either ImageData (.vti)
is written for grid-based or mesh-based simulations, respectively. or UnstructuredGrid (.vtu) for grid-based or mesh-based simulations,
respectively.
Parameters Parameters
---------- ----------
@ -2068,7 +2071,8 @@ class Result:
def export_DADF5(self, def export_DADF5(self,
fname, fname,
output: Union[str, List[str]] = '*'): output: Union[str, List[str]] = '*',
mapping = None):
""" """
Export visible components into a new DADF5 file. Export visible components into a new DADF5 file.
@ -2082,20 +2086,61 @@ class Result:
output : (list of) str, optional output : (list of) str, optional
Names of the datasets to export. Names of the datasets to export.
Defaults to '*', in which case all visible datasets are exported. 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: if Path(fname).expanduser().absolute() == self.fname:
raise PermissionError(f'cannot overwrite {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: 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.update(f_in.attrs)
f_out.attrs.create(k,v) for g in ['setup','geometry'] + (['cell_to'] if mapping is None else []):
for g in ['setup','geometry','cell_to']:
f_in.copy(g,f_out) 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']): for inc in util.show_progress(self.visible['increments']):
f_in.copy(inc,f_out,shallow=True) f_in.copy(inc,f_out,shallow=True)
for out in _match(output,f_in['/'.join([inc,'geometry'])].keys()): if mapping is None:
f_in[inc]['geometry'].copy(out,f_out[inc]['geometry']) 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: for label in self.homogenizations:
f_in[inc]['homogenization'].copy(label,f_out[inc]['homogenization'],shallow=True) 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()): for field in _match(self.visible['fields'],f_in['/'.join([inc,ty,label])].keys()):
p = '/'.join([inc,ty,label,field]) p = '/'.join([inc,ty,label,field])
for out in _match(output,f_in[p].keys()): for out in _match(output,f_in[p].keys()):
f_in[p].copy(out,f_out[p]) cp(f_in[p],f_out[p],out,None if mapping is None else mappings[ty][label.encode()])
def export_simulation_setup(self, def export_simulation_setup(self,

View File

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

View File

@ -41,7 +41,7 @@ class Table:
""" """
Return repr(self). Return repr(self).
Give short human-readable summary. Give short, human-readable summary.
""" """
self._relabel('shapes') self._relabel('shapes')
@ -255,8 +255,8 @@ class Table:
""" """
Load from ASCII table file. Load from ASCII table file.
Initial comments are marked by '#', the first non-comment line Initial comments are marked by '#'.
containing the column labels. The first non-comment line contains the column labels.
- Vector data column labels are indicated by '1_v, 2_v, ..., n_v'. - Vector data column labels are indicated by '1_v, 2_v, ..., n_v'.
- Tensor data column labels are indicated by '3x3:1_T, 3x3:2_T, ..., 3x3:9_T'. - Tensor data column labels are indicated by '3x3:1_T, 3x3:2_T, ..., 3x3:9_T'.
@ -264,7 +264,7 @@ class Table:
Parameters Parameters
---------- ----------
fname : file, str, or pathlib.Path fname : file, str, or pathlib.Path
Filename or file for reading. Filename or file to read.
Returns Returns
------- -------
@ -299,11 +299,18 @@ class Table:
@staticmethod @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'. - Euler angles (Bunge notation) in radians, 3 floats, label 'eu'.
- Spatial position in meters, 2 floats, label 'pos'. - Spatial position in meters, 2 floats, label 'pos'.
@ -316,7 +323,10 @@ class Table:
Parameters Parameters
---------- ----------
fname : file, str, or pathlib.Path fname : file, str, or pathlib.Path
Filename or file for reading. Filename or file to read.
shapes : dict with str:int pairs, optional
Column labels and their width.
Defaults to standard TSL ANG format.
Returns Returns
------- -------
@ -338,7 +348,6 @@ class Table:
data = np.loadtxt(content) 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: if (remainder := data.shape[1]-sum(shapes.values())) > 0:
shapes['unknown'] = remainder shapes['unknown'] = remainder
@ -458,9 +467,9 @@ class Table:
Parameters Parameters
---------- ----------
label_old : (iterable of) str label_old : (iterable of) str
Old column label(s). Old column labels.
label_new : (iterable of) str label_new : (iterable of) str
New column label(s). New column labels.
Returns Returns
------- -------
@ -488,7 +497,7 @@ class Table:
label : str or list label : str or list
Column labels for sorting. Column labels for sorting.
ascending : bool or list, optional ascending : bool or list, optional
Set sort order. Set sort order. Defaults to True.
Returns Returns
------- -------
@ -574,7 +583,7 @@ class Table:
Parameters Parameters
---------- ----------
fname : file, str, or pathlib.Path fname : file, str, or pathlib.Path
Filename or file for writing. Filename or file to write.
with_labels : bool, optional with_labels : bool, optional
Write column labels. Defaults to True. Write column labels. Defaults to True.
@ -594,4 +603,7 @@ class Table:
f = util.open_text(fname,'w') f = util.open_text(fname,'w')
f.write('\n'.join([f'# {c}' for c in self.comments] + [' '.join(labels)])+('\n' if labels else '')) 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 from typing import Optional, Union, Literal, List, Sequence
import numpy as np import numpy as np
import vtk from vtkmodules.vtkCommonCore import (
from vtk.util.numpy_support import numpy_to_vtk as np_to_vtk vtkPoints,
from vtk.util.numpy_support import numpy_to_vtkIdTypeArray as np_to_vtkIdTypeArray vtkStringArray,
from vtk.util.numpy_support import vtk_to_numpy as vtk_to_np 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 ._typehints import FloatSequence, IntSequence
from . import util from . import util
@ -23,16 +67,16 @@ class VTK:
""" """
def __init__(self, def __init__(self,
vtk_data: vtk.vtkDataSet): vtk_data: vtkDataSet):
""" """
New spatial visualization. New spatial visualization.
Parameters Parameters
---------- ----------
vtk_data : subclass of vtk.vtkDataSet vtk_data : subclass of vtkDataSet
Description of geometry and topology, optionally with attached data. Description of geometry and topology, optionally with attached data.
Valid types are vtk.vtkImageData, vtk.vtkUnstructuredGrid, Valid types are vtkImageData, vtkUnstructuredGrid,
vtk.vtkPolyData, and vtk.vtkRectilinearGrid. vtkPolyData, and vtkRectilinearGrid.
""" """
self.vtk_data = vtk_data self.vtk_data = vtk_data
@ -42,7 +86,7 @@ class VTK:
""" """
Return repr(self). Return repr(self).
Give short human-readable summary. Give short, human-readable summary.
""" """
info = [self.vtk_data.__vtkname__] info = [self.vtk_data.__vtkname__]
@ -76,14 +120,14 @@ class VTK:
def copy(self): def copy(self):
if isinstance(self.vtk_data,vtk.vtkImageData): if isinstance(self.vtk_data,vtkImageData):
dup = vtk.vtkImageData() dup = vtkImageData()
elif isinstance(self.vtk_data,vtk.vtkUnstructuredGrid): elif isinstance(self.vtk_data,vtkUnstructuredGrid):
dup = vtk.vtkUnstructuredGrid() dup = vtkUnstructuredGrid()
elif isinstance(self.vtk_data,vtk.vtkPolyData): elif isinstance(self.vtk_data,vtkPolyData):
dup = vtk.vtkPolyData() dup = vtkPolyData()
elif isinstance(self.vtk_data,vtk.vtkRectilinearGrid): elif isinstance(self.vtk_data,vtkRectilinearGrid):
dup = vtk.vtkRectilinearGrid() dup = vtkRectilinearGrid()
else: else:
raise TypeError raise TypeError
@ -114,7 +158,7 @@ class VTK:
Comments. Comments.
""" """
s = vtk.vtkStringArray() s = vtkStringArray()
s.SetName('comments') s.SetName('comments')
for c in comments: for c in comments:
s.InsertNextValue(c) s.InsertNextValue(c)
@ -154,7 +198,7 @@ class VTK:
size: FloatSequence, size: FloatSequence,
origin: FloatSequence = np.zeros(3)) -> 'VTK': 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. This is the common type for grid solver results.
@ -163,7 +207,7 @@ class VTK:
cells : sequence of int, len (3) cells : sequence of int, len (3)
Number of cells along each dimension. Number of cells along each dimension.
size : sequence of float, len (3) size : sequence of float, len (3)
Physical length along each dimension. Edge length along each dimension.
origin : sequence of float, len (3), optional origin : sequence of float, len (3), optional
Coordinates of grid origin. Coordinates of grid origin.
@ -173,7 +217,7 @@ class VTK:
VTK-based geometry without nodal or cell data. VTK-based geometry without nodal or cell data.
""" """
vtk_data = vtk.vtkImageData() vtk_data = vtkImageData()
vtk_data.SetDimensions(*(np.array(cells)+1)) vtk_data.SetDimensions(*(np.array(cells)+1))
vtk_data.SetOrigin(*(np.array(origin))) vtk_data.SetOrigin(*(np.array(origin)))
vtk_data.SetSpacing(*(np.array(size)/np.array(cells))) vtk_data.SetSpacing(*(np.array(size)/np.array(cells)))
@ -186,7 +230,7 @@ class VTK:
connectivity: np.ndarray, connectivity: np.ndarray,
cell_type: str) -> 'VTK': cell_type: str) -> 'VTK':
""" """
Create VTK of type vtk.vtkUnstructuredGrid. Create VTK of type vtkUnstructuredGrid.
This is the common type for mesh solver results. This is the common type for mesh solver results.
@ -198,7 +242,7 @@ class VTK:
Cell connectivity (0-based), first dimension determines #Cells, Cell connectivity (0-based), first dimension determines #Cells,
second dimension determines #Nodes/Cell. second dimension determines #Nodes/Cell.
cell_type : str 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 Returns
------- -------
@ -206,18 +250,18 @@ class VTK:
VTK-based geometry without nodal or cell data. VTK-based geometry without nodal or cell data.
""" """
vtk_nodes = vtk.vtkPoints() vtk_nodes = vtkPoints()
vtk_nodes.SetData(np_to_vtk(np.ascontiguousarray(nodes))) vtk_nodes.SetData(numpy_to_vtk(np.ascontiguousarray(nodes)))
cells = vtk.vtkCellArray() cells = vtkCellArray()
cells.SetNumberOfCells(connectivity.shape[0]) cells.SetNumberOfCells(connectivity.shape[0])
T = np.concatenate((np.ones((connectivity.shape[0],1),dtype=np.int64)*connectivity.shape[1], T = np.concatenate((np.ones((connectivity.shape[0],1),dtype=np.int64)*connectivity.shape[1],
connectivity),axis=1).ravel() 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) vtk_data.SetPoints(vtk_nodes)
cell_types = {'TRIANGLE':vtk.VTK_TRIANGLE, 'QUAD':vtk.VTK_QUAD, cell_types = {'TRIANGLE':VTK_TRIANGLE, 'QUAD':VTK_QUAD,
'TETRA' :vtk.VTK_TETRA, 'HEXAHEDRON':vtk.VTK_HEXAHEDRON} 'TETRA' :VTK_TETRA, 'HEXAHEDRON':VTK_HEXAHEDRON}
vtk_data.SetCells(cell_types[cell_type.split("_",1)[-1].upper()],cells) vtk_data.SetCells(cell_types[cell_type.split("_",1)[-1].upper()],cells)
return VTK(vtk_data) return VTK(vtk_data)
@ -226,7 +270,7 @@ class VTK:
@staticmethod @staticmethod
def from_poly_data(points: np.ndarray) -> 'VTK': 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. This is the common type for point-wise data.
@ -242,15 +286,15 @@ class VTK:
""" """
N = points.shape[0] N = points.shape[0]
vtk_points = vtk.vtkPoints() vtk_points = vtkPoints()
vtk_points.SetData(np_to_vtk(np.ascontiguousarray(points))) vtk_points.SetData(numpy_to_vtk(np.ascontiguousarray(points)))
vtk_cells = vtk.vtkCellArray() vtk_cells = vtkCellArray()
vtk_cells.SetNumberOfCells(N) 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)) 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.SetPoints(vtk_points)
vtk_data.SetVerts(vtk_cells) vtk_data.SetVerts(vtk_cells)
@ -260,7 +304,7 @@ class VTK:
@staticmethod @staticmethod
def from_rectilinear_grid(grid: FloatSequence) -> 'VTK': def from_rectilinear_grid(grid: FloatSequence) -> 'VTK':
""" """
Create VTK of type vtk.vtkRectilinearGrid. Create VTK of type vtkRectilinearGrid.
Parameters Parameters
---------- ----------
@ -273,9 +317,9 @@ class VTK:
VTK-based geometry without nodal or cell data. VTK-based geometry without nodal or cell data.
""" """
vtk_data = vtk.vtkRectilinearGrid() vtk_data = vtkRectilinearGrid()
vtk_data.SetDimensions(*map(len,grid)) 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'])] [coord[i].SetName(n) for i,n in enumerate(['x','y','z'])]
vtk_data.SetXCoordinates(coord[0]) vtk_data.SetXCoordinates(coord[0])
vtk_data.SetYCoordinates(coord[1]) vtk_data.SetYCoordinates(coord[1])
@ -293,10 +337,10 @@ class VTK:
Parameters Parameters
---------- ----------
fname : str or pathlib.Path fname : str or pathlib.Path
Filename for reading. Filename to read.
Valid extensions are .vti, .vtu, .vtp, .vtr, and .vtk. Valid extensions are .vti, .vtu, .vtp, .vtr, and .vtk.
dataset_type : {'ImageData', 'UnstructuredGrid', 'PolyData', 'RectilinearGrid'}, optional dataset_type : {'ImageData', 'UnstructuredGrid', 'PolyData', 'RectilinearGrid'}, optional
Name of the vtk.vtkDataSet subclass when opening a .vtk file. Name of the vtkDataSet subclass when opening a .vtk file.
Returns Returns
------- -------
@ -307,7 +351,7 @@ class VTK:
if not Path(fname).expanduser().is_file(): # vtk has a strange error handling if not Path(fname).expanduser().is_file(): # vtk has a strange error handling
raise FileNotFoundError(f'file "{fname}" not found') raise FileNotFoundError(f'file "{fname}" not found')
if (ext := Path(fname).suffix) == '.vtk' or dataset_type is not None: if (ext := Path(fname).suffix) == '.vtk' or dataset_type is not None:
reader = vtk.vtkGenericDataObjectReader() reader = vtkGenericDataObjectReader()
reader.SetFileName(str(Path(fname).expanduser())) reader.SetFileName(str(Path(fname).expanduser()))
if dataset_type is None: if dataset_type is None:
raise TypeError('dataset type for *.vtk file not given') 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') raise TypeError(f'unknown dataset type "{dataset_type}" for vtk file')
else: else:
if ext == '.vti': if ext == '.vti':
reader = vtk.vtkXMLImageDataReader() reader = vtkXMLImageDataReader()
elif ext == '.vtu': elif ext == '.vtu':
reader = vtk.vtkXMLUnstructuredGridReader() reader = vtkXMLUnstructuredGridReader()
elif ext == '.vtp': elif ext == '.vtp':
reader = vtk.vtkXMLPolyDataReader() reader = vtkXMLPolyDataReader()
elif ext == '.vtr': elif ext == '.vtr':
reader = vtk.vtkXMLRectilinearGridReader() reader = vtkXMLRectilinearGridReader()
else: else:
raise TypeError(f'unknown file extension "{ext}"') raise TypeError(f'unknown file extension "{ext}"')
@ -352,7 +396,7 @@ class VTK:
def as_ASCII(self) -> str: def as_ASCII(self) -> str:
"""ASCII representation of the VTK data.""" """ASCII representation of the VTK data."""
writer = vtk.vtkDataSetWriter() writer = vtkDataSetWriter()
writer.SetHeader(f'# {util.execution_stamp("VTK")}') writer.SetHeader(f'# {util.execution_stamp("VTK")}')
writer.WriteToOutputStringOn() writer.WriteToOutputStringOn()
writer.SetInputData(self.vtk_data) writer.SetInputData(self.vtk_data)
@ -370,21 +414,21 @@ class VTK:
Parameters Parameters
---------- ----------
fname : str or pathlib.Path fname : str or pathlib.Path
Filename for writing. Filename to write.
parallel : bool, optional parallel : bool, optional
Write data in parallel background process. Defaults to True. Write data in parallel background process. Defaults to True.
compress : bool, optional compress : bool, optional
Compress with zlib algorithm. Defaults to True. Compress with zlib algorithm. Defaults to True.
""" """
if isinstance(self.vtk_data,vtk.vtkImageData): if isinstance(self.vtk_data,vtkImageData):
writer = vtk.vtkXMLImageDataWriter() writer = vtkXMLImageDataWriter()
elif isinstance(self.vtk_data,vtk.vtkUnstructuredGrid): elif isinstance(self.vtk_data,vtkUnstructuredGrid):
writer = vtk.vtkXMLUnstructuredGridWriter() writer = vtkXMLUnstructuredGridWriter()
elif isinstance(self.vtk_data,vtk.vtkPolyData): elif isinstance(self.vtk_data,vtkPolyData):
writer = vtk.vtkXMLPolyDataWriter() writer = vtkXMLPolyDataWriter()
elif isinstance(self.vtk_data,vtk.vtkRectilinearGrid): elif isinstance(self.vtk_data,vtkRectilinearGrid):
writer = vtk.vtkXMLRectilinearGridWriter() writer = vtkXMLRectilinearGridWriter()
default_ext = '.'+writer.GetDefaultFileExtension() default_ext = '.'+writer.GetDefaultFileExtension()
ext = Path(fname).suffix ext = Path(fname).suffix
@ -433,6 +477,11 @@ class VTK:
Data to add or replace. Each table label is individually considered. Data to add or replace. Each table label is individually considered.
Number of rows needs to match either number of cells or number of points. Number of rows needs to match either number of cells or number of points.
Returns
-------
updated : damask.VTK
Updated VTK-based geometry.
Notes Notes
----- -----
If the number of cells equals the number of points, the data is added to both. If the number of cells equals the number of points, the data is added to both.
@ -451,11 +500,11 @@ class VTK:
.astype(np.single if data.dtype in [np.double,np.longdouble] else data.dtype) .astype(np.single if data.dtype in [np.double,np.longdouble] else data.dtype)
if data.dtype.type is np.str_: if data.dtype.type is np.str_:
d = vtk.vtkStringArray() d = vtkStringArray()
for s in np.squeeze(data_): for s in np.squeeze(data_):
d.InsertNextValue(s) d.InsertNextValue(s)
else: else:
d = np_to_vtk(data_,deep=True) d = numpy_to_vtk(data_,deep=True)
d.SetName(label) d.SetName(label)
@ -513,7 +562,7 @@ class VTK:
for a in range(cell_data.GetNumberOfArrays()): for a in range(cell_data.GetNumberOfArrays()):
if cell_data.GetArrayName(a) == label: if cell_data.GetArrayName(a) == label:
try: try:
return vtk_to_np(cell_data.GetArray(a)) return vtk_to_numpy(cell_data.GetArray(a))
except AttributeError: except AttributeError:
vtk_array = cell_data.GetAbstractArray(a) # string array vtk_array = cell_data.GetAbstractArray(a) # string array
@ -521,7 +570,7 @@ class VTK:
for a in range(point_data.GetNumberOfArrays()): for a in range(point_data.GetNumberOfArrays()):
if point_data.GetArrayName(a) == label: if point_data.GetArrayName(a) == label:
try: try:
return vtk_to_np(point_data.GetArray(a)) return vtk_to_numpy(point_data.GetArray(a))
except AttributeError: except AttributeError:
vtk_array = point_data.GetAbstractArray(a) # string array vtk_array = point_data.GetAbstractArray(a) # string array
@ -548,7 +597,7 @@ class VTK:
Notes Notes
----- -----
The first component is shown when visualizing vector datasets The first component is shown when visualizing vector datasets
(this includes tensor datasets because they are flattened). (this includes tensor datasets as they are flattened).
""" """
# See http://compilatrix.com/article/vtk-1 for possible improvements. # See http://compilatrix.com/article/vtk-1 for possible improvements.
@ -567,7 +616,7 @@ class VTK:
width = 1024 width = 1024
height = 768 height = 768
lut = vtk.vtkLookupTable() lut = vtkLookupTable()
colormap_ = Colormap.from_predefined(colormap) if isinstance(colormap,str) else \ colormap_ = Colormap.from_predefined(colormap) if isinstance(colormap,str) else \
colormap colormap
lut.SetNumberOfTableValues(len(colormap_.colors)) lut.SetNumberOfTableValues(len(colormap_.colors))
@ -576,33 +625,33 @@ class VTK:
lut.Build() lut.Build()
self.vtk_data.GetCellData().SetActiveScalars(label) self.vtk_data.GetCellData().SetActiveScalars(label)
mapper = vtk.vtkDataSetMapper() mapper = vtkDataSetMapper()
mapper.SetInputData(self.vtk_data) mapper.SetInputData(self.vtk_data)
mapper.SetLookupTable(lut) mapper.SetLookupTable(lut)
mapper.SetScalarRange(self.vtk_data.GetScalarRange()) mapper.SetScalarRange(self.vtk_data.GetScalarRange())
actor = vtk.vtkActor() actor = vtkActor()
actor.SetMapper(mapper) actor.SetMapper(mapper)
actor.GetProperty().SetColor(230/255,150/255,68/255) actor.GetProperty().SetColor(230/255,150/255,68/255)
ren = vtk.vtkRenderer() ren = vtkRenderer()
ren.AddActor(actor) ren.AddActor(actor)
if label is None: if label is None:
ren.SetBackground(67/255,128/255,208/255) ren.SetBackground(67/255,128/255,208/255)
else: else:
colormap_vtk = vtk.vtkScalarBarActor() colormap_vtk = vtkScalarBarActor()
colormap_vtk.SetLookupTable(lut) colormap_vtk.SetLookupTable(lut)
colormap_vtk.SetTitle(label) colormap_vtk.SetTitle(label)
colormap_vtk.SetMaximumWidthInPixels(width//100) colormap_vtk.SetMaximumWidthInPixels(width//100)
ren.AddActor2D(colormap_vtk) ren.AddActor2D(colormap_vtk)
ren.SetBackground(0.3,0.3,0.3) ren.SetBackground(0.3,0.3,0.3)
window = vtk.vtkRenderWindow() window = vtkRenderWindow()
window.AddRenderer(ren) window.AddRenderer(ren)
window.SetSize(width,height) window.SetSize(width,height)
window.SetWindowName(util.execution_stamp('VTK','show')) window.SetWindowName(util.execution_stamp('VTK','show'))
iren = vtk.vtkRenderWindowInteractor() iren = vtkRenderWindowInteractor()
iren.SetRenderWindow(window) iren.SetRenderWindow(window)
if os.name == 'posix' and 'DISPLAY' not in os.environ: if os.name == 'posix' and 'DISPLAY' not in os.environ:
print('Found no rendering device') print('Found no rendering device')

View File

@ -186,8 +186,6 @@ def displacement_fluct_point(size: _FloatSequence,
Fluctuating part of the cell center displacements. 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 = _ks(size,F.shape[:3],False)
k_s_squared = _np.einsum('...l,...l',k_s,k_s) k_s_squared = _np.einsum('...l,...l',k_s,k_s)
k_s_squared[0,0,0] = 1.0 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', displacement = -_np.einsum('ijkml,ijkl,l->ijkm',
_np.fft.rfftn(F,axes=(0,1,2)), _np.fft.rfftn(F,axes=(0,1,2)),
k_s, k_s,
integrator, _np.array([0.5j/_np.pi]*3),
) / k_s_squared[...,_np.newaxis] ) / k_s_squared[...,_np.newaxis]
return _np.fft.irfftn(displacement,axes=(0,1,2),s=F.shape[:3]) return _np.fft.irfftn(displacement,axes=(0,1,2),s=F.shape[:3])
@ -402,7 +400,7 @@ def displacement_node(size: _FloatSequence,
Returns Returns
------- -------
u_p : numpy.ndarray, shape (:,:,:,3) u_n : numpy.ndarray, shape (:,:,:,3)
Nodal displacements. Nodal displacements.
""" """
@ -564,17 +562,10 @@ def unravel_index(idx: _np.ndarray) -> _np.ndarray:
>>> seq = np.arange(6).reshape((3,2,1),order='F') >>> seq = np.arange(6).reshape((3,2,1),order='F')
>>> (coord_idx := damask.grid_filters.unravel_index(seq)) >>> (coord_idx := damask.grid_filters.unravel_index(seq))
array([[[[0, 0, 0]], array([[[[0, 0, 0]],
[[0, 1, 0]]], [[0, 1, 0]]],
[[[1, 0, 0]], [[[1, 0, 0]],
[[1, 1, 0]]], [[1, 1, 0]]],
[[[2, 0, 0]], [[[2, 0, 0]],
[[2, 1, 0]]]]) [[2, 1, 0]]]])
>>> coord_idx[1,1,0] >>> coord_idx[1,1,0]
array([1, 1, 0]) array([1, 1, 0])
@ -608,17 +599,12 @@ def ravel_index(idx: _np.ndarray) -> _np.ndarray:
>>> import damask >>> import damask
>>> (rev := np.array([[1,1,0],[0,1,0],[1,0,0],[0,0,0]]).reshape((2,2,1,3))) >>> (rev := np.array([[1,1,0],[0,1,0],[1,0,0],[0,0,0]]).reshape((2,2,1,3)))
array([[[[1, 1, 0]], array([[[[1, 1, 0]],
[[0, 1, 0]]], [[0, 1, 0]]],
[[[1, 0, 0]], [[[1, 0, 0]],
[[0, 0, 0]]]]) [[0, 0, 0]]]])
>>> (flat_idx := damask.grid_filters.ravel_index(rev)) >>> (flat_idx := damask.grid_filters.ravel_index(rev))
array([[[3], array([[[3],
[2]], [2]],
[[1], [[1],
[0]]]) [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 import numpy as _np
@ -14,7 +14,7 @@ from . import _rotation
def deformation_Cauchy_Green_left(F: _np.ndarray) -> _np.ndarray: def deformation_Cauchy_Green_left(F: _np.ndarray) -> _np.ndarray:
""" r"""
Calculate left Cauchy-Green deformation tensor (Finger deformation tensor). Calculate left Cauchy-Green deformation tensor (Finger deformation tensor).
Parameters Parameters
@ -27,12 +27,18 @@ def deformation_Cauchy_Green_left(F: _np.ndarray) -> _np.ndarray:
B : numpy.ndarray, shape (...,3,3) B : numpy.ndarray, shape (...,3,3)
Left Cauchy-Green deformation tensor. Left Cauchy-Green deformation tensor.
Notes
-----
.. math::
\vb{B} = \vb{F} \vb{F}^\text{T}
""" """
return _np.matmul(F,_tensor.transpose(F)) return _np.matmul(F,_tensor.transpose(F))
def deformation_Cauchy_Green_right(F: _np.ndarray) -> _np.ndarray: def deformation_Cauchy_Green_right(F: _np.ndarray) -> _np.ndarray:
""" r"""
Calculate right Cauchy-Green deformation tensor. Calculate right Cauchy-Green deformation tensor.
Parameters Parameters
@ -45,12 +51,18 @@ def deformation_Cauchy_Green_right(F: _np.ndarray) -> _np.ndarray:
C : numpy.ndarray, shape (...,3,3) C : numpy.ndarray, shape (...,3,3)
Right Cauchy-Green deformation tensor. Right Cauchy-Green deformation tensor.
Notes
-----
.. math::
\vb{C} = \vb{F}^\text{T} \vb{F}
""" """
return _np.matmul(_tensor.transpose(F),F) return _np.matmul(_tensor.transpose(F),F)
def equivalent_strain_Mises(epsilon: _np.ndarray) -> _np.ndarray: def equivalent_strain_Mises(epsilon: _np.ndarray) -> _np.ndarray:
""" r"""
Calculate the Mises equivalent of a strain tensor. Calculate the Mises equivalent of a strain tensor.
Parameters Parameters
@ -63,12 +75,23 @@ def equivalent_strain_Mises(epsilon: _np.ndarray) -> _np.ndarray:
epsilon_vM : numpy.ndarray, shape (...) epsilon_vM : numpy.ndarray, shape (...)
Von Mises equivalent strain of epsilon. 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) return _equivalent_Mises(epsilon,2.0/3.0)
def equivalent_stress_Mises(sigma: _np.ndarray) -> _np.ndarray: def equivalent_stress_Mises(sigma: _np.ndarray) -> _np.ndarray:
""" r"""
Calculate the Mises equivalent of a stress tensor. Calculate the Mises equivalent of a stress tensor.
Parameters Parameters
@ -81,6 +104,17 @@ def equivalent_stress_Mises(sigma: _np.ndarray) -> _np.ndarray:
sigma_vM : numpy.ndarray, shape (...) sigma_vM : numpy.ndarray, shape (...)
Von Mises equivalent stress of sigma. 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) 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: def rotation(T: _np.ndarray) -> _rotation.Rotation:
""" r"""
Calculate the rotational part of a tensor. Calculate the rotational part of a tensor.
Parameters Parameters
@ -118,23 +152,35 @@ def rotation(T: _np.ndarray) -> _rotation.Rotation:
R : damask.Rotation, shape (...) R : damask.Rotation, shape (...)
Rotational part of the vector. 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]) return _rotation.Rotation.from_matrix(_polar_decomposition(T,'R')[0])
def strain(F: _np.ndarray, def strain(F: _np.ndarray,
#t: _Literal['V', 'U'], should work, but rejected by SC
t: str, t: str,
m: float) -> _np.ndarray: m: float) -> _np.ndarray:
""" r"""
Calculate strain tensor (SethHill family). Calculate strain tensor (SethHill family).
Parameters Parameters
---------- ----------
F : numpy.ndarray, shape (...,3,3) F : numpy.ndarray, shape (...,3,3)
Deformation gradient. Deformation gradient.
t : {V, U} t : {'V', 'U'}
Type of the polar decomposition, V for left stretch tensor Type of the polar decomposition, 'V' for left stretch tensor
and U for right stretch tensor. or 'U' for right stretch tensor.
m : float m : float
Order of the strain. Order of the strain.
@ -143,25 +189,25 @@ def strain(F: _np.ndarray,
epsilon : numpy.ndarray, shape (...,3,3) epsilon : numpy.ndarray, shape (...,3,3)
Strain of F. Strain of F.
Notes
-----
The strain is defined as:
.. math::
\vb*{\epsilon}_V^{(m)} = \frac{1}{2m} (\vb{V}^{2m} - \vb{I}) \\\\
\vb*{\epsilon}_U^{(m)} = \frac{1}{2m} (\vb{U}^{2m} - \vb{I})
References References
---------- ----------
https://en.wikipedia.org/wiki/Finite_strain_theory | https://en.wikipedia.org/wiki/Finite_strain_theory
https://de.wikipedia.org/wiki/Verzerrungstensor | https://de.wikipedia.org/wiki/Verzerrungstensor
""" """
if t == 'V': 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)) w,n = _np.linalg.eigh(deformation_Cauchy_Green_left(F) if t=='V' else deformation_Cauchy_Green_right(F))
elif t == 'U': return 0.5 * _np.einsum('...j,...kj,...lj',_np.log(w),n,n) if m == 0.0 \
w,n = _np.linalg.eigh(deformation_Cauchy_Green_right(F)) else 0.5/m * (_np.einsum('...j,...kj,...lj', w**m,n,n) - _np.eye(3))
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
def stress_Cauchy(P: _np.ndarray, 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: def stretch_left(T: _np.ndarray) -> _np.ndarray:
""" r"""
Calculate left stretch of a tensor. Calculate left stretch of a tensor.
Parameters Parameters
@ -225,12 +271,23 @@ def stretch_left(T: _np.ndarray) -> _np.ndarray:
V : numpy.ndarray, shape (...,3,3) V : numpy.ndarray, shape (...,3,3)
Left stretch tensor from Polar decomposition of T. 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] return _polar_decomposition(T,'V')[0]
def stretch_right(T: _np.ndarray) -> _np.ndarray: def stretch_right(T: _np.ndarray) -> _np.ndarray:
""" r"""
Calculate right stretch of a tensor. Calculate right stretch of a tensor.
Parameters Parameters
@ -243,6 +300,17 @@ def stretch_right(T: _np.ndarray) -> _np.ndarray:
U : numpy.ndarray, shape (...,3,3) U : numpy.ndarray, shape (...,3,3)
Left stretch tensor from Polar decomposition of T. 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] return _polar_decomposition(T,'U')[0]
@ -257,8 +325,13 @@ def _polar_decomposition(T: _np.ndarray,
T : numpy.ndarray, shape (...,3,3) T : numpy.ndarray, shape (...,3,3)
Tensor of which the singular values are computed. Tensor of which the singular values are computed.
requested : sequence of {'R', 'U', 'V'} requested : sequence of {'R', 'U', 'V'}
Requested outputs: R for the rotation tensor, Requested outputs: 'R' for the rotation tensor,
V for left stretch tensor, and U for right stretch tensor. 'V' for left stretch tensor, and 'U' for right stretch tensor.
Returns
-------
VRU : tuple of numpy.ndarray, shape (...,3,3)
Requested components of the singular value decomposition.
""" """
u, _, vh = _np.linalg.svd(T) u, _, vh = _np.linalg.svd(T)
@ -290,6 +363,11 @@ def _equivalent_Mises(T_sym: _np.ndarray,
s : float s : float
Scaling factor (2/3 for strain, 3/2 for stress). 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) d = _tensor.deviatoric(T_sym)
return _np.sqrt(s*_np.sum(d**2.0,axis=(-1,-2))) return _np.sqrt(s*_np.sum(d**2.0,axis=(-1,-2)))

View File

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

View File

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

View File

@ -8,8 +8,9 @@ import shlex as _shlex
import re as _re import re as _re
import signal as _signal import signal as _signal
import fractions as _fractions import fractions as _fractions
from collections import abc as _abc from collections import abc as _abc, OrderedDict as _OrderedDict
from functools import reduce as _reduce, partial as _partial 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, \ 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, \ Dict as _Dict, List as _List, Tuple as _Tuple, Literal as _Literal, \
Any as _Any, TextIO as _TextIO Any as _Any, TextIO as _TextIO
@ -540,10 +541,11 @@ def shapeblender(a: _Tuple[int, ...],
def _docstringer(docstring: _Union[str, _Callable], def _docstringer(docstring: _Union[str, _Callable],
extra_parameters: _Optional[str] = None, adopted_parameters: _Union[None, str, _Callable] = None,
# extra_examples: _Optional[str] = None, adopted_return: _Union[None, str, _Callable] = None,
# extra_notes: _Optional[str] = None, adopted_notes: _Union[None, str, _Callable] = None,
return_type: _Union[None, str, _Callable] = None) -> str: adopted_examples: _Union[None, str, _Callable] = None,
adopted_references: _Union[None, str, _Callable] = None) -> str:
""" """
Extend a docstring. Extend a docstring.
@ -551,50 +553,85 @@ def _docstringer(docstring: _Union[str, _Callable],
---------- ----------
docstring : str or callable, optional docstring : str or callable, optional
Docstring (of callable) to extend. Docstring (of callable) to extend.
extra_parameters : str, optional adopted_* : str or callable, optional
Additional information to append to Parameters section. Additional information to insert into/append to respective section.
return_type : str or callable, optional
Type of return variable. Notes
-----
adopted_return fetches the typehint of a passed function instead of the docstring
""" """
docstring_ = str( docstring if isinstance(docstring,str) docstring_: str = str( docstring if isinstance(docstring,str)
else docstring.__doc__ if hasattr(docstring,'__doc__') else docstring.__doc__ if callable(docstring) and docstring.__doc__
else '') else '').rstrip()+'\n'
d = dict(Parameters=extra_parameters, sections = _OrderedDict(
# Examples=extra_examples, Parameters=adopted_parameters,
# Notes=extra_notes, Returns=adopted_return,
) Examples=adopted_examples,
for key,extra in [(k,v) for (k,v) in d.items() if v is not None]: Notes=adopted_notes,
if not (heading := _re.search(fr'^([ ]*){key}\s*\n\1{"-"*len(key)}', References=adopted_references)
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)
if return_type is None: for i, (key, adopted) in [(i,(k,v)) for (i,(k,v)) in enumerate(sections.items()) if v is not None]:
return docstring_ section_regex = fr'^([ ]*){key}\s*\n\1*{"-"*len(key)}\s*\n'
else: if key=='Returns':
if isinstance(return_type,str): if callable(adopted):
return_type_ = return_type 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: else:
return_class = return_type.__annotations__.get('return','') section_content_regex = fr'{section_regex}(?P<content>.*?)\n *(\n|\Z)'
return_type_ = (_sys.modules[return_type.__module__].__name__.split('.')[0] adopted_: str = adopted.__doc__ if callable(adopted) else adopted #type: ignore
+'.' try:
+(return_class.__name__ if not isinstance(return_class,str) else return_class) 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, def extend_docstring(docstring: _Union[None, str, _Callable] = None,
extra_parameters: _Optional[str] = None) -> _Callable: **kwargs) -> _Callable:
""" """
Decorator: Extend the function's docstring. 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 : str or callable, optional
Docstring to extend. Defaults to that of decorated function. Docstring to extend. Defaults to that of decorated function.
extra_parameters : str, optional adopted_* : str or callable, optional
Additional information to append to Parameters section. Additional information to insert into/append to respective section.
Notes Notes
----- -----
@ -611,13 +648,55 @@ def extend_docstring(docstring: _Union[None, str, _Callable] = None,
""" """
def _decorator(func): def _decorator(func):
if 'adopted_return' not in kwargs: kwargs['adopted_return'] = func
func.__doc__ = _docstringer(func.__doc__ if docstring is None else docstring, func.__doc__ = _docstringer(func.__doc__ if docstring is None else docstring,
extra_parameters, **kwargs)
func if isinstance(docstring,_Callable) else None,
)
return func return func
return _decorator 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: def DREAM3D_base_group(fname: _Union[str, _Path]) -> str:
""" """

View File

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

View File

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

View File

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