Merge remote-tracking branch 'origin/development' into 256-grid-geometry-displacement-reconstruction

This commit is contained in:
Martin Diehl 2023-06-04 14:26:31 +02:00
commit 5b8194d8eb
295 changed files with 3628 additions and 2936 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.1' 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'
@ -11,14 +11,14 @@ env:
jobs: jobs:
gcc: gcc_ubuntu:
runs-on: ${{ matrix.os }} runs-on: ubuntu-22.04
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-latest] gcc_v: [9, 10, 11, 12]
gcc_v: [9, 10, 11] # Version of GCC compilers fail-fast: false
env: env:
GCC_V: ${{ matrix.gcc_v }} GCC_V: ${{ matrix.gcc_v }}
@ -27,8 +27,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: GCC - Install (Linux) - name: GCC - Install
if: contains( matrix.os, 'ubuntu')
run: | run: |
sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt-get update sudo apt-get update
@ -38,12 +37,6 @@ jobs:
--slave /usr/bin/g++ g++ /usr/bin/g++-${GCC_V} \ --slave /usr/bin/g++ g++ /usr/bin/g++-${GCC_V} \
--slave /usr/bin/gcov gcov /usr/bin/gcov-${GCC_V} --slave /usr/bin/gcov gcov /usr/bin/gcov-${GCC_V}
- name: GCC - Install (macOS)
if: contains( matrix.os, 'macos')
run: |
brew install gcc@${GCC_V} || brew upgrade gcc@${GCC_V} || true
brew link gcc@${GCC_V}
- name: PETSc - Cache download - name: PETSc - Cache download
id: petsc-download id: petsc-download
uses: actions/cache@v3 uses: actions/cache@v3
@ -63,28 +56,19 @@ jobs:
export PETSC_ARCH=gcc${GCC_V} export PETSC_ARCH=gcc${GCC_V}
printenv >> $GITHUB_ENV printenv >> $GITHUB_ENV
- name: PETSc - Cache installation - name: PETSc - Cache Installation
id: petsc-install id: petsc-install
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
path: petsc-${{ env.PETSC_VERSION }} path: petsc-${{ env.PETSC_VERSION }}
key: petsc-${{ env.PETSC_VERSION }}-${{ matrix.os }}-gcc${{ matrix.gcc_v }}-${{ hashFiles('**/petscversion.h') }} key: petsc-${{ env.PETSC_VERSION }}-gcc${{ matrix.gcc_v }}-${{ hashFiles('**/petscversion.h') }}
- name: PETSc - Install (Linux) - name: PETSc - Installation
if: contains( matrix.os, 'ubuntu')
run: | run: |
cd petsc-${PETSC_VERSION} cd petsc-${PETSC_VERSION}
./configure --with-fc=gfortran --with-cc=gcc --with-cxx=g++ \ ./configure --with-fc=gfortran --with-cc=gcc --with-cxx=g++ \
--download-mpich --download-fftw --download-hdf5 --download-hdf5-fortran-bindings=1 --download-zlib \ --download-openmpi --download-fftw --download-hdf5 --download-hdf5-fortran-bindings=1 --download-zlib \
--with-mpi-f90module-visibility=0 --with-mpi-f90module-visibility=1
make all
- name: PETSc - Install (macOS)
if: contains( matrix.os, 'macos')
run: |
cd petsc-${PETSC_VERSION}
./configure --with-fc=gfortran-${GCC_V} --with-cc=gcc-${GCC_V} --with-cxx=g++-${GCC_V} \
--download-openmpi --download-fftw --download-hdf5 --download-hdf5-fortran-bindings=1 --download-zlib
make all make all
- name: DAMASK - Compile - name: DAMASK - Compile
@ -99,6 +83,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 -w examples/grid
./bin/DAMASK_mesh -h
intel: intel:
@ -107,6 +93,7 @@ jobs:
strategy: strategy:
matrix: matrix:
intel_v: [classic, llvm] # Variant of Intel compilers intel_v: [classic, llvm] # Variant of Intel compilers
fail-fast: false
env: env:
INTEL_V: ${{ matrix.intel_v }} INTEL_V: ${{ matrix.intel_v }}
@ -143,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 "1715s/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}
@ -179,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
@ -187,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 -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

@ -181,13 +181,13 @@ open-source:
script: script:
- module load ${COMPILER_INTEL} ${MPI_INTEL} ${PETSC_INTEL} - module load ${COMPILER_INTEL} ${MPI_INTEL} ${PETSC_INTEL}
- cd PRIVATE/testing/pytest - cd PRIVATE/testing/pytest
- pytest -k 'not compile and not Marc' --basetemp ${TESTROOT}/open-source -v - pytest -k 'not compile and not Marc' -m 'not cifail' --basetemp ${TESTROOT}/open-source -v
Marc: Marc:
stage: fortran stage: fortran
script: script:
- cd PRIVATE/testing/pytest - cd PRIVATE/testing/pytest
- pytest -k 'not compile and Marc' --damask-root=${TESTROOT} --basetemp ${TESTROOT}/Marc -v - pytest -k 'not compile and Marc' -m 'not cifail' --damask-root=${TESTROOT} --basetemp ${TESTROOT}/Marc -v
# Needs closer look # Needs closer look
# Phenopowerlaw_singleSlip: # Phenopowerlaw_singleSlip:

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()
@ -90,14 +92,21 @@ endif()
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
if (CMAKE_Fortran_COMPILER_ID STREQUAL "Intel") if (CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
include(Compiler-Intel)
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
include(Compiler-GNU) include(Compiler-GNU)
set(Fortran_COMPILER_VERSION_MIN 9.1)
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "Intel")
include(Compiler-Intel)
set(Fortran_COMPILER_VERSION_MIN 19)
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "IntelLLVM") elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "IntelLLVM")
include(Compiler-IntelLLVM) include(Compiler-IntelLLVM)
set(Fortran_COMPILER_VERSION_MIN 19)
else() else()
message(FATAL_ERROR "Compiler type(CMAKE_Fortran_COMPILER_ID) not recognized") message(FATAL_ERROR "Compiler '${CMAKE_Fortran_COMPILER_ID}' not supported")
endif()
if(CMAKE_Fortran_COMPILER_VERSION VERSION_LESS Fortran_COMPILER_VERSION_MIN)
message(FATAL_ERROR "Version '${CMAKE_Fortran_COMPILER_VERSION}' of '${CMAKE_Fortran_COMPILER_ID}' is not supported")
endif() endif()
file(STRINGS "$ENV{PETSC_DIR}/$ENV{PETSC_ARCH}/lib/petsc/conf/petscvariables" PETSC_EXTERNAL_LIB REGEX "PETSC_EXTERNAL_LIB_BASIC = .*$?") file(STRINGS "$ENV{PETSC_DIR}/$ENV{PETSC_ARCH}/lib/petsc/conf/petscvariables" PETSC_EXTERNAL_LIB REGEX "PETSC_EXTERNAL_LIB_BASIC = .*$?")

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 547bfe56d67cba358a7bb9582f2f7c0e344befd8 Subproject commit 22a23a9d5939d49d9d277c7066d9b68003a33324

View File

@ -1 +1 @@
3.0.0-alpha7-297-g22de899aa 3.0.0-alpha7-534-g51210a05e

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

@ -1,7 +1,7 @@
type: Hooke type: Hooke
references: references:
- Wang et al., - Z. Wang et al.,
Materials Science and Engineering:A 674:406-412, 2016, Materials Science and Engineering:A 674:406-412, 2016,
https://doi.org/10.1016/j.msea.2016.08.010, https://doi.org/10.1016/j.msea.2016.08.010,
fit to Tab. 2 (last 3 rows) fit to Tab. 2 (last 3 rows)

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

@ -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

@ -0,0 +1,19 @@
type: phenopowerlaw
references:
- T.J. Barrett and M. Knezevic,
Computer Methods in Applied Mechanics and Engineering 354:245-270, 2019,
https://doi.org/10.1016/j.cma.2019.05.035,
fitted to data shown in Fig 1 and Fig. 2a
output: [xi_sl, gamma_sl]
N_sl: [12]
n_sl: 20
a_sl: 3.7
h_0_sl-sl: 1.02e+9
xi_0_sl: [76.e+6]
xi_inf_sl: [266.e+6]
h_sl-sl: [1, 1, 5.123, 0.574, 1.123, 1.123, 1]
dot_gamma_0_sl: 0.001

View File

@ -4,19 +4,16 @@ references:
- W.F. Hosford et al., - W.F. Hosford et al.,
Acta Metallurgica 8(3):187-199, 1960, Acta Metallurgica 8(3):187-199, 1960,
https://doi.org/10.1016/0001-6160(60)90127-9, https://doi.org/10.1016/0001-6160(60)90127-9,
fitted from Fig. 5 fitted to Fig. 5 ([111] and [001])
- U.F. Kocks,
Metallurgical and Materials Transactions B 1:11211143, 1970,
https://doi.org/10.1007/BF02900224
output: [xi_sl, gamma_sl] output: [xi_sl, gamma_sl]
N_sl: [12] N_sl: [12]
n_sl: 20 n_sl: 20
a_sl: 3.1 a_sl: 5.4
h_0_sl-sl: 1.7e+8 h_0_sl-sl: 281.5e+6
xi_0_sl: [5.0e+6] xi_0_sl: [2.69e+6]
xi_inf_sl: [37.5e+6] xi_inf_sl: [67.5e+6]
h_sl-sl: [1, 1, 1.4, 1.4, 1.4, 1.4, 1.4] h_sl-sl: [1, 1, 5.123, 0.574, 1.123, 1.123, 1]
dot_gamma_0_sl: 7.5e-5 dot_gamma_0_sl: 7.5e-5

View File

@ -1,22 +1,19 @@
type: phenopowerlaw type: phenopowerlaw
references: references:
- T Takeuchi, - T. Takeuchi,
Transactions of the Japan Institute of Metals 16(10):629-640, 1975, Transactions of the Japan Institute of Metals 16(10):629-640, 1975,
https://doi.org/10.2320/matertrans1960.16.629, https://doi.org/10.2320/matertrans1960.16.629,
fitted from Fig. 3b fitted to Fig. 3b ([111] and [001])
- U.F. Kocks,
Metallurgical and Materials Transactions B 1:11211143, 1970,
https://doi.org/10.1007/BF02900224
output: [xi_sl, gamma_sl] output: [xi_sl, gamma_sl]
N_sl: [12] N_sl: [12]
n_sl: 20 n_sl: 20
a_sl: 1.0 a_sl: 0.6
h_0_sl-sl: 2.4e+8 h_0_sl-sl: 3.5e+8
xi_0_sl: [1.5e+6] xi_0_sl: [1.6e+6]
xi_inf_sl: [112.5e+6] xi_inf_sl: [96.4e+6]
h_sl-sl: [1, 1, 1.4, 1.4, 1.4, 1.4, 1.4] h_sl-sl: [1, 1, 5.123, 0.574, 1.123, 1.123, 1]
dot_gamma_0_sl: 3.e-3 dot_gamma_0_sl: 3.e-3

View File

@ -4,17 +4,16 @@ references:
- K.M. Jackson and C. Lang, - K.M. Jackson and C. Lang,
Platinum Metals Review 50:15-19, 2006, Platinum Metals Review 50:15-19, 2006,
https://doi.org/10.1595/147106705X93359, https://doi.org/10.1595/147106705X93359,
fitted from Fig. 5 (Pt-5% Cu recrystallised) fitted to Fig. 5 (Pt-5% Cu recrystallised)
- U.F. Kocks,
Metallurgical and Materials Transactions B 1:11211143, 1970, output: [xi_sl, gamma_sl]
https://doi.org/10.1007/BF02900224
N_sl: [12] N_sl: [12]
n_sl: 1.6 n_sl: 20
a_sl: 0.8 a_sl: 0.9
h_0_sl-sl: 300.0e+6 h_0_sl-sl: 781.2e+6
xi_0_sl: [150.0e+6] xi_0_sl: [114.e+6]
xi_inf_sl: [500.0e+6] xi_inf_sl: [207.e+6]
h_sl-sl: [1, 1, 1.4, 1.4, 1.4, 1.4, 1.4] h_sl-sl: [1, 1, 5.123, 0.574, 1.123, 1.123, 1]
dot_gamma_0_sl: 0.0001 dot_gamma_0_sl: 0.001

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

@ -2,6 +2,7 @@ import copy
from io import StringIO from io import StringIO
from collections.abc import Iterable from collections.abc import Iterable
import abc import abc
import platform
from typing import Optional, Union, Dict, Any, Type, TypeVar from typing import Optional, Union, Dict, Any, Type, TypeVar
import numpy as np import numpy as np
@ -43,7 +44,7 @@ class NiceDumper(SafeDumper):
return self.represent_data(data.tolist()) return self.represent_data(data.tolist())
if isinstance(data, Rotation): if isinstance(data, Rotation):
return self.represent_data(data.quaternion.tolist()) return self.represent_data(data.quaternion.tolist())
if hasattr(data, 'dtype'): if isinstance(data, np.generic):
return self.represent_data(data.item()) return self.represent_data(data.item())
return super().represent_data(data) return super().represent_data(data)
@ -57,15 +58,42 @@ class Config(dict):
"""YAML-based configuration.""" """YAML-based configuration."""
def __init__(self, def __init__(self,
yml: Union[None, str, Dict[str, Any]] = None, config: Optional[Union[str, Dict[str, Any]]] = None,
**kwargs): **kwargs):
"""Initialize from YAML, dict, or key=value pairs.""" """
if isinstance(yml,str): New YAML-based configuration.
kwargs.update(yaml.load(yml, Loader=SafeLoader))
elif isinstance(yml,dict): Parameters
kwargs.update(yml) ----------
config : dict or str, optional
Configuration. String needs to be valid YAML.
**kwargs: arbitray keyword-value pairs, optional
Top level entries of the configuration.
Notes
-----
Values given as keyword-value pairs take precedence
over entries with the same keyword in 'config'.
"""
if int(platform.python_version_tuple()[1]) >= 9:
if isinstance(config,str):
kwargs = yaml.load(config, Loader=SafeLoader) | kwargs
elif isinstance(config,dict):
kwargs = config | kwargs # type: ignore
super().__init__(**kwargs)
else:
if isinstance(config,str):
c = yaml.load(config, Loader=SafeLoader)
elif isinstance(config,dict):
c = config.copy()
else:
c = {}
c.update(kwargs)
super().__init__(**c)
super().__init__(**kwargs)
def __repr__(self) -> str: def __repr__(self) -> str:
""" """

View File

@ -1,12 +1,14 @@
from typing import Optional, Union, Sequence, Dict, Any, List
import numpy as np import numpy as np
import h5py import h5py
from typing import Optional, Union, Sequence, Dict, Any, Collection
from ._typehints import FileHandle from ._typehints import FileHandle, FloatSequence, StrSequence
from . import Config from . import Config
from . import Rotation from . import Rotation
from . import Orientation from . import Orientation
from . import util from . import util
from . import tensor
from . import Table from . import Table
@ -22,33 +24,43 @@ class ConfigMaterial(Config):
""" """
def __init__(self, def __init__(self,
d: Optional[Dict[str, Any]] = None, config: Optional[Union[str,Dict[str,Any]]] = None,*,
**kwargs): homogenization: Optional[Dict[str,Dict]] = None,
phase: Optional[Dict[str,Dict]] = None,
material: Optional[List[Dict[str,Any]]] = None):
""" """
New material configuration. New material configuration.
Parameters Parameters
---------- ----------
d : dictionary or YAML string, optional config : dict or str, optional
Initial content. Defaults to None, in which case empty entries for Material configuration. String needs to be valid YAML.
any missing material, homogenization, and phase entry are created. homogenization : dict, optional
kwargs : key=value pairs, optional Homogenization configuration.
Initial content specified as pairs of key=value. Defaults to an empty dict if 'config' is not given.
phase : dict, optional
Phase configuration.
Defaults to an empty dict if 'config' is not given.
material : dict, optional
Materialpoint configuration.
Defaults to an empty list if 'config' is not given.
""" """
default: Collection kwargs: Dict[str,Union[Dict[str,Dict],List[Dict[str,Any]]]] = {}
if d is None: for arg,value in zip(['homogenization','phase','material'],[homogenization,phase,material]):
for section, default in {'material':[],'homogenization':{},'phase':{}}.items(): if value is None and config is None:
if section not in kwargs: kwargs.update({section:default}) kwargs[arg] = [] if arg == 'material' else {}
elif value is not None:
kwargs[arg] = value
super().__init__(d,**kwargs) super().__init__(config,**kwargs)
def save(self, def save(self,
fname: FileHandle = 'material.yaml', fname: FileHandle = 'material.yaml',
**kwargs): **kwargs):
""" """
Save to yaml file. Save to YAML file.
Parameters Parameters
---------- ----------
@ -65,7 +77,7 @@ class ConfigMaterial(Config):
def load(cls, def load(cls,
fname: FileHandle = 'material.yaml') -> 'ConfigMaterial': fname: FileHandle = 'material.yaml') -> 'ConfigMaterial':
""" """
Load from yaml file. Load from YAML file.
Parameters Parameters
---------- ----------
@ -168,8 +180,12 @@ class ConfigMaterial(Config):
@staticmethod @staticmethod
def from_table(table: Table, def from_table(table: Table,*,
**kwargs) -> 'ConfigMaterial': homogenization: Optional[Union[str,StrSequence]] = None,
phase: Optional[Union[str,StrSequence]] = None,
v: Optional[Union[str,FloatSequence]] = None,
O: Optional[Union[str,FloatSequence]] = None,
V_e: Optional[Union[str,FloatSequence]] = None) -> 'ConfigMaterial':
""" """
Generate from an ASCII table. Generate from an ASCII table.
@ -177,16 +193,33 @@ class ConfigMaterial(Config):
---------- ----------
table : damask.Table table : damask.Table
Table that contains material information. Table that contains material information.
**kwargs homogenization: (array-like) of str, optional
Keyword arguments where the key is the property name and Homogenization label.
the value specifies either the label of the data column in the table phase: (array-like) of str, optional
or a constant value. Phase label (per constituent).
v: (array-like) of float or str, optional
Constituent volume fraction (per constituent).
Defaults to 1/N_constituent.
O: (array-like) of damask.Rotation or np.array/list of shape(4) or str, optional
Orientation as unit quaternion (per constituent).
V_e: (array-like) of np.array/list of shape(3,3) or str, optional
Left elastic stretch (per constituent).
Returns Returns
------- -------
new : damask.ConfigMaterial new : damask.ConfigMaterial
Material configuration from values in table. Material configuration from values in table.
Notes
-----
If the value of an argument is a string that is a column label,
data from the table is used to fill the corresponding entry in
the material configuration. Otherwise, the value is used directly.
First index of array-like values that are defined per constituent
runs over materials, whereas second index runs over constituents.
Examples Examples
-------- --------
>>> import damask >>> import damask
@ -228,15 +261,16 @@ class ConfigMaterial(Config):
phase: {Aluminum: null, Steel: null} phase: {Aluminum: null, Steel: null}
""" """
kwargs_ = {k:table.get(v) if v in table.labels else np.atleast_2d([v]*len(table)).T for k,v in kwargs.items()} kwargs = {}
for arg,val in zip(['homogenization','phase','v','O','V_e'],[homogenization,phase,v,O,V_e]):
if val is not None:
kwargs[arg] = table.get(val) if val in table.labels else np.atleast_2d([val]*len(table)).T # type: ignore
_,idx = np.unique(np.hstack(list(kwargs_.values())),return_index=True,axis=0) _,idx = np.unique(np.hstack(list(kwargs.values())),return_index=True,axis=0)
idx = np.sort(idx) idx = np.sort(idx)
kwargs_ = {k:np.atleast_1d(v[idx].squeeze()) for k,v in kwargs_.items()} kwargs = {k:np.atleast_1d(v[idx].squeeze()) for k,v in kwargs.items()}
for what in ['phase','homogenization']:
if what not in kwargs_: kwargs_[what] = what+'_label'
return ConfigMaterial().material_add(**kwargs_) return ConfigMaterial().material_add(**kwargs)
@property @property
@ -361,7 +395,7 @@ class ConfigMaterial(Config):
Parameters Parameters
---------- ----------
mapping: dictionary mapping: dict
Mapping from old name to new name Mapping from old name to new name
ID: list of ints, optional ID: list of ints, optional
Limit renaming to selected material IDs. Limit renaming to selected material IDs.
@ -394,7 +428,7 @@ class ConfigMaterial(Config):
Parameters Parameters
---------- ----------
mapping: dictionary mapping: dict
Mapping from old name to new name Mapping from old name to new name
ID: list of ints, optional ID: list of ints, optional
Limit renaming to selected homogenization IDs. Limit renaming to selected homogenization IDs.
@ -416,11 +450,11 @@ class ConfigMaterial(Config):
def material_add(self,*, def material_add(self,*,
homogenization: Any = None, homogenization: Optional[Union[str,StrSequence]] = None,
phase: Any = None, phase: Optional[Union[str,StrSequence]] = None,
v: Any = None, v: Optional[Union[float,FloatSequence]] = None,
O: Any = None, O: Optional[Union[float,FloatSequence]] = None,
V_e: Any = None) -> 'ConfigMaterial': V_e: Optional[Union[float,FloatSequence]] = None) -> 'ConfigMaterial':
""" """
Add material entries. Add material entries.
@ -432,6 +466,7 @@ class ConfigMaterial(Config):
Phase label (per constituent). Phase label (per constituent).
v: (array-like) of float, optional v: (array-like) of float, optional
Constituent volume fraction (per constituent). Constituent volume fraction (per constituent).
Defaults to 1/N_constituent.
O: (array-like) of damask.Rotation or np.array/list of shape(4), optional O: (array-like) of damask.Rotation or np.array/list of shape(4), optional
Orientation as unit quaternion (per constituent). Orientation as unit quaternion (per constituent).
V_e: (array-like) of np.array/list of shape(3,3), optional V_e: (array-like) of np.array/list of shape(3,3), optional
@ -444,9 +479,8 @@ class ConfigMaterial(Config):
Notes Notes
----- -----
First index of array-like values that are defined per First index of array-like values that are defined per constituent
consituent runs over materials, whereas second index runs runs over materials, whereas second index runs over constituents.
over constituents.
Examples Examples
-------- --------
@ -525,49 +559,48 @@ class ConfigMaterial(Config):
phase: {Austenite: null, Ferrite: null} phase: {Austenite: null, Ferrite: null}
""" """
kwargs = {} dim = {'O':(4,),'V_e':(3,3,)}
for keyword,value in zip(['homogenization','phase','v','O','V_e'],[homogenization,phase,v,O,V_e]): ex = dict((keyword, -len(val)) for keyword,val in dim.items())
if value is not None: kwargs[keyword] = value
_constituent_properties = ['phase','O','v','V_e'] N_materials,N_constituents = 1,1
_dim = {'O':(4,),'V_e':(3,3,)} shape = {}
_ex = dict((k, -len(v)) for k, v in _dim.items()) for arg,val in zip(['homogenization','phase','v','O','V_e'],[homogenization,phase,v,O,V_e]):
if val is None: continue
shape[arg] = np.array(val)
s = shape[arg].shape[:ex.get(arg,None)] # type: ignore
N_materials = max(N_materials,s[0]) if len(s)>0 else N_materials
N_constituents = max(N_constituents,s[1]) if len(s)>1 else N_constituents
N,n = 1,1 shape['v'] = np.array(shape.get('v',1./N_constituents),float)
shaped : Dict[str, Union[None,np.ndarray]] = \
{'v': None,
'phase': None,
'homogenization': None,
}
for k,v in kwargs.items(): mat: Sequence[dict] = [{'constituents':[{} for _ in range(N_constituents)]} for _ in range(N_materials)]
shaped[k] = np.array(v)
s = shaped[k].shape[:_ex.get(k,None)] # type: ignore
N = max(N,s[0]) if len(s)>0 else N
n = max(n,s[1]) if len(s)>1 else n
shaped['v'] = np.array(1./n) if shaped['v'] is None else shaped['v'] for k,v in shape.items():
target = (N_materials,N_constituents) + dim.get(k,())
mat: Sequence[dict] = [{'constituents':[{} for _ in range(n)]} for _ in range(N)] broadcasted = np.broadcast_to(np.array(v).reshape(util.shapeshifter(np.array(v).shape,target,'right')),target)
if k == 'v':
for k,v in shaped.items(): if np.min(broadcasted) < 0 or np.max(broadcasted) > 1:
target = (N,n) + _dim.get(k,()) raise ValueError('volume fraction "v" out of range')
obj = np.broadcast_to(np.array(v).reshape(util.shapeshifter(() if v is None else v.shape, if len(np.atleast_1d(broadcasted)) > 1:
target, total = np.sum(broadcasted,axis=-1)
mode = 'right')), if np.min(total) < 0 or np.max(total) > 1:
target) raise ValueError('volume fraction "v" out of range')
for i in range(N): if k == 'O' and not np.allclose(1.0,np.linalg.norm(broadcasted,axis=-1)):
if k in _constituent_properties: raise ValueError('orientation "O" is not a unit quaterion')
for j in range(n): elif k == 'V_e' and not np.allclose(broadcasted,tensor.symmetric(broadcasted)):
mat[i]['constituents'][j][k] = obj[i,j].item() if isinstance(obj[i,j],np.generic) else obj[i,j] raise ValueError('elastic stretch "V_e" is not symmetric')
for i in range(N_materials):
if k == 'homogenization':
mat[i][k] = broadcasted[i,0]
else: else:
mat[i][k] = obj[i,0].item() if isinstance(obj[i,0],np.generic) else obj[i,0] for j in range(N_constituents):
mat[i]['constituents'][j][k] = broadcasted[i,j]
dup = self.copy() dup = self.copy()
dup['material'] = dup['material'] + mat if 'material' in dup else mat dup['material'] = dup['material'] + mat if 'material' in dup else mat
for what in [item for item in ['phase','homogenization'] if shaped[item] is not None]: for what in [item for item in ['phase','homogenization'] if item in shape]:
for k in np.unique(shaped[what]): # type: ignore for k in np.unique(shape[what]): # type: ignore
if k not in dup[what]: dup[what][str(k)] = None if k not in dup[what]: dup[what][str(k)] = None
return dup return dup

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
@ -65,9 +65,9 @@ def _empty_like(dataset: np.ma.core.MaskedArray,
class Result: class Result:
""" """
Add data to and export data from a DADF5 file. Add data to and export data from a DADF5 (DAMASK HDF5) file.
A DADF5 (DAMASK HDF5) file contains DAMASK results. A DADF5 file contains DAMASK results.
Its group/folder structure reflects the layout in material.yaml. Its group/folder structure reflects the layout in material.yaml.
This class provides a customizable view on the DADF5 file. This class provides a customizable view on the DADF5 file.
@ -93,7 +93,7 @@ class Result:
def __init__(self, fname: Union[str, Path]): def __init__(self, fname: Union[str, Path]):
""" """
New result view bound to a HDF5 file. New result view bound to a DADF5 file.
Parameters Parameters
---------- ----------
@ -106,10 +106,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: 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()
@ -167,7 +165,7 @@ class Result:
""" """
Return repr(self). Return repr(self).
Give short human-readable summary. Give short, human-readable summary.
""" """
with h5py.File(self.fname,'r') as f: with h5py.File(self.fname,'r') as f:
@ -195,7 +193,7 @@ class Result:
homogenizations: Union[None, str, Sequence[str], bool] = None, homogenizations: Union[None, str, Sequence[str], bool] = None,
fields: Union[None, str, Sequence[str], bool] = None) -> "Result": fields: Union[None, str, Sequence[str], bool] = None) -> "Result":
""" """
Manages the visibility of the groups. Manage the visibility of the groups.
Parameters Parameters
---------- ----------
@ -319,15 +317,15 @@ class Result:
Parameters Parameters
---------- ----------
increments: (list of) int, (list of) str, or bool, optional. increments: (list of) int, (list of) str, or bool, optional.
Number(s) of increments to select. Numbers of increments to select.
times: (list of) float, (list of) str, or bool, optional. times: (list of) float, (list of) str, or bool, optional.
Simulation time(s) of increments to select. Simulation times of increments to select.
phases: (list of) str, or bool, optional. phases: (list of) str, or bool, optional.
Name(s) of phases to select. Names of phases to select.
homogenizations: (list of) str, or bool, optional. homogenizations: (list of) str, or bool, optional.
Name(s) of homogenizations to select. Names of homogenizations to select.
fields: (list of) str, or bool, optional. fields: (list of) str, or bool, optional.
Name(s) of fields to select. Names of fields to select.
protected: bool, optional. protected: bool, optional.
Protection status of existing data. Protection status of existing data.
@ -375,15 +373,15 @@ class Result:
Parameters Parameters
---------- ----------
increments: (list of) int, (list of) str, or bool, optional. increments: (list of) int, (list of) str, or bool, optional.
Number(s) of increments to select. Numbers of increments to select.
times: (list of) float, (list of) str, or bool, optional. times: (list of) float, (list of) str, or bool, optional.
Simulation time(s) of increments to select. Simulation times of increments to select.
phases: (list of) str, or bool, optional. phases: (list of) str, or bool, optional.
Name(s) of phases to select. Names of phases to select.
homogenizations: (list of) str, or bool, optional. homogenizations: (list of) str, or bool, optional.
Name(s) of homogenizations to select. Names of homogenizations to select.
fields: (list of) str, or bool, optional. fields: (list of) str, or bool, optional.
Name(s) of fields to select. Names of fields to select.
Returns Returns
------- -------
@ -418,15 +416,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
------- -------
@ -678,7 +676,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
@ -721,9 +719,11 @@ class Result:
Parameters Parameters
---------- ----------
P : str, optional P : str, optional
Name of the dataset containing the first Piola-Kirchhoff stress. Defaults to 'P'. Name of the dataset containing the first Piola-Kirchhoff stress.
Defaults to 'P'.
F : str, optional F : str, optional
Name of the dataset containing the deformation gradient. Defaults to 'F'. Name of the dataset containing the deformation gradient.
Defaults to 'F'.
""" """
self._add_generic_pointwise(self._add_stress_Cauchy,{'P':P,'F':F}) self._add_generic_pointwise(self._add_stress_Cauchy,{'P':P,'F':F})
@ -1023,14 +1023,14 @@ class Result:
x: str, x: str,
ord: Union[None, int, float, Literal['fro', 'nuc']] = None): ord: Union[None, int, float, Literal['fro', 'nuc']] = None):
""" """
Add the norm of vector or tensor. Add the norm of a vector or tensor.
Parameters Parameters
---------- ----------
x : str x : str
Name of vector or tensor dataset. Name of vector or tensor dataset.
ord : {non-zero int, inf, -inf, 'fro', 'nuc'}, optional ord : {non-zero int, inf, -inf, 'fro', 'nuc'}, optional
Order of the norm. inf means NumPys inf object. For details refer to numpy.linalg.norm. Order of the norm. inf means NumPy's inf object. For details refer to numpy.linalg.norm.
""" """
self._add_generic_pointwise(self._add_norm,{'x':x},{'ord':ord}) self._add_generic_pointwise(self._add_norm,{'x':x},{'ord':ord})
@ -1052,7 +1052,7 @@ class Result:
def add_stress_second_Piola_Kirchhoff(self, def add_stress_second_Piola_Kirchhoff(self,
P: str = 'P', P: str = 'P',
F: str = 'F'): F: str = 'F'):
""" r"""
Add second Piola-Kirchhoff stress calculated from first Piola-Kirchhoff stress and deformation gradient. Add second Piola-Kirchhoff stress calculated from first Piola-Kirchhoff stress and deformation gradient.
Parameters Parameters
@ -1064,9 +1064,10 @@ class Result:
Notes Notes
----- -----
The definition of the second Piola-Kirchhoff stress (S = [F^-1 P]_sym) The definition of the second Piola-Kirchhoff stress
:math:`\vb{S} = \left(\vb{F}^{-1} \vb{P}\right)_\text{sym}`
follows the standard definition in nonlinear continuum mechanics. follows the standard definition in nonlinear continuum mechanics.
As such, no intermediate configuration, for instance that reached by F_p, As such, no intermediate configuration, for instance that reached by :math:`\vb{F}_\text{p}`,
is taken into account. is taken into account.
""" """
@ -1240,10 +1241,11 @@ class Result:
Notes Notes
----- -----
The incoporation of rotational parts into the elastic and plastic The presence of rotational parts in the elastic and plastic deformation gradient
deformation gradient requires it to use material/Lagragian strain measures calls for the use of
(based on 'U') for plastic strains and spatial/Eulerian strain measures material/Lagragian strain measures (based on 'U') for plastic strains and
(based on 'V') for elastic strains when calculating averages. spatial/Eulerian strain measures (based on 'V') for elastic strains
when calculating averages.
""" """
self._add_generic_pointwise(self._add_strain,{'F':F},{'t':t,'m':m}) self._add_generic_pointwise(self._add_strain,{'F':F},{'t':t,'m':m})
@ -1302,7 +1304,7 @@ class Result:
Notes Notes
----- -----
This function is only available for structured grids, This function is only available for structured grids,
i.e. results from the grid solver. i.e. fields resulting from the grid solver.
""" """
self._add_generic_grid(self._add_curl,{'f':f},{'size':self.size}) self._add_generic_grid(self._add_curl,{'f':f},{'size':self.size})
@ -1331,7 +1333,7 @@ class Result:
Notes Notes
----- -----
This function is only available for structured grids, This function is only available for structured grids,
i.e. results from the grid solver. i.e. fields resulting from the grid solver.
""" """
self._add_generic_grid(self._add_divergence,{'f':f},{'size':self.size}) self._add_generic_grid(self._add_divergence,{'f':f},{'size':self.size})
@ -1361,7 +1363,7 @@ class Result:
Notes Notes
----- -----
This function is only available for structured grids, This function is only available for structured grids,
i.e. results from the grid solver. i.e. fields resulting from the grid solver.
""" """
self._add_generic_grid(self._add_gradient,{'f':f},{'size':self.size}) self._add_generic_grid(self._add_gradient,{'f':f},{'size':self.size})
@ -1379,10 +1381,10 @@ class Result:
---------- ----------
func : function func : function
Callback function that calculates a new dataset from one or Callback function that calculates a new dataset from one or
more datasets per HDF5 group. more datasets per DADF5 group.
datasets : dictionary datasets : dictionary
Details of the datasets to be used: Details of the datasets to be used:
{arg (name to which the data is passed in func): label (in HDF5 file)}. {arg (name to which the data is passed in func): label (in DADF5 file)}.
args : dictionary, optional args : dictionary, optional
Arguments parsed to func. Arguments parsed to func.
@ -1462,10 +1464,10 @@ class Result:
---------- ----------
callback : function callback : function
Callback function that calculates a new dataset from one or Callback function that calculates a new dataset from one or
more datasets per HDF5 group. more datasets per DADF5 group.
datasets : dictionary datasets : dictionary
Details of the datasets to be used: Details of the datasets to be used:
{arg (name to which the data is passed in func): label (in HDF5 file)}. {arg (name to which the data is passed in func): label (in DADF5 file)}.
args : dictionary, optional args : dictionary, optional
Arguments parsed to func. Arguments parsed to func.
@ -1500,7 +1502,7 @@ class Result:
dataset.attrs['overwritten'] = True dataset.attrs['overwritten'] = True
else: else:
shape = result['data'].shape shape = result['data'].shape
if compress := (result['data'].size >= chunk_size*2): if compress := result['data'].size >= chunk_size*2:
chunks = (chunk_size//np.prod(shape[1:]),)+shape[1:] chunks = (chunk_size//np.prod(shape[1:]),)+shape[1:]
else: else:
chunks = shape chunks = shape
@ -1828,9 +1830,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
---------- ----------
@ -1922,7 +1925,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.
@ -1936,20 +1940,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)
@ -1961,7 +2006,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)}'\
@ -902,7 +902,8 @@ class Rotation:
return Rotation(Rotation._om2qu(om)) return Rotation(Rotation._om2qu(om))
@staticmethod @staticmethod
def from_matrix(R: np.ndarray) -> 'Rotation': def from_matrix(R: np.ndarray,
normalize: bool = False) -> 'Rotation':
""" """
Initialize from rotation matrix. Initialize from rotation matrix.
@ -910,13 +911,17 @@ class Rotation:
---------- ----------
R : numpy.ndarray, shape (...,3,3) R : numpy.ndarray, shape (...,3,3)
Rotation matrix with det(R) = 1 and R.T R = I. Rotation matrix with det(R) = 1 and R.T R = I.
normalize : bool, optional
Rescales rotation matrix to unit determinant. Defaults to False.
Returns Returns
------- -------
new : damask.Rotation new : damask.Rotation
""" """
return Rotation.from_basis(R) return Rotation.from_basis(np.array(R,dtype=float) * (np.linalg.det(R)**(-1./3.))[...,np.newaxis,np.newaxis]
if normalize else
R)
@staticmethod @staticmethod
def from_parallel(a: np.ndarray, def from_parallel(a: np.ndarray,

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

@ -8,6 +8,7 @@ import numpy as np
FloatSequence = Union[np.ndarray,Sequence[float]] FloatSequence = Union[np.ndarray,Sequence[float]]
IntSequence = Union[np.ndarray,Sequence[int]] IntSequence = Union[np.ndarray,Sequence[int]]
StrSequence = Union[np.ndarray,Sequence[str]]
FileHandle = Union[TextIO, str, Path] FileHandle = Union[TextIO, str, Path]
CrystalFamily = Union[None,Literal['triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic']] CrystalFamily = Union[None,Literal['triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic']]
CrystalLattice = Union[None,Literal['aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF']] CrystalLattice = Union[None,Literal['aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF']]

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

@ -402,7 +402,7 @@ def displacement_node(size: _FloatSequence,
Returns Returns
------- -------
u_p : numpy.ndarray, shape (:,:,:,3) u_n : numpy.ndarray, shape (:,:,:,3)
Nodal displacements. Nodal displacements.
""" """
@ -564,17 +564,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 +601,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:
""" """
@ -800,7 +879,7 @@ class ProgressBar:
prefix: str, prefix: str,
bar_length: int): bar_length: int):
""" """
Set current time as basis for ETA estimation. New progress bar.
Parameters Parameters
---------- ----------

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

@ -1 +0,0 @@
27972d6a0955e4e6e27a6ac5762abda8

View File

@ -1 +0,0 @@
dd71d25ccb52c3fdfd2ab727fc852a98

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