Merge remote-tracking branch 'origin/development' into 256-grid-geometry-displacement-reconstruction
This commit is contained in:
commit
5b8194d8eb
|
@ -12,12 +12,12 @@
|
|||
*.pbz2 binary
|
||||
|
||||
# ignore files from MSC.Marc in language statistics
|
||||
install/MarcMentat/** linguist-vendored
|
||||
src/Marc/include/* linguist-vendored
|
||||
install/MarcMentat/** linguist-vendored
|
||||
src/Marc/include/* linguist-vendored
|
||||
install/MarcMentat/MSC_modifications.py linguist-vendored=false
|
||||
|
||||
# ignore reference files for tests in language statistics
|
||||
python/tests/reference/** linguist-vendored
|
||||
python/tests/resources/** linguist-vendored
|
||||
|
||||
# ignore deprecated scripts
|
||||
processing/legacy/** linguist-vendored
|
||||
processing/legacy/** linguist-vendored
|
||||
|
|
|
@ -2,7 +2,7 @@ name: Grid and Mesh Solver
|
|||
on: [push]
|
||||
|
||||
env:
|
||||
PETSC_VERSION: '3.18.1'
|
||||
PETSC_VERSION: '3.18.4'
|
||||
HOMEBREW_NO_ANALYTICS: 'ON' # Make Homebrew installation a little quicker
|
||||
HOMEBREW_NO_AUTO_UPDATE: 'ON'
|
||||
HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK: 'ON'
|
||||
|
@ -11,14 +11,14 @@ env:
|
|||
|
||||
jobs:
|
||||
|
||||
gcc:
|
||||
gcc_ubuntu:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
gcc_v: [9, 10, 11] # Version of GCC compilers
|
||||
gcc_v: [9, 10, 11, 12]
|
||||
fail-fast: false
|
||||
|
||||
env:
|
||||
GCC_V: ${{ matrix.gcc_v }}
|
||||
|
@ -27,8 +27,7 @@ jobs:
|
|||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: GCC - Install (Linux)
|
||||
if: contains( matrix.os, 'ubuntu')
|
||||
- name: GCC - Install
|
||||
run: |
|
||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
|
||||
sudo apt-get update
|
||||
|
@ -38,12 +37,6 @@ jobs:
|
|||
--slave /usr/bin/g++ g++ /usr/bin/g++-${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
|
||||
id: petsc-download
|
||||
uses: actions/cache@v3
|
||||
|
@ -63,28 +56,19 @@ jobs:
|
|||
export PETSC_ARCH=gcc${GCC_V}
|
||||
printenv >> $GITHUB_ENV
|
||||
|
||||
- name: PETSc - Cache installation
|
||||
- name: PETSc - Cache Installation
|
||||
id: petsc-install
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
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)
|
||||
if: contains( matrix.os, 'ubuntu')
|
||||
- name: PETSc - Installation
|
||||
run: |
|
||||
cd petsc-${PETSC_VERSION}
|
||||
./configure --with-fc=gfortran --with-cc=gcc --with-cxx=g++ \
|
||||
--download-mpich --download-fftw --download-hdf5 --download-hdf5-fortran-bindings=1 --download-zlib \
|
||||
--with-mpi-f90module-visibility=0
|
||||
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
|
||||
--download-openmpi --download-fftw --download-hdf5 --download-hdf5-fortran-bindings=1 --download-zlib \
|
||||
--with-mpi-f90module-visibility=1
|
||||
make all
|
||||
|
||||
- name: DAMASK - Compile
|
||||
|
@ -99,6 +83,8 @@ jobs:
|
|||
- name: DAMASK - Run
|
||||
run: |
|
||||
./bin/DAMASK_grid -l tensionX.yaml -g 20grains16x16x16.vti -w examples/grid
|
||||
./bin/DAMASK_mesh -h
|
||||
|
||||
|
||||
intel:
|
||||
|
||||
|
@ -107,6 +93,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
intel_v: [classic, llvm] # Variant of Intel compilers
|
||||
fail-fast: false
|
||||
|
||||
env:
|
||||
INTEL_V: ${{ matrix.intel_v }}
|
||||
|
@ -143,7 +130,7 @@ jobs:
|
|||
- name: PETSc - Prepare
|
||||
run: |
|
||||
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
|
||||
export PETSC_DIR=${PWD}/petsc-${PETSC_VERSION}
|
||||
export PETSC_ARCH=intel-${INTEL_V}
|
||||
|
@ -179,6 +166,7 @@ jobs:
|
|||
make all
|
||||
|
||||
- name: DAMASK - Compile
|
||||
if: contains( matrix.intel_v, 'classic')
|
||||
run: |
|
||||
cmake -B build/grid -DDAMASK_SOLVER=grid -DCMAKE_INSTALL_PREFIX=${PWD}
|
||||
cmake --build build/grid --parallel
|
||||
|
@ -187,6 +175,19 @@ jobs:
|
|||
cmake --build build/mesh --parallel
|
||||
cmake --install build/mesh
|
||||
|
||||
# ifx has issue with openMP
|
||||
# https://community.intel.com/t5/Intel-Fortran-Compiler/ifx-ICE-and-SEGFAULT/m-p/1459877
|
||||
- name: DAMASK - Compile
|
||||
if: contains( matrix.intel_v, 'llvm')
|
||||
run: |
|
||||
cmake -B build/grid -DDAMASK_SOLVER=grid -DCMAKE_INSTALL_PREFIX=${PWD} -DOPENMP=OFF
|
||||
cmake --build build/grid --parallel
|
||||
cmake --install build/grid
|
||||
cmake -B build/mesh -DDAMASK_SOLVER=mesh -DCMAKE_INSTALL_PREFIX=${PWD} -DOPENMP=OFF
|
||||
cmake --build build/mesh --parallel
|
||||
cmake --install build/mesh
|
||||
|
||||
- name: DAMASK - Run
|
||||
run: |
|
||||
./bin/DAMASK_grid -l tensionX.yaml -g 20grains16x16x16.vti -w examples/grid
|
||||
./bin/DAMASK_mesh -h
|
||||
|
|
|
@ -9,7 +9,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.8', '3.9', '3.10']
|
||||
python-version: ['3.8', '3.9', '3.10', '3.11']
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
fail-fast: false
|
||||
|
||||
|
@ -75,7 +75,7 @@ jobs:
|
|||
run: >
|
||||
sudo apt-get update &&
|
||||
sudo apt-get remove mysql* &&
|
||||
sudo apt-get install python3-pandas python3-scipy python3-h5py python3-vtk7 python3-matplotlib python3-yaml -y
|
||||
sudo apt-get install python3-pandas python3-scipy python3-h5py python3-vtk9 python3-matplotlib python3-yaml -y
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
|
|
|
@ -181,13 +181,13 @@ open-source:
|
|||
script:
|
||||
- module load ${COMPILER_INTEL} ${MPI_INTEL} ${PETSC_INTEL}
|
||||
- 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:
|
||||
stage: fortran
|
||||
script:
|
||||
- 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
|
||||
# Phenopowerlaw_singleSlip:
|
||||
|
|
|
@ -11,7 +11,7 @@ endif()
|
|||
project(Prerequisites LANGUAGES)
|
||||
set(ENV{PKG_CONFIG_PATH} "$ENV{PETSC_DIR}/$ENV{PETSC_ARCH}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")
|
||||
pkg_check_modules(PETSC_MIN REQUIRED PETSc>=3.12.0 QUIET) #CMake does not support version range
|
||||
pkg_check_modules(PETSC REQUIRED PETSc<3.19.0)
|
||||
pkg_check_modules(PETSC REQUIRED PETSc<3.20.0)
|
||||
|
||||
pkg_get_variable(CMAKE_Fortran_COMPILER PETSc fcompiler)
|
||||
pkg_get_variable(CMAKE_C_COMPILER PETSc ccompiler)
|
||||
|
@ -22,6 +22,8 @@ if(DAMASK_SOLVER STREQUAL "GRID")
|
|||
project(damask-grid HOMEPAGE_URL https://damask.mpie.de LANGUAGES Fortran C)
|
||||
elseif(DAMASK_SOLVER STREQUAL "MESH")
|
||||
project(damask-mesh HOMEPAGE_URL https://damask.mpie.de LANGUAGES Fortran C)
|
||||
elseif(DAMASK_SOLVER STREQUAL "TEST")
|
||||
project(damask-test HOMEPAGE_URL https://damask.mpie.de LANGUAGES Fortran C)
|
||||
else()
|
||||
message(FATAL_ERROR "Invalid solver: DAMASK_SOLVER=${DAMASK_SOLVER}")
|
||||
endif()
|
||||
|
@ -90,14 +92,21 @@ endif()
|
|||
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
||||
if (CMAKE_Fortran_COMPILER_ID STREQUAL "Intel")
|
||||
include(Compiler-Intel)
|
||||
elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
|
||||
if (CMAKE_Fortran_COMPILER_ID STREQUAL "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")
|
||||
include(Compiler-IntelLLVM)
|
||||
set(Fortran_COMPILER_VERSION_MIN 19)
|
||||
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()
|
||||
|
||||
file(STRINGS "$ENV{PETSC_DIR}/$ENV{PETSC_ARCH}/lib/petsc/conf/petscvariables" PETSC_EXTERNAL_LIB REGEX "PETSC_EXTERNAL_LIB_BASIC = .*$?")
|
||||
|
|
5
Makefile
5
Makefile
|
@ -17,6 +17,11 @@ mesh:
|
|||
@cmake -B build/mesh -DDAMASK_SOLVER=mesh -DCMAKE_INSTALL_PREFIX=${PWD} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILDCMD_POST=${BUILDCMD_POST} -DBUILDCMD_PRE=${BUILDCMD_PRE} -DOPTIMIZATION=${OPTIMIZATION} -DOPENMP=${OPENMP}
|
||||
@cmake --build build/mesh --parallel --target install
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
@cmake -B build/test -DDAMASK_SOLVER=test -DCMAKE_INSTALL_PREFIX=${PWD} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILDCMD_POST=${BUILDCMD_POST} -DBUILDCMD_PRE=${BUILDCMD_PRE} -DOPTIMIZATION=${OPTIMIZATION} -DOPENMP=${OPENMP}
|
||||
@cmake --build build/test --parallel --target install
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@rm -rf build
|
||||
|
|
2
PRIVATE
2
PRIVATE
|
@ -1 +1 @@
|
|||
Subproject commit 547bfe56d67cba358a7bb9582f2f7c0e344befd8
|
||||
Subproject commit 22a23a9d5939d49d9d277c7066d9b68003a33324
|
|
@ -93,8 +93,6 @@ set (DEBUG_FLAGS "${DEBUG_FLAGS},pointers")
|
|||
# ... for certain disassociated or uninitialized pointers or unallocated allocatable objects.
|
||||
set (DEBUG_FLAGS "${DEBUG_FLAGS},uninit")
|
||||
# ... for uninitialized variables.
|
||||
set (DEBUG_FLAGS "${DEBUG_FLAGS} -ftrapuv")
|
||||
# ... initializes stack local variables to an unusual value to aid error detection
|
||||
set (DEBUG_FLAGS "${DEBUG_FLAGS} -fpe-all=0 -ftz")
|
||||
# ... capture all floating-point exceptions, need to overwrite -no-ftz
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ if (CMAKE_Fortran_COMPILER_VERSION VERSION_LESS 18.0)
|
|||
endif ()
|
||||
|
||||
if (OPENMP)
|
||||
set (OPENMP_FLAGS "-qopenmp")
|
||||
set (OPENMP_FLAGS "-fiopenmp")
|
||||
endif ()
|
||||
|
||||
if (OPTIMIZATION STREQUAL "OFF" OR OPTIMIZATION STREQUAL "DEBUG")
|
||||
|
@ -23,6 +23,8 @@ endif ()
|
|||
set (STANDARD_CHECK "-stand f18 -assume nostd_mod_proc_name")
|
||||
set (LINKER_FLAGS "${LINKER_FLAGS} -shared-intel")
|
||||
# Link against shared Intel libraries instead of static ones
|
||||
set (LINKER_FLAGS "${LINKER_FLAGS} -shared-intel -fc=ifx")
|
||||
# enforce use of ifx for MPI wrapper
|
||||
|
||||
#------------------------------------------------------------------------------------------------
|
||||
# Fine tuning compilation options
|
||||
|
@ -93,8 +95,6 @@ set (DEBUG_FLAGS "${DEBUG_FLAGS},pointers")
|
|||
# ... for certain disassociated or uninitialized pointers or unallocated allocatable objects.
|
||||
set (DEBUG_FLAGS "${DEBUG_FLAGS},uninit")
|
||||
# ... for uninitialized variables.
|
||||
set (DEBUG_FLAGS "${DEBUG_FLAGS} -ftrapuv")
|
||||
# ... initializes stack local variables to an unusual value to aid error detection
|
||||
set (DEBUG_FLAGS "${DEBUG_FLAGS} -fpe-all=0 -ftz")
|
||||
# ... capture all floating-point exceptions, need to overwrite -no-ftz
|
||||
|
||||
|
|
|
@ -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]
|
|
@ -6,8 +6,8 @@ N_cl: [3]
|
|||
|
||||
g_crit: [0.5e+7]
|
||||
s_crit: [0.006666]
|
||||
dot_o: 1.e-3
|
||||
q: 20
|
||||
dot_o_0: 1.e-3
|
||||
p: 20
|
||||
|
||||
l_c: 1.0
|
||||
mu: 0.001
|
||||
|
|
|
@ -3,7 +3,7 @@ type: Hooke
|
|||
references:
|
||||
- D.J. Dever,
|
||||
Journal of Applied Physics 43(8):3293-3301, 1972,
|
||||
https://doi.org/10.1063/1.1661710
|
||||
https://doi.org/10.1063/1.1661710,
|
||||
fit to Tab. II (T_min=25ºC, T_max=880ºC)
|
||||
|
||||
C_11: 232.2e+9
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
type: Hooke
|
||||
|
||||
references:
|
||||
- Wang et al.,
|
||||
- Z. Wang et al.,
|
||||
Materials Science and Engineering:A 674:406-412, 2016,
|
||||
https://doi.org/10.1016/j.msea.2016.08.010,
|
||||
fit to Tab. 2 (last 3 rows)
|
||||
|
|
|
@ -3,8 +3,9 @@ type: Hooke
|
|||
references:
|
||||
- S.A. Kim and W.L. Johnson,
|
||||
Materials Science & Engineering A 452-453:633-639, 2007,
|
||||
https://doi.org/10.1016/j.msea.2006.11.147
|
||||
https://doi.org/10.1016/j.msea.2006.11.147,
|
||||
Tab. 1 (averaged for bcc)
|
||||
|
||||
C_11: 268.1e+9
|
||||
C_12: 111.2e+9
|
||||
C_44: 79.06e+9
|
||||
C_11: 267.9e+9
|
||||
C_12: 110.8e+9
|
||||
C_44: 78.9e+9
|
||||
|
|
|
@ -3,7 +3,7 @@ type: dislotungsten
|
|||
references:
|
||||
- D. Cereceda et al.,
|
||||
International Journal of Plasticity 78:242-265, 2016,
|
||||
http://dx.doi.org/10.1016/j.ijplas.2015.09.002
|
||||
https://doi.org/10.1016/j.ijplas.2015.09.002
|
||||
- R. Gröger et al.,
|
||||
Acta Materialia 56(19):5412-5425, 2008,
|
||||
https://doi.org/10.1016/j.actamat.2008.07.037
|
||||
|
|
|
@ -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]
|
|
@ -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
|
|
@ -4,19 +4,16 @@ references:
|
|||
- W.F. Hosford et al.,
|
||||
Acta Metallurgica 8(3):187-199, 1960,
|
||||
https://doi.org/10.1016/0001-6160(60)90127-9,
|
||||
fitted from Fig. 5
|
||||
- U.F. Kocks,
|
||||
Metallurgical and Materials Transactions B 1:1121–1143, 1970,
|
||||
https://doi.org/10.1007/BF02900224
|
||||
fitted to Fig. 5 ([111] and [001])
|
||||
|
||||
output: [xi_sl, gamma_sl]
|
||||
|
||||
N_sl: [12]
|
||||
|
||||
n_sl: 20
|
||||
a_sl: 3.1
|
||||
h_0_sl-sl: 1.7e+8
|
||||
xi_0_sl: [5.0e+6]
|
||||
xi_inf_sl: [37.5e+6]
|
||||
h_sl-sl: [1, 1, 1.4, 1.4, 1.4, 1.4, 1.4]
|
||||
a_sl: 5.4
|
||||
h_0_sl-sl: 281.5e+6
|
||||
xi_0_sl: [2.69e+6]
|
||||
xi_inf_sl: [67.5e+6]
|
||||
h_sl-sl: [1, 1, 5.123, 0.574, 1.123, 1.123, 1]
|
||||
dot_gamma_0_sl: 7.5e-5
|
||||
|
|
|
@ -1,22 +1,19 @@
|
|||
type: phenopowerlaw
|
||||
|
||||
references:
|
||||
- T Takeuchi,
|
||||
- T. Takeuchi,
|
||||
Transactions of the Japan Institute of Metals 16(10):629-640, 1975,
|
||||
https://doi.org/10.2320/matertrans1960.16.629,
|
||||
fitted from Fig. 3b
|
||||
- U.F. Kocks,
|
||||
Metallurgical and Materials Transactions B 1:1121–1143, 1970,
|
||||
https://doi.org/10.1007/BF02900224
|
||||
fitted to Fig. 3b ([111] and [001])
|
||||
|
||||
output: [xi_sl, gamma_sl]
|
||||
|
||||
N_sl: [12]
|
||||
|
||||
n_sl: 20
|
||||
a_sl: 1.0
|
||||
h_0_sl-sl: 2.4e+8
|
||||
xi_0_sl: [1.5e+6]
|
||||
xi_inf_sl: [112.5e+6]
|
||||
h_sl-sl: [1, 1, 1.4, 1.4, 1.4, 1.4, 1.4]
|
||||
a_sl: 0.6
|
||||
h_0_sl-sl: 3.5e+8
|
||||
xi_0_sl: [1.6e+6]
|
||||
xi_inf_sl: [96.4e+6]
|
||||
h_sl-sl: [1, 1, 5.123, 0.574, 1.123, 1.123, 1]
|
||||
dot_gamma_0_sl: 3.e-3
|
||||
|
|
|
@ -4,17 +4,16 @@ references:
|
|||
- K.M. Jackson and C. Lang,
|
||||
Platinum Metals Review 50:15-19, 2006,
|
||||
https://doi.org/10.1595/147106705X93359,
|
||||
fitted from Fig. 5 (Pt-5% Cu recrystallised)
|
||||
- U.F. Kocks,
|
||||
Metallurgical and Materials Transactions B 1:1121–1143, 1970,
|
||||
https://doi.org/10.1007/BF02900224
|
||||
fitted to Fig. 5 (Pt-5% Cu recrystallised)
|
||||
|
||||
output: [xi_sl, gamma_sl]
|
||||
|
||||
N_sl: [12]
|
||||
|
||||
n_sl: 1.6
|
||||
a_sl: 0.8
|
||||
h_0_sl-sl: 300.0e+6
|
||||
xi_0_sl: [150.0e+6]
|
||||
xi_inf_sl: [500.0e+6]
|
||||
h_sl-sl: [1, 1, 1.4, 1.4, 1.4, 1.4, 1.4]
|
||||
dot_gamma_0_sl: 0.0001
|
||||
n_sl: 20
|
||||
a_sl: 0.9
|
||||
h_0_sl-sl: 781.2e+6
|
||||
xi_0_sl: [114.e+6]
|
||||
xi_inf_sl: [207.e+6]
|
||||
h_sl-sl: [1, 1, 5.123, 0.574, 1.123, 1.123, 1]
|
||||
dot_gamma_0_sl: 0.001
|
||||
|
|
|
@ -25,5 +25,6 @@ from ._colormap import Colormap # noqa
|
|||
from ._vtk import VTK # noqa
|
||||
from ._config import Config # noqa
|
||||
from ._configmaterial import ConfigMaterial # noqa
|
||||
from ._loadcasegrid import LoadcaseGrid # noqa
|
||||
from ._grid import Grid # noqa
|
||||
from ._result import Result # noqa
|
||||
|
|
|
@ -28,10 +28,10 @@ _REF_WHITE = np.array([.95047, 1.00000, 1.08883])
|
|||
|
||||
class Colormap(mpl.colors.ListedColormap):
|
||||
"""
|
||||
Enhance matplotlib colormap functionality to be used within DAMASK.
|
||||
Enhance matplotlib colormap functionality for use within DAMASK.
|
||||
|
||||
Colors are internally stored as R(ed) G(green) B(lue) values.
|
||||
The colormap can be used in matplotlib, seaborn, etc., or can
|
||||
A colormap can be used in matplotlib, seaborn, etc., or can be
|
||||
exported to file for external use.
|
||||
|
||||
References
|
||||
|
@ -153,12 +153,12 @@ class Colormap(mpl.colors.ListedColormap):
|
|||
- 'hsl': Hue Saturation Luminance.
|
||||
- 'xyz': CIE Xyz.
|
||||
- 'lab': CIE Lab.
|
||||
- 'msh': Msh (for perceptual uniform interpolation).
|
||||
- 'msh': Msh (for perceptually uniform interpolation).
|
||||
|
||||
Returns
|
||||
-------
|
||||
new : damask.Colormap
|
||||
Colormap within given bounds.
|
||||
Colormap spanning given bounds.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
@ -276,7 +276,7 @@ class Colormap(mpl.colors.ListedColormap):
|
|||
def shade(self,
|
||||
field: np.ndarray,
|
||||
bounds: Optional[FloatSequence] = None,
|
||||
gap: Optional[float] = None) -> Image:
|
||||
gap: Optional[float] = None) -> Image.Image:
|
||||
"""
|
||||
Generate PIL image of 2D field using colormap.
|
||||
|
||||
|
@ -288,6 +288,7 @@ class Colormap(mpl.colors.ListedColormap):
|
|||
Value range (left,right) spanned by colormap.
|
||||
gap : field.dtype, optional
|
||||
Transparent value. NaN will always be rendered transparent.
|
||||
Defaults to None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -334,6 +335,7 @@ class Colormap(mpl.colors.ListedColormap):
|
|||
--------
|
||||
>>> import damask
|
||||
>>> damask.Colormap.from_predefined('stress').reversed()
|
||||
Colormap: stress_r
|
||||
|
||||
"""
|
||||
rev = super().reversed(name)
|
||||
|
@ -353,6 +355,7 @@ class Colormap(mpl.colors.ListedColormap):
|
|||
If None, colormap name + suffix.
|
||||
suffix: str, optional
|
||||
Extension to use for colormap file.
|
||||
Defaults to empty.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -452,8 +455,8 @@ class Colormap(mpl.colors.ListedColormap):
|
|||
|
||||
References
|
||||
----------
|
||||
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
|
||||
https://www.kennethmoreland.com/color-maps/diverging_map.py
|
||||
| https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
|
||||
| https://www.kennethmoreland.com/color-maps/diverging_map.py
|
||||
|
||||
"""
|
||||
def rad_diff(a,b):
|
||||
|
@ -735,8 +738,8 @@ class Colormap(mpl.colors.ListedColormap):
|
|||
|
||||
References
|
||||
----------
|
||||
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
|
||||
https://www.kennethmoreland.com/color-maps/diverging_map.py
|
||||
| https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
|
||||
| https://www.kennethmoreland.com/color-maps/diverging_map.py
|
||||
|
||||
"""
|
||||
M = np.linalg.norm(lab)
|
||||
|
@ -763,8 +766,8 @@ class Colormap(mpl.colors.ListedColormap):
|
|||
|
||||
References
|
||||
----------
|
||||
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
|
||||
https://www.kennethmoreland.com/color-maps/diverging_map.py
|
||||
| https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
|
||||
| https://www.kennethmoreland.com/color-maps/diverging_map.py
|
||||
|
||||
"""
|
||||
return np.array([
|
||||
|
|
|
@ -2,6 +2,7 @@ import copy
|
|||
from io import StringIO
|
||||
from collections.abc import Iterable
|
||||
import abc
|
||||
import platform
|
||||
from typing import Optional, Union, Dict, Any, Type, TypeVar
|
||||
|
||||
import numpy as np
|
||||
|
@ -43,7 +44,7 @@ class NiceDumper(SafeDumper):
|
|||
return self.represent_data(data.tolist())
|
||||
if isinstance(data, Rotation):
|
||||
return self.represent_data(data.quaternion.tolist())
|
||||
if hasattr(data, 'dtype'):
|
||||
if isinstance(data, np.generic):
|
||||
return self.represent_data(data.item())
|
||||
|
||||
return super().represent_data(data)
|
||||
|
@ -57,15 +58,42 @@ class Config(dict):
|
|||
"""YAML-based configuration."""
|
||||
|
||||
def __init__(self,
|
||||
yml: Union[None, str, Dict[str, Any]] = None,
|
||||
config: Optional[Union[str, Dict[str, Any]]] = None,
|
||||
**kwargs):
|
||||
"""Initialize from YAML, dict, or key=value pairs."""
|
||||
if isinstance(yml,str):
|
||||
kwargs.update(yaml.load(yml, Loader=SafeLoader))
|
||||
elif isinstance(yml,dict):
|
||||
kwargs.update(yml)
|
||||
"""
|
||||
New YAML-based configuration.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
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:
|
||||
"""
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
from typing import Optional, Union, Sequence, Dict, Any, List
|
||||
|
||||
import numpy as np
|
||||
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 Rotation
|
||||
from . import Orientation
|
||||
from . import util
|
||||
from . import tensor
|
||||
from . import Table
|
||||
|
||||
|
||||
|
@ -22,33 +24,43 @@ class ConfigMaterial(Config):
|
|||
"""
|
||||
|
||||
def __init__(self,
|
||||
d: Optional[Dict[str, Any]] = None,
|
||||
**kwargs):
|
||||
config: Optional[Union[str,Dict[str,Any]]] = None,*,
|
||||
homogenization: Optional[Dict[str,Dict]] = None,
|
||||
phase: Optional[Dict[str,Dict]] = None,
|
||||
material: Optional[List[Dict[str,Any]]] = None):
|
||||
"""
|
||||
New material configuration.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
d : dictionary or YAML string, optional
|
||||
Initial content. Defaults to None, in which case empty entries for
|
||||
any missing material, homogenization, and phase entry are created.
|
||||
kwargs : key=value pairs, optional
|
||||
Initial content specified as pairs of key=value.
|
||||
config : dict or str, optional
|
||||
Material configuration. String needs to be valid YAML.
|
||||
homogenization : dict, optional
|
||||
Homogenization configuration.
|
||||
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
|
||||
if d is None:
|
||||
for section, default in {'material':[],'homogenization':{},'phase':{}}.items():
|
||||
if section not in kwargs: kwargs.update({section:default})
|
||||
kwargs: Dict[str,Union[Dict[str,Dict],List[Dict[str,Any]]]] = {}
|
||||
for arg,value in zip(['homogenization','phase','material'],[homogenization,phase,material]):
|
||||
if value is None and config is None:
|
||||
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,
|
||||
fname: FileHandle = 'material.yaml',
|
||||
**kwargs):
|
||||
"""
|
||||
Save to yaml file.
|
||||
Save to YAML file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -65,7 +77,7 @@ class ConfigMaterial(Config):
|
|||
def load(cls,
|
||||
fname: FileHandle = 'material.yaml') -> 'ConfigMaterial':
|
||||
"""
|
||||
Load from yaml file.
|
||||
Load from YAML file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -168,8 +180,12 @@ class ConfigMaterial(Config):
|
|||
|
||||
|
||||
@staticmethod
|
||||
def from_table(table: Table,
|
||||
**kwargs) -> 'ConfigMaterial':
|
||||
def from_table(table: Table,*,
|
||||
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.
|
||||
|
||||
|
@ -177,16 +193,33 @@ class ConfigMaterial(Config):
|
|||
----------
|
||||
table : damask.Table
|
||||
Table that contains material information.
|
||||
**kwargs
|
||||
Keyword arguments where the key is the property name and
|
||||
the value specifies either the label of the data column in the table
|
||||
or a constant value.
|
||||
homogenization: (array-like) of str, optional
|
||||
Homogenization label.
|
||||
phase: (array-like) of str, optional
|
||||
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
|
||||
-------
|
||||
new : damask.ConfigMaterial
|
||||
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
|
||||
--------
|
||||
>>> import damask
|
||||
|
@ -228,15 +261,16 @@ class ConfigMaterial(Config):
|
|||
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)
|
||||
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'
|
||||
kwargs = {k:np.atleast_1d(v[idx].squeeze()) for k,v in kwargs.items()}
|
||||
|
||||
return ConfigMaterial().material_add(**kwargs_)
|
||||
return ConfigMaterial().material_add(**kwargs)
|
||||
|
||||
|
||||
@property
|
||||
|
@ -361,7 +395,7 @@ class ConfigMaterial(Config):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
mapping: dictionary
|
||||
mapping: dict
|
||||
Mapping from old name to new name
|
||||
ID: list of ints, optional
|
||||
Limit renaming to selected material IDs.
|
||||
|
@ -394,7 +428,7 @@ class ConfigMaterial(Config):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
mapping: dictionary
|
||||
mapping: dict
|
||||
Mapping from old name to new name
|
||||
ID: list of ints, optional
|
||||
Limit renaming to selected homogenization IDs.
|
||||
|
@ -416,11 +450,11 @@ class ConfigMaterial(Config):
|
|||
|
||||
|
||||
def material_add(self,*,
|
||||
homogenization: Any = None,
|
||||
phase: Any = None,
|
||||
v: Any = None,
|
||||
O: Any = None,
|
||||
V_e: Any = None) -> 'ConfigMaterial':
|
||||
homogenization: Optional[Union[str,StrSequence]] = None,
|
||||
phase: Optional[Union[str,StrSequence]] = None,
|
||||
v: Optional[Union[float,FloatSequence]] = None,
|
||||
O: Optional[Union[float,FloatSequence]] = None,
|
||||
V_e: Optional[Union[float,FloatSequence]] = None) -> 'ConfigMaterial':
|
||||
"""
|
||||
Add material entries.
|
||||
|
||||
|
@ -432,6 +466,7 @@ class ConfigMaterial(Config):
|
|||
Phase label (per constituent).
|
||||
v: (array-like) of float, optional
|
||||
Constituent volume fraction (per constituent).
|
||||
Defaults to 1/N_constituent.
|
||||
O: (array-like) of damask.Rotation or np.array/list of shape(4), optional
|
||||
Orientation as unit quaternion (per constituent).
|
||||
V_e: (array-like) of np.array/list of shape(3,3), optional
|
||||
|
@ -444,9 +479,8 @@ class ConfigMaterial(Config):
|
|||
|
||||
Notes
|
||||
-----
|
||||
First index of array-like values that are defined per
|
||||
consituent runs over materials, whereas second index runs
|
||||
over constituents.
|
||||
First index of array-like values that are defined per constituent
|
||||
runs over materials, whereas second index runs over constituents.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
@ -525,49 +559,48 @@ class ConfigMaterial(Config):
|
|||
phase: {Austenite: null, Ferrite: null}
|
||||
|
||||
"""
|
||||
kwargs = {}
|
||||
for keyword,value in zip(['homogenization','phase','v','O','V_e'],[homogenization,phase,v,O,V_e]):
|
||||
if value is not None: kwargs[keyword] = value
|
||||
dim = {'O':(4,),'V_e':(3,3,)}
|
||||
ex = dict((keyword, -len(val)) for keyword,val in dim.items())
|
||||
|
||||
_constituent_properties = ['phase','O','v','V_e']
|
||||
_dim = {'O':(4,),'V_e':(3,3,)}
|
||||
_ex = dict((k, -len(v)) for k, v in _dim.items())
|
||||
N_materials,N_constituents = 1,1
|
||||
shape = {}
|
||||
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
|
||||
shaped : Dict[str, Union[None,np.ndarray]] = \
|
||||
{'v': None,
|
||||
'phase': None,
|
||||
'homogenization': None,
|
||||
}
|
||||
shape['v'] = np.array(shape.get('v',1./N_constituents),float)
|
||||
|
||||
for k,v in kwargs.items():
|
||||
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
|
||||
mat: Sequence[dict] = [{'constituents':[{} for _ in range(N_constituents)]} for _ in range(N_materials)]
|
||||
|
||||
shaped['v'] = np.array(1./n) if shaped['v'] is None else shaped['v']
|
||||
|
||||
mat: Sequence[dict] = [{'constituents':[{} for _ in range(n)]} for _ in range(N)]
|
||||
|
||||
for k,v in shaped.items():
|
||||
target = (N,n) + _dim.get(k,())
|
||||
obj = np.broadcast_to(np.array(v).reshape(util.shapeshifter(() if v is None else v.shape,
|
||||
target,
|
||||
mode = 'right')),
|
||||
target)
|
||||
for i in range(N):
|
||||
if k in _constituent_properties:
|
||||
for j in range(n):
|
||||
mat[i]['constituents'][j][k] = obj[i,j].item() if isinstance(obj[i,j],np.generic) else obj[i,j]
|
||||
for k,v in shape.items():
|
||||
target = (N_materials,N_constituents) + dim.get(k,())
|
||||
broadcasted = np.broadcast_to(np.array(v).reshape(util.shapeshifter(np.array(v).shape,target,'right')),target)
|
||||
if k == 'v':
|
||||
if np.min(broadcasted) < 0 or np.max(broadcasted) > 1:
|
||||
raise ValueError('volume fraction "v" out of range')
|
||||
if len(np.atleast_1d(broadcasted)) > 1:
|
||||
total = np.sum(broadcasted,axis=-1)
|
||||
if np.min(total) < 0 or np.max(total) > 1:
|
||||
raise ValueError('volume fraction "v" out of range')
|
||||
if k == 'O' and not np.allclose(1.0,np.linalg.norm(broadcasted,axis=-1)):
|
||||
raise ValueError('orientation "O" is not a unit quaterion')
|
||||
elif k == 'V_e' and not np.allclose(broadcasted,tensor.symmetric(broadcasted)):
|
||||
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:
|
||||
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['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 k in np.unique(shaped[what]): # type: ignore
|
||||
for what in [item for item in ['phase','homogenization'] if item in shape]:
|
||||
for k in np.unique(shape[what]): # type: ignore
|
||||
if k not in dup[what]: dup[what][str(k)] = None
|
||||
|
||||
return dup
|
||||
|
|
|
@ -307,15 +307,13 @@ class Crystal():
|
|||
Cubic crystal family:
|
||||
|
||||
>>> import damask
|
||||
>>> cubic = damask.Crystal(family='cubic')
|
||||
>>> cubic
|
||||
>>> (cubic := damask.Crystal(family='cubic'))
|
||||
Crystal family: cubic
|
||||
|
||||
Body-centered cubic Bravais lattice with parameters of iron:
|
||||
|
||||
>>> import damask
|
||||
>>> Fe = damask.Crystal(lattice='cI', a=287e-12)
|
||||
>>> Fe
|
||||
>>> (Fe := damask.Crystal(lattice='cI', a=287e-12))
|
||||
Crystal family: cubic
|
||||
Bravais lattice: cI
|
||||
a=2.87e-10 m, b=2.87e-10 m, c=2.87e-10 m
|
||||
|
@ -406,7 +404,7 @@ class Crystal():
|
|||
"""
|
||||
Return repr(self).
|
||||
|
||||
Give short human-readable summary.
|
||||
Give short, human-readable summary.
|
||||
|
||||
"""
|
||||
family = f'Crystal family: {self.family}'
|
||||
|
|
|
@ -32,10 +32,10 @@ class Grid:
|
|||
"""
|
||||
Geometry definition for grid solvers.
|
||||
|
||||
Create and manipulate geometry definitions for storage as VTK
|
||||
image data files ('.vti' extension). A grid contains the
|
||||
material ID (referring to the entry in 'material.yaml') and
|
||||
the physical size.
|
||||
Create and manipulate geometry definitions for storage as VTK ImageData
|
||||
files ('.vti' extension). A grid has a physical size, a coordinate origin,
|
||||
and contains the material ID (indexing an entry in 'material.yaml')
|
||||
as well as initial condition fields.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
|
@ -57,7 +57,7 @@ class Grid:
|
|||
origin : sequence of float, len (3), optional
|
||||
Coordinates of grid origin in meter. Defaults to [0.0,0.0,0.0].
|
||||
initial_conditions : dictionary, optional
|
||||
Labels and values of the inital conditions at each material point.
|
||||
Initial condition label and field values at each grid point.
|
||||
comments : (sequence of) str, optional
|
||||
Additional, human-readable information, e.g. history of operations.
|
||||
|
||||
|
@ -74,7 +74,7 @@ class Grid:
|
|||
"""
|
||||
Return repr(self).
|
||||
|
||||
Give short human-readable summary.
|
||||
Give short, human-readable summary.
|
||||
|
||||
"""
|
||||
mat_min = np.nanmin(self.material)
|
||||
|
@ -144,7 +144,7 @@ class Grid:
|
|||
|
||||
@property
|
||||
def size(self) -> np.ndarray:
|
||||
"""Physical size of grid in meter."""
|
||||
"""Edge lengths of grid in meter."""
|
||||
return self._size
|
||||
|
||||
@size.setter
|
||||
|
@ -157,7 +157,7 @@ class Grid:
|
|||
|
||||
@property
|
||||
def origin(self) -> np.ndarray:
|
||||
"""Coordinates of grid origin in meter."""
|
||||
"""Vector to grid origin in meter."""
|
||||
return self._origin
|
||||
|
||||
@origin.setter
|
||||
|
@ -186,7 +186,7 @@ class Grid:
|
|||
|
||||
@property
|
||||
def cells(self) -> np.ndarray:
|
||||
"""Number of cells in x,y,z direction."""
|
||||
"""Cell counts along x,y,z direction."""
|
||||
return np.asarray(self.material.shape)
|
||||
|
||||
|
||||
|
@ -199,7 +199,7 @@ class Grid:
|
|||
@staticmethod
|
||||
def load(fname: Union[str, Path]) -> 'Grid':
|
||||
"""
|
||||
Load from VTK image data file.
|
||||
Load from VTK ImageData file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -317,6 +317,11 @@ class Grid:
|
|||
loaded : damask.Grid
|
||||
Grid-based geometry from file.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Material indices in Neper usually start at 1 unless
|
||||
a buffer material with index 0 is added.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Read a periodic polycrystal generated with Neper.
|
||||
|
@ -325,7 +330,7 @@ class Grid:
|
|||
>>> N_grains = 20
|
||||
>>> cells = (32,32,32)
|
||||
>>> damask.util.run(f'neper -T -n {N_grains} -tesrsize {cells[0]}:{cells[1]}:{cells[2]} -periodicity all -format vtk')
|
||||
>>> damask.Grid.load_Neper(f'n{N_grains}-id1.vtk')
|
||||
>>> damask.Grid.load_Neper(f'n{N_grains}-id1.vtk').renumber()
|
||||
cells: 32 × 32 × 32
|
||||
size: 1.0 × 1.0 × 1.0 m³
|
||||
origin: 0.0 0.0 0.0 m
|
||||
|
@ -336,7 +341,7 @@ class Grid:
|
|||
cells = np.array(v.vtk_data.GetDimensions())-1
|
||||
bbox = np.array(v.vtk_data.GetBounds()).reshape(3,2).T
|
||||
|
||||
return Grid(material = v.get('MaterialId').reshape(cells,order='F').astype('int32',casting='unsafe') - 1,
|
||||
return Grid(material = v.get('MaterialId').reshape(cells,order='F').astype('int32',casting='unsafe'),
|
||||
size = bbox[1] - bbox[0],
|
||||
origin = bbox[0],
|
||||
comments = util.execution_stamp('Grid','load_Neper'),
|
||||
|
@ -470,9 +475,9 @@ class Grid:
|
|||
Parameters
|
||||
----------
|
||||
cells : sequence of int, len (3)
|
||||
Number of cells in x,y,z direction.
|
||||
Cell counts along x,y,z direction.
|
||||
size : sequence of float, len (3)
|
||||
Physical size of the grid in meter.
|
||||
Edge lengths of the grid in meter.
|
||||
seeds : numpy.ndarray of float, shape (:,3)
|
||||
Position of the seed points in meter. All points need to lay within the box.
|
||||
weights : sequence of float, len (seeds.shape[0])
|
||||
|
@ -527,9 +532,9 @@ class Grid:
|
|||
Parameters
|
||||
----------
|
||||
cells : sequence of int, len (3)
|
||||
Number of cells in x,y,z direction.
|
||||
Cell counts along x,y,z direction.
|
||||
size : sequence of float, len (3)
|
||||
Physical size of the grid in meter.
|
||||
Edge lengths of the grid in meter.
|
||||
seeds : numpy.ndarray of float, shape (:,3)
|
||||
Position of the seed points in meter. All points need to lay within the box.
|
||||
material : sequence of int, len (seeds.shape[0]), optional
|
||||
|
@ -608,14 +613,14 @@ class Grid:
|
|||
periods: int = 1,
|
||||
materials: IntSequence = (0,1)) -> 'Grid':
|
||||
"""
|
||||
Create grid from definition of triply periodic minimal surface.
|
||||
Create grid from definition of triply-periodic minimal surface.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cells : sequence of int, len (3)
|
||||
Number of cells in x,y,z direction.
|
||||
Cell counts along x,y,z direction.
|
||||
size : sequence of float, len (3)
|
||||
Physical size of the grid in meter.
|
||||
Edge lengths of the grid in meter.
|
||||
surface : str
|
||||
Type of the minimal surface. See notes for details.
|
||||
threshold : float, optional.
|
||||
|
@ -664,19 +669,19 @@ class Grid:
|
|||
>>> import numpy as np
|
||||
>>> import damask
|
||||
>>> damask.Grid.from_minimal_surface([64]*3,np.ones(3)*1.e-4,'Gyroid')
|
||||
cells : 64 x 64 x 64
|
||||
size : 0.0001 x 0.0001 x 0.0001 m³
|
||||
cells : 64 × 64 × 64
|
||||
size : 0.0001 × 0.0001 × 0.0001 m³
|
||||
origin: 0.0 0.0 0.0 m
|
||||
# materials: 2
|
||||
|
||||
Minimal surface of 'Neovius' type with non-default material IDs.
|
||||
Minimal surface of 'Neovius' type with specific material IDs.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import damask
|
||||
>>> damask.Grid.from_minimal_surface([80]*3,np.ones(3)*5.e-4,
|
||||
... 'Neovius',materials=(1,5))
|
||||
cells : 80 x 80 x 80
|
||||
size : 0.0005 x 0.0005 x 0.0005 m³
|
||||
cells : 80 × 80 × 80
|
||||
size : 0.0005 × 0.0005 × 0.0005 m³
|
||||
origin: 0.0 0.0 0.0 m
|
||||
# materials: 2 (min: 1, max: 5)
|
||||
|
||||
|
@ -695,12 +700,13 @@ class Grid:
|
|||
fname: Union[str, Path],
|
||||
compress: bool = True):
|
||||
"""
|
||||
Save as VTK image data file.
|
||||
Save as VTK ImageData file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fname : str or pathlib.Path
|
||||
Filename to write. Valid extension is .vti, it will be appended if not given.
|
||||
Filename to write.
|
||||
Valid extension is .vti, which will be appended if not given.
|
||||
compress : bool, optional
|
||||
Compress with zlib algorithm. Defaults to True.
|
||||
|
||||
|
@ -727,7 +733,7 @@ class Grid:
|
|||
fname : str or file handle
|
||||
Geometry file to write with extension '.geom'.
|
||||
compress : bool, optional
|
||||
Compress geometry with 'x of y' and 'a to b'.
|
||||
Compress geometry using 'x of y' and 'a to b'.
|
||||
|
||||
"""
|
||||
warnings.warn('Support for ASCII-based geom format will be removed in DAMASK 3.0.0', DeprecationWarning,2)
|
||||
|
@ -771,13 +777,13 @@ class Grid:
|
|||
Parameters
|
||||
----------
|
||||
cells : sequence of int, len (3), optional
|
||||
Number of cells x,y,z direction.
|
||||
Cell counts along x,y,z direction.
|
||||
offset : sequence of int, len (3), optional
|
||||
Offset (measured in cells) from old to new grid.
|
||||
Defaults to [0,0,0].
|
||||
fill : int, optional
|
||||
Material ID to fill the background.
|
||||
Defaults to material.max() + 1.
|
||||
Defaults to material.max()+1.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -790,11 +796,11 @@ class Grid:
|
|||
|
||||
>>> import numpy as np
|
||||
>>> import damask
|
||||
>>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4)
|
||||
>>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-3)
|
||||
>>> g.canvas([32,32,16],[0,0,16])
|
||||
cells : 33 x 32 x 16
|
||||
size : 0.0001 x 0.0001 x 5e-05 m³
|
||||
origin: 0.0 0.0 5e-05 m
|
||||
cells: 32 × 32 × 16
|
||||
size: 0.001 × 0.001 × 0.0005 m³
|
||||
origin: 0.0 0.0 0.0005 m
|
||||
# materials: 1
|
||||
|
||||
"""
|
||||
|
@ -837,16 +843,33 @@ class Grid:
|
|||
|
||||
Examples
|
||||
--------
|
||||
Mirror along x- and y-direction.
|
||||
Mirror along y-direction.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import damask
|
||||
>>> g = damask.Grid(np.zeros([32]*3,int),np.ones(3)*1e-4)
|
||||
>>> g.mirror('xy',True)
|
||||
cells : 64 x 64 x 32
|
||||
size : 0.0002 x 0.0002 x 0.0001 m³
|
||||
>>> (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 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 m³
|
||||
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 m³
|
||||
origin: 0.0 0.0 0.0 m
|
||||
# materials: 120
|
||||
|
||||
Independence of mirroring order.
|
||||
|
||||
>>> g.mirror('xy') == g.mirror(['y','x'])
|
||||
True
|
||||
|
||||
"""
|
||||
if not set(directions).issubset(valid := ['x', 'y', 'z']):
|
||||
|
@ -884,11 +907,29 @@ class Grid:
|
|||
updated : damask.Grid
|
||||
Updated grid-based geometry.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Invariance of flipping order.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import damask
|
||||
>>> (g := damask.Grid(np.arange(4*5*6).reshape([4,5,6]),np.ones(3)))
|
||||
cells: 4 × 5 × 6
|
||||
size: 1.0 × 1.0 × 1.0 m³
|
||||
origin: 0.0 0.0 0.0 m
|
||||
# materials: 120
|
||||
>>> g.flip('xyz') == g.flip(['x','z','y'])
|
||||
True
|
||||
|
||||
Invariance of flipping a (fully) mirrored grid.
|
||||
|
||||
>>> g.mirror('x',True) == g.mirror('x',True).flip('x')
|
||||
True
|
||||
|
||||
"""
|
||||
if not set(directions).issubset(valid := ['x', 'y', 'z']):
|
||||
raise ValueError(f'invalid direction "{set(directions).difference(valid)}" specified')
|
||||
|
||||
|
||||
mat = np.flip(self.material, [valid.index(d) for d in directions if d in valid])
|
||||
|
||||
return Grid(material = mat,
|
||||
|
@ -902,7 +943,7 @@ class Grid:
|
|||
R: Rotation,
|
||||
fill: Optional[int] = None) -> 'Grid':
|
||||
"""
|
||||
Rotate grid (and pad if required).
|
||||
Rotate grid (possibly extending its bounding box).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -910,13 +951,27 @@ class Grid:
|
|||
Rotation to apply to the grid.
|
||||
fill : int, optional
|
||||
Material ID to fill enlarged bounding box.
|
||||
Defaults to material.max() + 1.
|
||||
Defaults to material.max()+1.
|
||||
|
||||
Returns
|
||||
-------
|
||||
updated : damask.Grid
|
||||
Updated grid-based geometry.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Rotation by 180° (π) is equivalent to twice flipping.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import damask
|
||||
>>> (g := damask.Grid(np.arange(4*5*6).reshape([4,5,6]),np.ones(3)))
|
||||
cells: 4 × 5 × 6
|
||||
size: 1.0 × 1.0 × 1.0 m³
|
||||
origin: 0.0 0.0 0.0 m
|
||||
# materials: 120
|
||||
>>> g.rotate(damask.Rotation.from_axis_angle([0,0,1,180],degrees=True)) == g.flip('xy')
|
||||
True
|
||||
|
||||
"""
|
||||
material = self.material
|
||||
# These rotations are always applied in the reference coordinate system, i.e. (z,x,z) not (z,x',z'')
|
||||
|
@ -941,12 +996,12 @@ class Grid:
|
|||
def scale(self,
|
||||
cells: IntSequence) -> 'Grid':
|
||||
"""
|
||||
Scale grid to new cell count.
|
||||
Scale grid to new cell counts.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cells : sequence of int, len (3)
|
||||
Number of cells in x,y,z direction.
|
||||
Cell counts along x,y,z direction.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -955,7 +1010,7 @@ class Grid:
|
|||
|
||||
Examples
|
||||
--------
|
||||
Double resolution.
|
||||
Double grid resolution.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import damask
|
||||
|
@ -965,8 +1020,8 @@ class Grid:
|
|||
origin: 0.0 0.0 0.0 m
|
||||
# materials: 1
|
||||
>>> g.scale(g.cells*2)
|
||||
cells : 64 x 64 x 64
|
||||
size : 0.0001 x 0.0001 x 0.0001 m³
|
||||
cells : 64 × 64 × 64
|
||||
size : 0.0001 × 0.0001 × 0.0001 m³
|
||||
origin: 0.0 0.0 0.0 m
|
||||
# materials: 1
|
||||
|
||||
|
@ -994,7 +1049,7 @@ class Grid:
|
|||
Parameters
|
||||
----------
|
||||
idx : numpy.ndarray of int, shape (:,:,:) or (:,:,:,3)
|
||||
Grid of flat indices or coordinate indices.
|
||||
Grid of flat indices or coordinate indices.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -1069,7 +1124,7 @@ class Grid:
|
|||
|
||||
def sort(self) -> 'Grid':
|
||||
"""
|
||||
Sort material indices such that min(material) is located at (0,0,0).
|
||||
Sort material indices such that min(material ID) is located at (0,0,0).
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -1186,7 +1241,7 @@ class Grid:
|
|||
fill : int, optional
|
||||
Fill value for primitive. Defaults to material.max()+1.
|
||||
R : damask.Rotation, optional
|
||||
Rotation of primitive. Defaults to no rotation.
|
||||
Rotation of the primitive. Defaults to no rotation.
|
||||
inverse : bool, optional
|
||||
Retain original materials within primitive and fill outside.
|
||||
Defaults to False.
|
||||
|
@ -1206,8 +1261,8 @@ class Grid:
|
|||
>>> import damask
|
||||
>>> g = damask.Grid(np.zeros([64]*3,int), np.ones(3)*1e-4)
|
||||
>>> g.add_primitive(np.ones(3)*5e-5,np.ones(3)*5e-5,1)
|
||||
cells : 64 x 64 x 64
|
||||
size : 0.0001 x 0.0001 x 0.0001 m³
|
||||
cells : 64 × 64 × 64
|
||||
size : 0.0001 × 0.0001 × 0.0001 m³
|
||||
origin: 0.0 0.0 0.0 m
|
||||
# materials: 2
|
||||
|
||||
|
@ -1217,8 +1272,8 @@ class Grid:
|
|||
>>> import damask
|
||||
>>> g = damask.Grid(np.zeros([64]*3,int), np.ones(3)*1e-4)
|
||||
>>> g.add_primitive(np.ones(3,int)*32,np.zeros(3),np.inf)
|
||||
cells : 64 x 64 x 64
|
||||
size : 0.0001 x 0.0001 x 0.0001 m³
|
||||
cells : 64 × 64 × 64
|
||||
size : 0.0001 × 0.0001 × 0.0001 m³
|
||||
origin: 0.0 0.0 0.0 m
|
||||
# materials: 2
|
||||
|
||||
|
|
|
@ -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))
|
|
@ -1,6 +1,5 @@
|
|||
import inspect
|
||||
import copy
|
||||
from typing import Optional, Union, Callable, Dict, Any, Tuple, TypeVar
|
||||
from typing import Optional, Union, TypeVar
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
@ -10,31 +9,6 @@ from . import Crystal
|
|||
from . import util
|
||||
from . import tensor
|
||||
|
||||
|
||||
_parameter_doc = \
|
||||
"""
|
||||
family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'}, optional.
|
||||
Name of the crystal family.
|
||||
Family will be inferred if 'lattice' is given.
|
||||
lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}, optional.
|
||||
Name of the Bravais lattice in Pearson notation.
|
||||
a : float, optional
|
||||
Length of lattice parameter 'a'.
|
||||
b : float, optional
|
||||
Length of lattice parameter 'b'.
|
||||
c : float, optional
|
||||
Length of lattice parameter 'c'.
|
||||
alpha : float, optional
|
||||
Angle between b and c lattice basis.
|
||||
beta : float, optional
|
||||
Angle between c and a lattice basis.
|
||||
gamma : float, optional
|
||||
Angle between a and b lattice basis.
|
||||
degrees : bool, optional
|
||||
Angles are given in degrees. Defaults to False.
|
||||
|
||||
"""
|
||||
|
||||
MyType = TypeVar('MyType', bound='Orientation')
|
||||
|
||||
class Orientation(Rotation,Crystal):
|
||||
|
@ -94,7 +68,7 @@ class Orientation(Rotation,Crystal):
|
|||
|
||||
"""
|
||||
|
||||
@util.extend_docstring(extra_parameters=_parameter_doc)
|
||||
@util.extend_docstring(adopted_parameters=Crystal.__init__)
|
||||
def __init__(self,
|
||||
rotation: Union[FloatSequence, Rotation] = np.array([1.,0.,0.,0.]),
|
||||
*,
|
||||
|
@ -108,7 +82,7 @@ class Orientation(Rotation,Crystal):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
rotation : list, numpy.ndarray, Rotation, optional
|
||||
rotation : list, numpy.ndarray, or Rotation, optional
|
||||
Unit quaternion in positive real hemisphere.
|
||||
Use .from_quaternion to perform a sanity check.
|
||||
Defaults to no rotation.
|
||||
|
@ -123,7 +97,7 @@ class Orientation(Rotation,Crystal):
|
|||
"""
|
||||
Return repr(self).
|
||||
|
||||
Give short human-readable summary.
|
||||
Give short, human-readable summary.
|
||||
|
||||
"""
|
||||
return util.srepr([Crystal.__repr__(self),
|
||||
|
@ -261,134 +235,91 @@ class Orientation(Rotation,Crystal):
|
|||
Compound rotation self*other, i.e. first other then self rotation.
|
||||
|
||||
"""
|
||||
if isinstance(other, (Orientation,Rotation)):
|
||||
return self.copy(Rotation(self.quaternion)*Rotation(other.quaternion))
|
||||
else:
|
||||
if not isinstance(other, (Orientation,Rotation)):
|
||||
raise TypeError('use "O@b", i.e. matmul, to apply Orientation "O" to object "b"')
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _split_kwargs(kwargs: Dict[str, Any],
|
||||
target: Callable) -> Tuple[Dict[str, Any], ...]:
|
||||
"""
|
||||
Separate keyword arguments in 'kwargs' targeted at 'target' from general keyword arguments of Orientation objects.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kwargs : dictionary
|
||||
Contains all **kwargs.
|
||||
target: method
|
||||
Function to scan for kwarg signature.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rot_kwargs: dictionary
|
||||
Valid keyword arguments of 'target' function of Rotation class.
|
||||
ori_kwargs: dictionary
|
||||
Valid keyword arguments of Orientation object.
|
||||
|
||||
"""
|
||||
kws: Tuple[Dict[str, Any], ...] = ()
|
||||
for t in (target,Orientation.__init__):
|
||||
kws += ({key: kwargs[key] for key in set(inspect.signature(t).parameters) & set(kwargs)},)
|
||||
|
||||
invalid_keys = set(kwargs)-(set(kws[0])|set(kws[1]))
|
||||
if invalid_keys:
|
||||
raise TypeError(f"{inspect.stack()[1][3]}() got an unexpected keyword argument '{invalid_keys.pop()}'")
|
||||
|
||||
return kws
|
||||
return self.copy(Rotation(self.quaternion)*Rotation(other.quaternion))
|
||||
|
||||
|
||||
@classmethod
|
||||
@util.extend_docstring(Rotation.from_random,
|
||||
extra_parameters=_parameter_doc)
|
||||
adopted_parameters=Crystal.__init__)
|
||||
@util.pass_on('rotation', Rotation.from_random, wrapped=__init__)
|
||||
def from_random(cls, **kwargs) -> 'Orientation':
|
||||
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_random)
|
||||
return cls(rotation=Rotation.from_random(**kwargs_rot),**kwargs_ori)
|
||||
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
@util.extend_docstring(Rotation.from_quaternion,
|
||||
extra_parameters=_parameter_doc)
|
||||
adopted_parameters=Crystal.__init__)
|
||||
@util.pass_on('rotation', Rotation.from_quaternion, wrapped=__init__)
|
||||
def from_quaternion(cls, **kwargs) -> 'Orientation':
|
||||
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_quaternion)
|
||||
return cls(rotation=Rotation.from_quaternion(**kwargs_rot),**kwargs_ori)
|
||||
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
@util.extend_docstring(Rotation.from_Euler_angles,
|
||||
extra_parameters=_parameter_doc)
|
||||
adopted_parameters=Crystal.__init__)
|
||||
@util.pass_on('rotation', Rotation.from_Euler_angles, wrapped=__init__)
|
||||
def from_Euler_angles(cls, **kwargs) -> 'Orientation':
|
||||
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_Euler_angles)
|
||||
return cls(rotation=Rotation.from_Euler_angles(**kwargs_rot),**kwargs_ori)
|
||||
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
@util.extend_docstring(Rotation.from_axis_angle,
|
||||
extra_parameters=_parameter_doc)
|
||||
adopted_parameters=Crystal.__init__)
|
||||
@util.pass_on('rotation', Rotation.from_axis_angle, wrapped=__init__)
|
||||
def from_axis_angle(cls, **kwargs) -> 'Orientation':
|
||||
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_axis_angle)
|
||||
return cls(rotation=Rotation.from_axis_angle(**kwargs_rot),**kwargs_ori)
|
||||
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
@util.extend_docstring(Rotation.from_basis,
|
||||
extra_parameters=_parameter_doc)
|
||||
adopted_parameters=Crystal.__init__)
|
||||
@util.pass_on('rotation', Rotation.from_basis, wrapped=__init__)
|
||||
def from_basis(cls, **kwargs) -> 'Orientation':
|
||||
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_basis)
|
||||
return cls(rotation=Rotation.from_basis(**kwargs_rot),**kwargs_ori)
|
||||
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
@util.extend_docstring(Rotation.from_matrix,
|
||||
extra_parameters=_parameter_doc)
|
||||
adopted_parameters=Crystal.__init__)
|
||||
@util.pass_on('rotation', Rotation.from_matrix, wrapped=__init__)
|
||||
def from_matrix(cls, **kwargs) -> 'Orientation':
|
||||
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_matrix)
|
||||
return cls(rotation=Rotation.from_matrix(**kwargs_rot),**kwargs_ori)
|
||||
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
@util.extend_docstring(Rotation.from_Rodrigues_vector,
|
||||
extra_parameters=_parameter_doc)
|
||||
adopted_parameters=Crystal.__init__)
|
||||
@util.pass_on('rotation', Rotation.from_Rodrigues_vector, wrapped=__init__)
|
||||
def from_Rodrigues_vector(cls, **kwargs) -> 'Orientation':
|
||||
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_Rodrigues_vector)
|
||||
return cls(rotation=Rotation.from_Rodrigues_vector(**kwargs_rot),**kwargs_ori)
|
||||
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
@util.extend_docstring(Rotation.from_homochoric,
|
||||
extra_parameters=_parameter_doc)
|
||||
adopted_parameters=Crystal.__init__)
|
||||
@util.pass_on('rotation', Rotation.from_homochoric, wrapped=__init__)
|
||||
def from_homochoric(cls, **kwargs) -> 'Orientation':
|
||||
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_homochoric)
|
||||
return cls(rotation=Rotation.from_homochoric(**kwargs_rot),**kwargs_ori)
|
||||
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
@util.extend_docstring(Rotation.from_cubochoric,
|
||||
extra_parameters=_parameter_doc)
|
||||
adopted_parameters=Crystal.__init__)
|
||||
@util.pass_on('rotation', Rotation.from_cubochoric, wrapped=__init__)
|
||||
def from_cubochoric(cls, **kwargs) -> 'Orientation':
|
||||
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_cubochoric)
|
||||
return cls(rotation=Rotation.from_cubochoric(**kwargs_rot),**kwargs_ori)
|
||||
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
@util.extend_docstring(Rotation.from_spherical_component,
|
||||
extra_parameters=_parameter_doc)
|
||||
adopted_parameters=Crystal.__init__)
|
||||
@util.pass_on('rotation', Rotation.from_spherical_component, wrapped=__init__)
|
||||
def from_spherical_component(cls, **kwargs) -> 'Orientation':
|
||||
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_spherical_component)
|
||||
return cls(rotation=Rotation.from_spherical_component(**kwargs_rot),**kwargs_ori)
|
||||
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
@util.extend_docstring(Rotation.from_fiber_component,
|
||||
extra_parameters=_parameter_doc)
|
||||
adopted_parameters=Crystal.__init__)
|
||||
@util.pass_on('rotation', Rotation.from_fiber_component, wrapped=__init__)
|
||||
def from_fiber_component(cls, **kwargs) -> 'Orientation':
|
||||
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_fiber_component)
|
||||
return cls(rotation=Rotation.from_fiber_component(**kwargs_rot),**kwargs_ori)
|
||||
return cls(**kwargs)
|
||||
|
||||
|
||||
@classmethod
|
||||
@util.extend_docstring(extra_parameters=_parameter_doc)
|
||||
@util.extend_docstring(adopted_parameters=Crystal.__init__)
|
||||
def from_directions(cls,
|
||||
uvw: FloatSequence,
|
||||
hkl: FloatSequence,
|
||||
|
@ -467,24 +398,24 @@ class Orientation(Rotation,Crystal):
|
|||
if self.family == 'cubic':
|
||||
return (np.prod(np.sqrt(2)-1. >= rho_abs,axis=-1) *
|
||||
(1. >= np.sum(rho_abs,axis=-1))).astype(bool)
|
||||
elif self.family == 'hexagonal':
|
||||
if self.family == 'hexagonal':
|
||||
return (np.prod(1. >= rho_abs,axis=-1) *
|
||||
(2. >= np.sqrt(3)*rho_abs[...,0] + rho_abs[...,1]) *
|
||||
(2. >= np.sqrt(3)*rho_abs[...,1] + rho_abs[...,0]) *
|
||||
(2. >= np.sqrt(3) + rho_abs[...,2])).astype(bool)
|
||||
elif self.family == 'tetragonal':
|
||||
if self.family == 'tetragonal':
|
||||
return (np.prod(1. >= rho_abs[...,:2],axis=-1) *
|
||||
(np.sqrt(2) >= rho_abs[...,0] + rho_abs[...,1]) *
|
||||
(np.sqrt(2) >= rho_abs[...,2] + 1.)).astype(bool)
|
||||
elif self.family == 'orthorhombic':
|
||||
if self.family == 'orthorhombic':
|
||||
return (np.prod(1. >= rho_abs,axis=-1)).astype(bool)
|
||||
elif self.family == 'monoclinic':
|
||||
if self.family == 'monoclinic':
|
||||
return np.logical_or( 1. >= rho_abs[...,1],
|
||||
np.isnan(rho_abs[...,1]))
|
||||
elif self.family == 'triclinic':
|
||||
if self.family == 'triclinic':
|
||||
return np.ones(rho_abs.shape[:-1]).astype(bool)
|
||||
else:
|
||||
raise TypeError(f'unknown symmetry "{self.family}"')
|
||||
|
||||
raise TypeError(f'unknown symmetry "{self.family}"')
|
||||
|
||||
|
||||
@property
|
||||
|
@ -510,38 +441,40 @@ class Orientation(Rotation,Crystal):
|
|||
return ((rho[...,0] >= rho[...,1]) &
|
||||
(rho[...,1] >= rho[...,2]) &
|
||||
(rho[...,2] >= 0)).astype(bool)
|
||||
elif self.family == 'hexagonal':
|
||||
if self.family == 'hexagonal':
|
||||
return ((rho[...,0] >= rho[...,1]*np.sqrt(3)) &
|
||||
(rho[...,1] >= 0) &
|
||||
(rho[...,2] >= 0)).astype(bool)
|
||||
elif self.family == 'tetragonal':
|
||||
if self.family == 'tetragonal':
|
||||
return ((rho[...,0] >= rho[...,1]) &
|
||||
(rho[...,1] >= 0) &
|
||||
(rho[...,2] >= 0)).astype(bool)
|
||||
elif self.family == 'orthorhombic':
|
||||
if self.family == 'orthorhombic':
|
||||
return ((rho[...,0] >= 0) &
|
||||
(rho[...,1] >= 0) &
|
||||
(rho[...,2] >= 0)).astype(bool)
|
||||
elif self.family == 'monoclinic':
|
||||
if self.family == 'monoclinic':
|
||||
return ((rho[...,1] >= 0) &
|
||||
(rho[...,2] >= 0)).astype(bool)
|
||||
else:
|
||||
return np.ones_like(rho[...,0],dtype=bool)
|
||||
|
||||
return np.ones_like(rho[...,0],dtype=bool)
|
||||
|
||||
def disorientation(self,
|
||||
other: 'Orientation',
|
||||
return_operators: bool = False) -> object:
|
||||
"""
|
||||
Calculate disorientation between myself and given other orientation.
|
||||
Calculate disorientation between self and given other orientation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
other : Orientation
|
||||
Orientation to calculate disorientation for.
|
||||
Shape of other blends with shape of own rotation array.
|
||||
For example, shapes of (2,3) for own rotations and (3,2) for other's result in (2,3,2) disorientations.
|
||||
For example, shapes of (2,3) for own rotations
|
||||
and (3,2) for other's result in (2,3,2) disorientations.
|
||||
return_operators : bool, optional
|
||||
Return index pair of symmetrically equivalent orientations that result in disorientation axis falling into FZ.
|
||||
Return index pair of symmetrically equivalent orientations
|
||||
that result in disorientation axis falling into FZ.
|
||||
Defaults to False.
|
||||
|
||||
Returns
|
||||
|
@ -578,8 +511,8 @@ class Orientation(Rotation,Crystal):
|
|||
>>> N = 10000
|
||||
>>> a = damask.Orientation.from_random(shape=N,family='cubic')
|
||||
>>> b = damask.Orientation.from_random(shape=N,family='cubic')
|
||||
>>> d = a.disorientation(b).as_axis_angle(degrees=True,pair=True)[1]
|
||||
>>> plt.hist(d,25)
|
||||
>>> n,omega = a.disorientation(b).as_axis_angle(degrees=True,pair=True)
|
||||
>>> plt.hist(omega,25)
|
||||
>>> plt.show()
|
||||
|
||||
"""
|
||||
|
@ -626,6 +559,7 @@ class Orientation(Rotation,Crystal):
|
|||
----------
|
||||
weights : numpy.ndarray, shape (self.shape), optional
|
||||
Relative weights of orientations.
|
||||
Defaults to equal weights.
|
||||
return_cloud : bool, optional
|
||||
Return the specific (symmetrically equivalent) orientations that were averaged.
|
||||
Defaults to False.
|
||||
|
@ -895,8 +829,8 @@ class Orientation(Rotation,Crystal):
|
|||
Schmid matrix (in lab frame) of first octahedral slip system of a face-centered
|
||||
cubic crystal in "Goss" orientation.
|
||||
|
||||
>>> import damask
|
||||
>>> import numpy as np
|
||||
>>> import damask
|
||||
>>> np.set_printoptions(3,suppress=True,floatmode='fixed')
|
||||
>>> O = damask.Orientation.from_Euler_angles(phi=[0,45,0],degrees=True,lattice='cF')
|
||||
>>> O.Schmid(N_slip=[1])
|
||||
|
@ -936,8 +870,9 @@ class Orientation(Rotation,Crystal):
|
|||
|
||||
Returns
|
||||
-------
|
||||
Orientations related to self following the selected
|
||||
model for the orientation relationship.
|
||||
rel : Orientation, shape (:,self.shape)
|
||||
Orientations related to self according to the selected
|
||||
model for the orientation relationship.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
|
|
@ -15,7 +15,7 @@ from typing import Optional, Union, Callable, Any, Sequence, Literal, Dict, List
|
|||
|
||||
import h5py
|
||||
import numpy as np
|
||||
import numpy.ma as ma
|
||||
from numpy import ma
|
||||
|
||||
import damask
|
||||
from . import VTK
|
||||
|
@ -65,9 +65,9 @@ def _empty_like(dataset: np.ma.core.MaskedArray,
|
|||
|
||||
class Result:
|
||||
"""
|
||||
Add data to and export data from a DADF5 file.
|
||||
Add data to and export data from a DADF5 (DAMASK HDF5) file.
|
||||
|
||||
A DADF5 (DAMASK HDF5) file contains DAMASK results.
|
||||
A DADF5 file contains DAMASK results.
|
||||
Its group/folder structure reflects the layout in material.yaml.
|
||||
|
||||
This class provides a customizable view on the DADF5 file.
|
||||
|
@ -93,7 +93,7 @@ class Result:
|
|||
|
||||
def __init__(self, fname: Union[str, Path]):
|
||||
"""
|
||||
New result view bound to a HDF5 file.
|
||||
New result view bound to a DADF5 file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -106,10 +106,8 @@ class Result:
|
|||
self.version_major = f.attrs['DADF5_version_major']
|
||||
self.version_minor = f.attrs['DADF5_version_minor']
|
||||
|
||||
if self.version_major != 0 or not 12 <= self.version_minor <= 14:
|
||||
if (self.version_major != 0 or not 14 <= self.version_minor <= 14) and self.version_major != 1:
|
||||
raise TypeError(f'unsupported DADF5 version "{self.version_major}.{self.version_minor}"')
|
||||
if self.version_major == 0 and self.version_minor < 14:
|
||||
self.export_simulation_setup = None # type: ignore
|
||||
|
||||
self.structured = 'cells' in f['geometry'].attrs.keys()
|
||||
|
||||
|
@ -167,7 +165,7 @@ class Result:
|
|||
"""
|
||||
Return repr(self).
|
||||
|
||||
Give short human-readable summary.
|
||||
Give short, human-readable summary.
|
||||
|
||||
"""
|
||||
with h5py.File(self.fname,'r') as f:
|
||||
|
@ -195,7 +193,7 @@ class Result:
|
|||
homogenizations: Union[None, str, Sequence[str], bool] = None,
|
||||
fields: Union[None, str, Sequence[str], bool] = None) -> "Result":
|
||||
"""
|
||||
Manages the visibility of the groups.
|
||||
Manage the visibility of the groups.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -319,15 +317,15 @@ class Result:
|
|||
Parameters
|
||||
----------
|
||||
increments: (list of) int, (list of) str, or bool, optional.
|
||||
Number(s) of increments to select.
|
||||
Numbers of increments to select.
|
||||
times: (list of) float, (list of) str, or bool, optional.
|
||||
Simulation time(s) of increments to select.
|
||||
Simulation times of increments to select.
|
||||
phases: (list of) str, or bool, optional.
|
||||
Name(s) of phases to select.
|
||||
Names of phases to select.
|
||||
homogenizations: (list of) str, or bool, optional.
|
||||
Name(s) of homogenizations to select.
|
||||
Names of homogenizations to select.
|
||||
fields: (list of) str, or bool, optional.
|
||||
Name(s) of fields to select.
|
||||
Names of fields to select.
|
||||
protected: bool, optional.
|
||||
Protection status of existing data.
|
||||
|
||||
|
@ -375,15 +373,15 @@ class Result:
|
|||
Parameters
|
||||
----------
|
||||
increments: (list of) int, (list of) str, or bool, optional.
|
||||
Number(s) of increments to select.
|
||||
Numbers of increments to select.
|
||||
times: (list of) float, (list of) str, or bool, optional.
|
||||
Simulation time(s) of increments to select.
|
||||
Simulation times of increments to select.
|
||||
phases: (list of) str, or bool, optional.
|
||||
Name(s) of phases to select.
|
||||
Names of phases to select.
|
||||
homogenizations: (list of) str, or bool, optional.
|
||||
Name(s) of homogenizations to select.
|
||||
Names of homogenizations to select.
|
||||
fields: (list of) str, or bool, optional.
|
||||
Name(s) of fields to select.
|
||||
Names of fields to select.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -418,15 +416,15 @@ class Result:
|
|||
Parameters
|
||||
----------
|
||||
increments: (list of) int, (list of) str, or bool, optional.
|
||||
Number(s) of increments to select.
|
||||
Numbers of increments to select.
|
||||
times: (list of) float, (list of) str, or bool, optional.
|
||||
Simulation time(s) of increments to select.
|
||||
Simulation times of increments to select.
|
||||
phases: (list of) str, or bool, optional.
|
||||
Name(s) of phases to select.
|
||||
Names of phases to select.
|
||||
homogenizations: (list of) str, or bool, optional.
|
||||
Name(s) of homogenizations to select.
|
||||
Names of homogenizations to select.
|
||||
fields: (list of) str, or bool, optional.
|
||||
Name(s) of fields to select.
|
||||
Names of fields to select.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -678,7 +676,7 @@ class Result:
|
|||
... '1/m²','total mobile dislocation density')
|
||||
>>> r.add_calculation('np.sum(#rho_dip#,axis=1)','rho_dip_total',
|
||||
... '1/m²','total dislocation dipole density')
|
||||
>>> r.add_calculation('#rho_dip_total#+#rho_mob_total','rho_total',
|
||||
>>> r.add_calculation('#rho_dip_total#+#rho_mob_total#','rho_total',
|
||||
... '1/m²','total dislocation density')
|
||||
|
||||
Add Mises equivalent of the Cauchy stress without storage of
|
||||
|
@ -721,9 +719,11 @@ class Result:
|
|||
Parameters
|
||||
----------
|
||||
P : str, optional
|
||||
Name of the dataset containing the first Piola-Kirchhoff stress. Defaults to 'P'.
|
||||
Name of the dataset containing the first Piola-Kirchhoff stress.
|
||||
Defaults to 'P'.
|
||||
F : str, optional
|
||||
Name of the dataset containing the deformation gradient. Defaults to 'F'.
|
||||
Name of the dataset containing the deformation gradient.
|
||||
Defaults to 'F'.
|
||||
|
||||
"""
|
||||
self._add_generic_pointwise(self._add_stress_Cauchy,{'P':P,'F':F})
|
||||
|
@ -1023,14 +1023,14 @@ class Result:
|
|||
x: str,
|
||||
ord: Union[None, int, float, Literal['fro', 'nuc']] = None):
|
||||
"""
|
||||
Add the norm of vector or tensor.
|
||||
Add the norm of a vector or tensor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : str
|
||||
Name of vector or tensor dataset.
|
||||
ord : {non-zero int, inf, -inf, 'fro', 'nuc'}, optional
|
||||
Order of the norm. inf means NumPy’s inf object. For details refer to numpy.linalg.norm.
|
||||
Order of the norm. inf means NumPy's inf object. For details refer to numpy.linalg.norm.
|
||||
|
||||
"""
|
||||
self._add_generic_pointwise(self._add_norm,{'x':x},{'ord':ord})
|
||||
|
@ -1052,7 +1052,7 @@ class Result:
|
|||
def add_stress_second_Piola_Kirchhoff(self,
|
||||
P: str = 'P',
|
||||
F: str = 'F'):
|
||||
"""
|
||||
r"""
|
||||
Add second Piola-Kirchhoff stress calculated from first Piola-Kirchhoff stress and deformation gradient.
|
||||
|
||||
Parameters
|
||||
|
@ -1064,9 +1064,10 @@ class Result:
|
|||
|
||||
Notes
|
||||
-----
|
||||
The definition of the second Piola-Kirchhoff stress (S = [F^-1 P]_sym)
|
||||
The definition of the second Piola-Kirchhoff stress
|
||||
:math:`\vb{S} = \left(\vb{F}^{-1} \vb{P}\right)_\text{sym}`
|
||||
follows the standard definition in nonlinear continuum mechanics.
|
||||
As such, no intermediate configuration, for instance that reached by F_p,
|
||||
As such, no intermediate configuration, for instance that reached by :math:`\vb{F}_\text{p}`,
|
||||
is taken into account.
|
||||
|
||||
"""
|
||||
|
@ -1240,10 +1241,11 @@ class Result:
|
|||
|
||||
Notes
|
||||
-----
|
||||
The incoporation of rotational parts into the elastic and plastic
|
||||
deformation gradient requires it to use material/Lagragian strain measures
|
||||
(based on 'U') for plastic strains and spatial/Eulerian strain measures
|
||||
(based on 'V') for elastic strains when calculating averages.
|
||||
The presence of rotational parts in the elastic and plastic deformation gradient
|
||||
calls for the use of
|
||||
material/Lagragian strain measures (based on 'U') for plastic strains and
|
||||
spatial/Eulerian strain measures (based on 'V') for elastic strains
|
||||
when calculating averages.
|
||||
|
||||
"""
|
||||
self._add_generic_pointwise(self._add_strain,{'F':F},{'t':t,'m':m})
|
||||
|
@ -1302,7 +1304,7 @@ class Result:
|
|||
Notes
|
||||
-----
|
||||
This function is only available for structured grids,
|
||||
i.e. results from the grid solver.
|
||||
i.e. fields resulting from the grid solver.
|
||||
|
||||
"""
|
||||
self._add_generic_grid(self._add_curl,{'f':f},{'size':self.size})
|
||||
|
@ -1331,7 +1333,7 @@ class Result:
|
|||
Notes
|
||||
-----
|
||||
This function is only available for structured grids,
|
||||
i.e. results from the grid solver.
|
||||
i.e. fields resulting from the grid solver.
|
||||
|
||||
"""
|
||||
self._add_generic_grid(self._add_divergence,{'f':f},{'size':self.size})
|
||||
|
@ -1361,7 +1363,7 @@ class Result:
|
|||
Notes
|
||||
-----
|
||||
This function is only available for structured grids,
|
||||
i.e. results from the grid solver.
|
||||
i.e. fields resulting from the grid solver.
|
||||
|
||||
"""
|
||||
self._add_generic_grid(self._add_gradient,{'f':f},{'size':self.size})
|
||||
|
@ -1379,10 +1381,10 @@ class Result:
|
|||
----------
|
||||
func : function
|
||||
Callback function that calculates a new dataset from one or
|
||||
more datasets per HDF5 group.
|
||||
more datasets per DADF5 group.
|
||||
datasets : dictionary
|
||||
Details of the datasets to be used:
|
||||
{arg (name to which the data is passed in func): label (in HDF5 file)}.
|
||||
{arg (name to which the data is passed in func): label (in DADF5 file)}.
|
||||
args : dictionary, optional
|
||||
Arguments parsed to func.
|
||||
|
||||
|
@ -1462,10 +1464,10 @@ class Result:
|
|||
----------
|
||||
callback : function
|
||||
Callback function that calculates a new dataset from one or
|
||||
more datasets per HDF5 group.
|
||||
more datasets per DADF5 group.
|
||||
datasets : dictionary
|
||||
Details of the datasets to be used:
|
||||
{arg (name to which the data is passed in func): label (in HDF5 file)}.
|
||||
{arg (name to which the data is passed in func): label (in DADF5 file)}.
|
||||
args : dictionary, optional
|
||||
Arguments parsed to func.
|
||||
|
||||
|
@ -1500,7 +1502,7 @@ class Result:
|
|||
dataset.attrs['overwritten'] = True
|
||||
else:
|
||||
shape = result['data'].shape
|
||||
if compress := (result['data'].size >= chunk_size*2):
|
||||
if compress := result['data'].size >= chunk_size*2:
|
||||
chunks = (chunk_size//np.prod(shape[1:]),)+shape[1:]
|
||||
else:
|
||||
chunks = shape
|
||||
|
@ -1828,9 +1830,10 @@ class Result:
|
|||
Export to VTK cell/point data.
|
||||
|
||||
One VTK file per visible increment is created.
|
||||
For point data, the VTK format is poly data (.vtp).
|
||||
For cell data, either an image (.vti) or unstructured (.vtu) dataset
|
||||
is written for grid-based or mesh-based simulations, respectively.
|
||||
For point data, the VTK format is PolyData (.vtp).
|
||||
For cell data, the file format is either ImageData (.vti)
|
||||
or UnstructuredGrid (.vtu) for grid-based or mesh-based simulations,
|
||||
respectively.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -1922,7 +1925,8 @@ class Result:
|
|||
|
||||
def export_DADF5(self,
|
||||
fname,
|
||||
output: Union[str, List[str]] = '*'):
|
||||
output: Union[str, List[str]] = '*',
|
||||
mapping = None):
|
||||
"""
|
||||
Export visible components into a new DADF5 file.
|
||||
|
||||
|
@ -1936,20 +1940,61 @@ class Result:
|
|||
output : (list of) str, optional
|
||||
Names of the datasets to export.
|
||||
Defaults to '*', in which case all visible datasets are exported.
|
||||
mapping : numpy.ndarray of int, shape (:,:,:), optional
|
||||
Indices for regridding.
|
||||
|
||||
"""
|
||||
if Path(fname).expanduser().absolute() == self.fname:
|
||||
raise PermissionError(f'cannot overwrite {self.fname}')
|
||||
|
||||
def cp(path_in,path_out,label,mapping):
|
||||
if mapping is None:
|
||||
path_in.copy(label,path_out)
|
||||
else:
|
||||
path_out.create_dataset(label,data=path_in[label][()][mapping])
|
||||
path_out[label].attrs.update(path_in[label].attrs)
|
||||
|
||||
|
||||
with h5py.File(self.fname,'r') as f_in, h5py.File(fname,'w') as f_out:
|
||||
for k,v in f_in.attrs.items():
|
||||
f_out.attrs.create(k,v)
|
||||
for g in ['setup','geometry','cell_to']:
|
||||
f_out.attrs.update(f_in.attrs)
|
||||
for g in ['setup','geometry'] + (['cell_to'] if mapping is None else []):
|
||||
f_in.copy(g,f_out)
|
||||
|
||||
if mapping is not None:
|
||||
cells = mapping.shape
|
||||
mapping_flat = mapping.flatten(order='F')
|
||||
f_out['geometry'].attrs['cells'] = cells
|
||||
f_out.create_group('cell_to') # ToDo: attribute missing
|
||||
mappings = {'phase':{},'homogenization':{}} # type: ignore
|
||||
|
||||
mapping_phase = f_in['cell_to']['phase'][()][mapping_flat]
|
||||
for p in np.unique(mapping_phase['label']):
|
||||
m = mapping_phase['label'] == p
|
||||
mappings['phase'][p] = mapping_phase[m]['entry']
|
||||
c = np.count_nonzero(m)
|
||||
mapping_phase[m] = list(zip((p,)*c,tuple(np.arange(c))))
|
||||
f_out['cell_to'].create_dataset('phase',data=mapping_phase.reshape(np.prod(mapping_flat.shape),-1))
|
||||
|
||||
mapping_homog = f_in['cell_to']['homogenization'][()][mapping]
|
||||
for h in np.unique(mapping_homog['label']):
|
||||
m = mapping_homog['label'] == h
|
||||
mappings['homogenization'][h] = mapping_homog[m]['entry']
|
||||
c = np.count_nonzero(m)
|
||||
mapping_homog[mapping_homog['label'] == h] = list(zip((h,)*c,tuple(np.arange(c))))
|
||||
f_out['cell_to'].create_dataset('homogenization',data=mapping_homog.flatten())
|
||||
|
||||
|
||||
for inc in util.show_progress(self.visible['increments']):
|
||||
f_in.copy(inc,f_out,shallow=True)
|
||||
for out in _match(output,f_in['/'.join([inc,'geometry'])].keys()):
|
||||
f_in[inc]['geometry'].copy(out,f_out[inc]['geometry'])
|
||||
if mapping is None:
|
||||
for label in ['u_p','u_n']:
|
||||
f_in[inc]['geometry'].copy(label,f_out[inc]['geometry'])
|
||||
else:
|
||||
u_p = f_in[inc]['geometry']['u_p'][()][mapping_flat]
|
||||
f_out[inc]['geometry'].create_dataset('u_p',data=u_p)
|
||||
u_n = np.zeros((len(mapping_flat),3)) # ToDo: needs implementation
|
||||
f_out[inc]['geometry'].create_dataset('u_n',data=u_n)
|
||||
|
||||
|
||||
for label in self.homogenizations:
|
||||
f_in[inc]['homogenization'].copy(label,f_out[inc]['homogenization'],shallow=True)
|
||||
|
@ -1961,7 +2006,7 @@ class Result:
|
|||
for field in _match(self.visible['fields'],f_in['/'.join([inc,ty,label])].keys()):
|
||||
p = '/'.join([inc,ty,label,field])
|
||||
for out in _match(output,f_in[p].keys()):
|
||||
f_in[p].copy(out,f_out[p])
|
||||
cp(f_in[p],f_out[p],out,None if mapping is None else mappings[ty][label.encode()])
|
||||
|
||||
|
||||
def export_simulation_setup(self,
|
||||
|
|
|
@ -35,8 +35,8 @@ class Rotation:
|
|||
Rotate vector 'a' (defined in coordinate system 'A') to
|
||||
coordinates 'b' expressed in system 'B':
|
||||
|
||||
>>> import damask
|
||||
>>> import numpy as np
|
||||
>>> import damask
|
||||
>>> Q = damask.Rotation.from_random()
|
||||
>>> a = np.random.rand(3)
|
||||
>>> b = Q @ a
|
||||
|
@ -45,8 +45,8 @@ class Rotation:
|
|||
|
||||
Compound rotations R1 (first) and R2 (second):
|
||||
|
||||
>>> import damask
|
||||
>>> import numpy as np
|
||||
>>> import damask
|
||||
>>> R1 = damask.Rotation.from_random()
|
||||
>>> R2 = damask.Rotation.from_random()
|
||||
>>> R = R2 * R1
|
||||
|
@ -69,7 +69,7 @@ class Rotation:
|
|||
|
||||
Parameters
|
||||
----------
|
||||
rotation : list, numpy.ndarray, Rotation, optional
|
||||
rotation : list, numpy.ndarray, or Rotation, optional
|
||||
Unit quaternion in positive real hemisphere.
|
||||
Use .from_quaternion to perform a sanity check.
|
||||
Defaults to no rotation.
|
||||
|
@ -88,7 +88,7 @@ class Rotation:
|
|||
"""
|
||||
Return repr(self).
|
||||
|
||||
Give short human-readable summary.
|
||||
Give short, human-readable summary.
|
||||
|
||||
"""
|
||||
return f'Quaternion{" " if self.quaternion.shape == (4,) else "s of shape "+str(self.quaternion.shape[:-1])+chr(10)}'\
|
||||
|
@ -902,7 +902,8 @@ class Rotation:
|
|||
return Rotation(Rotation._om2qu(om))
|
||||
|
||||
@staticmethod
|
||||
def from_matrix(R: np.ndarray) -> 'Rotation':
|
||||
def from_matrix(R: np.ndarray,
|
||||
normalize: bool = False) -> 'Rotation':
|
||||
"""
|
||||
Initialize from rotation matrix.
|
||||
|
||||
|
@ -910,13 +911,17 @@ class Rotation:
|
|||
----------
|
||||
R : numpy.ndarray, shape (...,3,3)
|
||||
Rotation matrix with det(R) = 1 and R.T ∙ R = I.
|
||||
normalize : bool, optional
|
||||
Rescales rotation matrix to unit determinant. Defaults to False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
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
|
||||
def from_parallel(a: np.ndarray,
|
||||
|
|
|
@ -41,7 +41,7 @@ class Table:
|
|||
"""
|
||||
Return repr(self).
|
||||
|
||||
Give short human-readable summary.
|
||||
Give short, human-readable summary.
|
||||
|
||||
"""
|
||||
self._relabel('shapes')
|
||||
|
@ -255,8 +255,8 @@ class Table:
|
|||
"""
|
||||
Load from ASCII table file.
|
||||
|
||||
Initial comments are marked by '#', the first non-comment line
|
||||
containing the column labels.
|
||||
Initial comments are marked by '#'.
|
||||
The first non-comment line contains the column labels.
|
||||
|
||||
- Vector data column labels are indicated by '1_v, 2_v, ..., n_v'.
|
||||
- Tensor data column labels are indicated by '3x3:1_T, 3x3:2_T, ..., 3x3:9_T'.
|
||||
|
@ -264,7 +264,7 @@ class Table:
|
|||
Parameters
|
||||
----------
|
||||
fname : file, str, or pathlib.Path
|
||||
Filename or file for reading.
|
||||
Filename or file to read.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -299,11 +299,18 @@ class Table:
|
|||
|
||||
|
||||
@staticmethod
|
||||
def load_ang(fname: FileHandle) -> 'Table':
|
||||
def load_ang(fname: FileHandle,
|
||||
shapes = {'eu':3,
|
||||
'pos':2,
|
||||
'IQ':1,
|
||||
'CI':1,
|
||||
'ID':1,
|
||||
'intensity':1,
|
||||
'fit':1}) -> 'Table':
|
||||
"""
|
||||
Load from ang file.
|
||||
Load from ANG file.
|
||||
|
||||
A valid TSL ang file has to have the following columns:
|
||||
Regular ANG files feature the following columns:
|
||||
|
||||
- Euler angles (Bunge notation) in radians, 3 floats, label 'eu'.
|
||||
- Spatial position in meters, 2 floats, label 'pos'.
|
||||
|
@ -316,7 +323,10 @@ class Table:
|
|||
Parameters
|
||||
----------
|
||||
fname : file, str, or pathlib.Path
|
||||
Filename or file for reading.
|
||||
Filename or file to read.
|
||||
shapes : dict with str:int pairs, optional
|
||||
Column labels and their width.
|
||||
Defaults to standard TSL ANG format.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -338,7 +348,6 @@ class Table:
|
|||
|
||||
data = np.loadtxt(content)
|
||||
|
||||
shapes = {'eu':3, 'pos':2, 'IQ':1, 'CI':1, 'ID':1, 'intensity':1, 'fit':1}
|
||||
if (remainder := data.shape[1]-sum(shapes.values())) > 0:
|
||||
shapes['unknown'] = remainder
|
||||
|
||||
|
@ -458,9 +467,9 @@ class Table:
|
|||
Parameters
|
||||
----------
|
||||
label_old : (iterable of) str
|
||||
Old column label(s).
|
||||
Old column labels.
|
||||
label_new : (iterable of) str
|
||||
New column label(s).
|
||||
New column labels.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -488,7 +497,7 @@ class Table:
|
|||
label : str or list
|
||||
Column labels for sorting.
|
||||
ascending : bool or list, optional
|
||||
Set sort order.
|
||||
Set sort order. Defaults to True.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -574,7 +583,7 @@ class Table:
|
|||
Parameters
|
||||
----------
|
||||
fname : file, str, or pathlib.Path
|
||||
Filename or file for writing.
|
||||
Filename or file to write.
|
||||
with_labels : bool, optional
|
||||
Write column labels. Defaults to True.
|
||||
|
||||
|
@ -594,4 +603,7 @@ class Table:
|
|||
f = util.open_text(fname,'w')
|
||||
|
||||
f.write('\n'.join([f'# {c}' for c in self.comments] + [' '.join(labels)])+('\n' if labels else ''))
|
||||
self.data.to_csv(f,sep=' ',na_rep='nan',index=False,header=False,line_terminator='\n')
|
||||
try: # backward compatibility
|
||||
self.data.to_csv(f,sep=' ',na_rep='nan',index=False,header=False,lineterminator='\n')
|
||||
except TypeError:
|
||||
self.data.to_csv(f,sep=' ',na_rep='nan',index=False,header=False,line_terminator='\n')
|
||||
|
|
|
@ -8,6 +8,7 @@ import numpy as np
|
|||
|
||||
FloatSequence = Union[np.ndarray,Sequence[float]]
|
||||
IntSequence = Union[np.ndarray,Sequence[int]]
|
||||
StrSequence = Union[np.ndarray,Sequence[str]]
|
||||
FileHandle = Union[TextIO, str, Path]
|
||||
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']]
|
||||
|
|
|
@ -4,10 +4,54 @@ from pathlib import Path
|
|||
from typing import Optional, Union, Literal, List, Sequence
|
||||
|
||||
import numpy as np
|
||||
import vtk
|
||||
from vtk.util.numpy_support import numpy_to_vtk as np_to_vtk
|
||||
from vtk.util.numpy_support import numpy_to_vtkIdTypeArray as np_to_vtkIdTypeArray
|
||||
from vtk.util.numpy_support import vtk_to_numpy as vtk_to_np
|
||||
from vtkmodules.vtkCommonCore import (
|
||||
vtkPoints,
|
||||
vtkStringArray,
|
||||
vtkLookupTable,
|
||||
)
|
||||
from vtkmodules.vtkCommonDataModel import (
|
||||
vtkDataSet,
|
||||
vtkCellArray,
|
||||
vtkImageData,
|
||||
vtkRectilinearGrid,
|
||||
vtkUnstructuredGrid,
|
||||
vtkPolyData,
|
||||
)
|
||||
from vtkmodules.vtkIOLegacy import (
|
||||
vtkGenericDataObjectReader,
|
||||
vtkDataSetWriter,
|
||||
)
|
||||
from vtkmodules.vtkIOXML import (
|
||||
vtkXMLImageDataReader,
|
||||
vtkXMLImageDataWriter,
|
||||
vtkXMLRectilinearGridReader,
|
||||
vtkXMLRectilinearGridWriter,
|
||||
vtkXMLUnstructuredGridReader,
|
||||
vtkXMLUnstructuredGridWriter,
|
||||
vtkXMLPolyDataReader,
|
||||
vtkXMLPolyDataWriter,
|
||||
)
|
||||
from vtkmodules.vtkRenderingCore import (
|
||||
vtkDataSetMapper,
|
||||
vtkActor,
|
||||
vtkRenderer,
|
||||
vtkRenderWindow,
|
||||
vtkRenderWindowInteractor,
|
||||
)
|
||||
from vtkmodules.vtkRenderingAnnotation import (
|
||||
vtkScalarBarActor,
|
||||
)
|
||||
from vtkmodules.util.vtkConstants import (
|
||||
VTK_TRIANGLE,
|
||||
VTK_QUAD,
|
||||
VTK_TETRA,
|
||||
VTK_HEXAHEDRON,
|
||||
)
|
||||
from vtkmodules.util.numpy_support import (
|
||||
numpy_to_vtk,
|
||||
numpy_to_vtkIdTypeArray,
|
||||
vtk_to_numpy,
|
||||
)
|
||||
|
||||
from ._typehints import FloatSequence, IntSequence
|
||||
from . import util
|
||||
|
@ -23,16 +67,16 @@ class VTK:
|
|||
"""
|
||||
|
||||
def __init__(self,
|
||||
vtk_data: vtk.vtkDataSet):
|
||||
vtk_data: vtkDataSet):
|
||||
"""
|
||||
New spatial visualization.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
vtk_data : subclass of vtk.vtkDataSet
|
||||
vtk_data : subclass of vtkDataSet
|
||||
Description of geometry and topology, optionally with attached data.
|
||||
Valid types are vtk.vtkImageData, vtk.vtkUnstructuredGrid,
|
||||
vtk.vtkPolyData, and vtk.vtkRectilinearGrid.
|
||||
Valid types are vtkImageData, vtkUnstructuredGrid,
|
||||
vtkPolyData, and vtkRectilinearGrid.
|
||||
|
||||
"""
|
||||
self.vtk_data = vtk_data
|
||||
|
@ -42,7 +86,7 @@ class VTK:
|
|||
"""
|
||||
Return repr(self).
|
||||
|
||||
Give short human-readable summary.
|
||||
Give short, human-readable summary.
|
||||
|
||||
"""
|
||||
info = [self.vtk_data.__vtkname__]
|
||||
|
@ -76,14 +120,14 @@ class VTK:
|
|||
|
||||
|
||||
def copy(self):
|
||||
if isinstance(self.vtk_data,vtk.vtkImageData):
|
||||
dup = vtk.vtkImageData()
|
||||
elif isinstance(self.vtk_data,vtk.vtkUnstructuredGrid):
|
||||
dup = vtk.vtkUnstructuredGrid()
|
||||
elif isinstance(self.vtk_data,vtk.vtkPolyData):
|
||||
dup = vtk.vtkPolyData()
|
||||
elif isinstance(self.vtk_data,vtk.vtkRectilinearGrid):
|
||||
dup = vtk.vtkRectilinearGrid()
|
||||
if isinstance(self.vtk_data,vtkImageData):
|
||||
dup = vtkImageData()
|
||||
elif isinstance(self.vtk_data,vtkUnstructuredGrid):
|
||||
dup = vtkUnstructuredGrid()
|
||||
elif isinstance(self.vtk_data,vtkPolyData):
|
||||
dup = vtkPolyData()
|
||||
elif isinstance(self.vtk_data,vtkRectilinearGrid):
|
||||
dup = vtkRectilinearGrid()
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
|
@ -114,7 +158,7 @@ class VTK:
|
|||
Comments.
|
||||
|
||||
"""
|
||||
s = vtk.vtkStringArray()
|
||||
s = vtkStringArray()
|
||||
s.SetName('comments')
|
||||
for c in comments:
|
||||
s.InsertNextValue(c)
|
||||
|
@ -154,7 +198,7 @@ class VTK:
|
|||
size: FloatSequence,
|
||||
origin: FloatSequence = np.zeros(3)) -> 'VTK':
|
||||
"""
|
||||
Create VTK of type vtk.vtkImageData.
|
||||
Create VTK of type vtkImageData.
|
||||
|
||||
This is the common type for grid solver results.
|
||||
|
||||
|
@ -163,7 +207,7 @@ class VTK:
|
|||
cells : sequence of int, len (3)
|
||||
Number of cells along each dimension.
|
||||
size : sequence of float, len (3)
|
||||
Physical length along each dimension.
|
||||
Edge length along each dimension.
|
||||
origin : sequence of float, len (3), optional
|
||||
Coordinates of grid origin.
|
||||
|
||||
|
@ -173,7 +217,7 @@ class VTK:
|
|||
VTK-based geometry without nodal or cell data.
|
||||
|
||||
"""
|
||||
vtk_data = vtk.vtkImageData()
|
||||
vtk_data = vtkImageData()
|
||||
vtk_data.SetDimensions(*(np.array(cells)+1))
|
||||
vtk_data.SetOrigin(*(np.array(origin)))
|
||||
vtk_data.SetSpacing(*(np.array(size)/np.array(cells)))
|
||||
|
@ -186,7 +230,7 @@ class VTK:
|
|||
connectivity: np.ndarray,
|
||||
cell_type: str) -> 'VTK':
|
||||
"""
|
||||
Create VTK of type vtk.vtkUnstructuredGrid.
|
||||
Create VTK of type vtkUnstructuredGrid.
|
||||
|
||||
This is the common type for mesh solver results.
|
||||
|
||||
|
@ -198,7 +242,7 @@ class VTK:
|
|||
Cell connectivity (0-based), first dimension determines #Cells,
|
||||
second dimension determines #Nodes/Cell.
|
||||
cell_type : str
|
||||
Name of the vtk.vtkCell subclass. Tested for TRIANGLE, QUAD, TETRA, and HEXAHEDRON.
|
||||
Name of the vtkCell subclass. Tested for TRIANGLE, QUAD, TETRA, and HEXAHEDRON.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -206,18 +250,18 @@ class VTK:
|
|||
VTK-based geometry without nodal or cell data.
|
||||
|
||||
"""
|
||||
vtk_nodes = vtk.vtkPoints()
|
||||
vtk_nodes.SetData(np_to_vtk(np.ascontiguousarray(nodes)))
|
||||
cells = vtk.vtkCellArray()
|
||||
vtk_nodes = vtkPoints()
|
||||
vtk_nodes.SetData(numpy_to_vtk(np.ascontiguousarray(nodes)))
|
||||
cells = vtkCellArray()
|
||||
cells.SetNumberOfCells(connectivity.shape[0])
|
||||
T = np.concatenate((np.ones((connectivity.shape[0],1),dtype=np.int64)*connectivity.shape[1],
|
||||
connectivity),axis=1).ravel()
|
||||
cells.SetCells(connectivity.shape[0],np_to_vtkIdTypeArray(T,deep=True))
|
||||
cells.SetCells(connectivity.shape[0],numpy_to_vtkIdTypeArray(T,deep=True))
|
||||
|
||||
vtk_data = vtk.vtkUnstructuredGrid()
|
||||
vtk_data = vtkUnstructuredGrid()
|
||||
vtk_data.SetPoints(vtk_nodes)
|
||||
cell_types = {'TRIANGLE':vtk.VTK_TRIANGLE, 'QUAD':vtk.VTK_QUAD,
|
||||
'TETRA' :vtk.VTK_TETRA, 'HEXAHEDRON':vtk.VTK_HEXAHEDRON}
|
||||
cell_types = {'TRIANGLE':VTK_TRIANGLE, 'QUAD':VTK_QUAD,
|
||||
'TETRA' :VTK_TETRA, 'HEXAHEDRON':VTK_HEXAHEDRON}
|
||||
vtk_data.SetCells(cell_types[cell_type.split("_",1)[-1].upper()],cells)
|
||||
|
||||
return VTK(vtk_data)
|
||||
|
@ -226,7 +270,7 @@ class VTK:
|
|||
@staticmethod
|
||||
def from_poly_data(points: np.ndarray) -> 'VTK':
|
||||
"""
|
||||
Create VTK of type vtk.polyData.
|
||||
Create VTK of type polyData.
|
||||
|
||||
This is the common type for point-wise data.
|
||||
|
||||
|
@ -242,15 +286,15 @@ class VTK:
|
|||
|
||||
"""
|
||||
N = points.shape[0]
|
||||
vtk_points = vtk.vtkPoints()
|
||||
vtk_points.SetData(np_to_vtk(np.ascontiguousarray(points)))
|
||||
vtk_points = vtkPoints()
|
||||
vtk_points.SetData(numpy_to_vtk(np.ascontiguousarray(points)))
|
||||
|
||||
vtk_cells = vtk.vtkCellArray()
|
||||
vtk_cells = vtkCellArray()
|
||||
vtk_cells.SetNumberOfCells(N)
|
||||
vtk_cells.SetCells(N,np_to_vtkIdTypeArray(np.stack((np.ones (N,dtype=np.int64),
|
||||
vtk_cells.SetCells(N,numpy_to_vtkIdTypeArray(np.stack((np.ones (N,dtype=np.int64),
|
||||
np.arange(N,dtype=np.int64)),axis=1).ravel(),deep=True))
|
||||
|
||||
vtk_data = vtk.vtkPolyData()
|
||||
vtk_data = vtkPolyData()
|
||||
vtk_data.SetPoints(vtk_points)
|
||||
vtk_data.SetVerts(vtk_cells)
|
||||
|
||||
|
@ -260,7 +304,7 @@ class VTK:
|
|||
@staticmethod
|
||||
def from_rectilinear_grid(grid: FloatSequence) -> 'VTK':
|
||||
"""
|
||||
Create VTK of type vtk.vtkRectilinearGrid.
|
||||
Create VTK of type vtkRectilinearGrid.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -273,9 +317,9 @@ class VTK:
|
|||
VTK-based geometry without nodal or cell data.
|
||||
|
||||
"""
|
||||
vtk_data = vtk.vtkRectilinearGrid()
|
||||
vtk_data = vtkRectilinearGrid()
|
||||
vtk_data.SetDimensions(*map(len,grid))
|
||||
coord = [np_to_vtk(np.array(grid[i]),deep=True) for i in [0,1,2]]
|
||||
coord = [numpy_to_vtk(np.array(grid[i]),deep=True) for i in [0,1,2]]
|
||||
[coord[i].SetName(n) for i,n in enumerate(['x','y','z'])]
|
||||
vtk_data.SetXCoordinates(coord[0])
|
||||
vtk_data.SetYCoordinates(coord[1])
|
||||
|
@ -293,10 +337,10 @@ class VTK:
|
|||
Parameters
|
||||
----------
|
||||
fname : str or pathlib.Path
|
||||
Filename for reading.
|
||||
Filename to read.
|
||||
Valid extensions are .vti, .vtu, .vtp, .vtr, and .vtk.
|
||||
dataset_type : {'ImageData', 'UnstructuredGrid', 'PolyData', 'RectilinearGrid'}, optional
|
||||
Name of the vtk.vtkDataSet subclass when opening a .vtk file.
|
||||
Name of the vtkDataSet subclass when opening a .vtk file.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -307,7 +351,7 @@ class VTK:
|
|||
if not Path(fname).expanduser().is_file(): # vtk has a strange error handling
|
||||
raise FileNotFoundError(f'file "{fname}" not found')
|
||||
if (ext := Path(fname).suffix) == '.vtk' or dataset_type is not None:
|
||||
reader = vtk.vtkGenericDataObjectReader()
|
||||
reader = vtkGenericDataObjectReader()
|
||||
reader.SetFileName(str(Path(fname).expanduser()))
|
||||
if dataset_type is None:
|
||||
raise TypeError('dataset type for *.vtk file not given')
|
||||
|
@ -327,13 +371,13 @@ class VTK:
|
|||
raise TypeError(f'unknown dataset type "{dataset_type}" for vtk file')
|
||||
else:
|
||||
if ext == '.vti':
|
||||
reader = vtk.vtkXMLImageDataReader()
|
||||
reader = vtkXMLImageDataReader()
|
||||
elif ext == '.vtu':
|
||||
reader = vtk.vtkXMLUnstructuredGridReader()
|
||||
reader = vtkXMLUnstructuredGridReader()
|
||||
elif ext == '.vtp':
|
||||
reader = vtk.vtkXMLPolyDataReader()
|
||||
reader = vtkXMLPolyDataReader()
|
||||
elif ext == '.vtr':
|
||||
reader = vtk.vtkXMLRectilinearGridReader()
|
||||
reader = vtkXMLRectilinearGridReader()
|
||||
else:
|
||||
raise TypeError(f'unknown file extension "{ext}"')
|
||||
|
||||
|
@ -352,7 +396,7 @@ class VTK:
|
|||
|
||||
def as_ASCII(self) -> str:
|
||||
"""ASCII representation of the VTK data."""
|
||||
writer = vtk.vtkDataSetWriter()
|
||||
writer = vtkDataSetWriter()
|
||||
writer.SetHeader(f'# {util.execution_stamp("VTK")}')
|
||||
writer.WriteToOutputStringOn()
|
||||
writer.SetInputData(self.vtk_data)
|
||||
|
@ -370,21 +414,21 @@ class VTK:
|
|||
Parameters
|
||||
----------
|
||||
fname : str or pathlib.Path
|
||||
Filename for writing.
|
||||
Filename to write.
|
||||
parallel : bool, optional
|
||||
Write data in parallel background process. Defaults to True.
|
||||
compress : bool, optional
|
||||
Compress with zlib algorithm. Defaults to True.
|
||||
|
||||
"""
|
||||
if isinstance(self.vtk_data,vtk.vtkImageData):
|
||||
writer = vtk.vtkXMLImageDataWriter()
|
||||
elif isinstance(self.vtk_data,vtk.vtkUnstructuredGrid):
|
||||
writer = vtk.vtkXMLUnstructuredGridWriter()
|
||||
elif isinstance(self.vtk_data,vtk.vtkPolyData):
|
||||
writer = vtk.vtkXMLPolyDataWriter()
|
||||
elif isinstance(self.vtk_data,vtk.vtkRectilinearGrid):
|
||||
writer = vtk.vtkXMLRectilinearGridWriter()
|
||||
if isinstance(self.vtk_data,vtkImageData):
|
||||
writer = vtkXMLImageDataWriter()
|
||||
elif isinstance(self.vtk_data,vtkUnstructuredGrid):
|
||||
writer = vtkXMLUnstructuredGridWriter()
|
||||
elif isinstance(self.vtk_data,vtkPolyData):
|
||||
writer = vtkXMLPolyDataWriter()
|
||||
elif isinstance(self.vtk_data,vtkRectilinearGrid):
|
||||
writer = vtkXMLRectilinearGridWriter()
|
||||
|
||||
default_ext = '.'+writer.GetDefaultFileExtension()
|
||||
ext = Path(fname).suffix
|
||||
|
@ -433,6 +477,11 @@ class VTK:
|
|||
Data to add or replace. Each table label is individually considered.
|
||||
Number of rows needs to match either number of cells or number of points.
|
||||
|
||||
Returns
|
||||
-------
|
||||
updated : damask.VTK
|
||||
Updated VTK-based geometry.
|
||||
|
||||
Notes
|
||||
-----
|
||||
If the number of cells equals the number of points, the data is added to both.
|
||||
|
@ -451,11 +500,11 @@ class VTK:
|
|||
.astype(np.single if data.dtype in [np.double,np.longdouble] else data.dtype)
|
||||
|
||||
if data.dtype.type is np.str_:
|
||||
d = vtk.vtkStringArray()
|
||||
d = vtkStringArray()
|
||||
for s in np.squeeze(data_):
|
||||
d.InsertNextValue(s)
|
||||
else:
|
||||
d = np_to_vtk(data_,deep=True)
|
||||
d = numpy_to_vtk(data_,deep=True)
|
||||
|
||||
d.SetName(label)
|
||||
|
||||
|
@ -513,7 +562,7 @@ class VTK:
|
|||
for a in range(cell_data.GetNumberOfArrays()):
|
||||
if cell_data.GetArrayName(a) == label:
|
||||
try:
|
||||
return vtk_to_np(cell_data.GetArray(a))
|
||||
return vtk_to_numpy(cell_data.GetArray(a))
|
||||
except AttributeError:
|
||||
vtk_array = cell_data.GetAbstractArray(a) # string array
|
||||
|
||||
|
@ -521,7 +570,7 @@ class VTK:
|
|||
for a in range(point_data.GetNumberOfArrays()):
|
||||
if point_data.GetArrayName(a) == label:
|
||||
try:
|
||||
return vtk_to_np(point_data.GetArray(a))
|
||||
return vtk_to_numpy(point_data.GetArray(a))
|
||||
except AttributeError:
|
||||
vtk_array = point_data.GetAbstractArray(a) # string array
|
||||
|
||||
|
@ -548,7 +597,7 @@ class VTK:
|
|||
Notes
|
||||
-----
|
||||
The first component is shown when visualizing vector datasets
|
||||
(this includes tensor datasets because they are flattened).
|
||||
(this includes tensor datasets as they are flattened).
|
||||
|
||||
"""
|
||||
# See http://compilatrix.com/article/vtk-1 for possible improvements.
|
||||
|
@ -567,7 +616,7 @@ class VTK:
|
|||
width = 1024
|
||||
height = 768
|
||||
|
||||
lut = vtk.vtkLookupTable()
|
||||
lut = vtkLookupTable()
|
||||
colormap_ = Colormap.from_predefined(colormap) if isinstance(colormap,str) else \
|
||||
colormap
|
||||
lut.SetNumberOfTableValues(len(colormap_.colors))
|
||||
|
@ -576,33 +625,33 @@ class VTK:
|
|||
lut.Build()
|
||||
|
||||
self.vtk_data.GetCellData().SetActiveScalars(label)
|
||||
mapper = vtk.vtkDataSetMapper()
|
||||
mapper = vtkDataSetMapper()
|
||||
mapper.SetInputData(self.vtk_data)
|
||||
mapper.SetLookupTable(lut)
|
||||
mapper.SetScalarRange(self.vtk_data.GetScalarRange())
|
||||
|
||||
actor = vtk.vtkActor()
|
||||
actor = vtkActor()
|
||||
actor.SetMapper(mapper)
|
||||
actor.GetProperty().SetColor(230/255,150/255,68/255)
|
||||
|
||||
ren = vtk.vtkRenderer()
|
||||
ren = vtkRenderer()
|
||||
ren.AddActor(actor)
|
||||
if label is None:
|
||||
ren.SetBackground(67/255,128/255,208/255)
|
||||
else:
|
||||
colormap_vtk = vtk.vtkScalarBarActor()
|
||||
colormap_vtk = vtkScalarBarActor()
|
||||
colormap_vtk.SetLookupTable(lut)
|
||||
colormap_vtk.SetTitle(label)
|
||||
colormap_vtk.SetMaximumWidthInPixels(width//100)
|
||||
ren.AddActor2D(colormap_vtk)
|
||||
ren.SetBackground(0.3,0.3,0.3)
|
||||
|
||||
window = vtk.vtkRenderWindow()
|
||||
window = vtkRenderWindow()
|
||||
window.AddRenderer(ren)
|
||||
window.SetSize(width,height)
|
||||
window.SetWindowName(util.execution_stamp('VTK','show'))
|
||||
|
||||
iren = vtk.vtkRenderWindowInteractor()
|
||||
iren = vtkRenderWindowInteractor()
|
||||
iren.SetRenderWindow(window)
|
||||
if os.name == 'posix' and 'DISPLAY' not in os.environ:
|
||||
print('Found no rendering device')
|
||||
|
|
|
@ -402,7 +402,7 @@ def displacement_node(size: _FloatSequence,
|
|||
|
||||
Returns
|
||||
-------
|
||||
u_p : numpy.ndarray, shape (:,:,:,3)
|
||||
u_n : numpy.ndarray, shape (:,:,:,3)
|
||||
Nodal displacements.
|
||||
|
||||
"""
|
||||
|
@ -564,17 +564,10 @@ def unravel_index(idx: _np.ndarray) -> _np.ndarray:
|
|||
>>> seq = np.arange(6).reshape((3,2,1),order='F')
|
||||
>>> (coord_idx := damask.grid_filters.unravel_index(seq))
|
||||
array([[[[0, 0, 0]],
|
||||
|
||||
[[0, 1, 0]]],
|
||||
|
||||
|
||||
[[[1, 0, 0]],
|
||||
|
||||
[[1, 1, 0]]],
|
||||
|
||||
|
||||
[[[2, 0, 0]],
|
||||
|
||||
[[2, 1, 0]]]])
|
||||
>>> coord_idx[1,1,0]
|
||||
array([1, 1, 0])
|
||||
|
@ -608,17 +601,12 @@ def ravel_index(idx: _np.ndarray) -> _np.ndarray:
|
|||
>>> import damask
|
||||
>>> (rev := np.array([[1,1,0],[0,1,0],[1,0,0],[0,0,0]]).reshape((2,2,1,3)))
|
||||
array([[[[1, 1, 0]],
|
||||
|
||||
[[0, 1, 0]]],
|
||||
|
||||
|
||||
[[[1, 0, 0]],
|
||||
|
||||
[[0, 0, 0]]]])
|
||||
>>> (flat_idx := damask.grid_filters.ravel_index(rev))
|
||||
array([[[3],
|
||||
[2]],
|
||||
|
||||
[[1],
|
||||
[0]]])
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ All routines operate on numpy.ndarrays of shape (...,3,3).
|
|||
|
||||
"""
|
||||
|
||||
from typing import Sequence as _Sequence
|
||||
from typing import Sequence as _Sequence#, Literal as _Literal
|
||||
|
||||
import numpy as _np
|
||||
|
||||
|
@ -14,7 +14,7 @@ from . import _rotation
|
|||
|
||||
|
||||
def deformation_Cauchy_Green_left(F: _np.ndarray) -> _np.ndarray:
|
||||
"""
|
||||
r"""
|
||||
Calculate left Cauchy-Green deformation tensor (Finger deformation tensor).
|
||||
|
||||
Parameters
|
||||
|
@ -27,12 +27,18 @@ def deformation_Cauchy_Green_left(F: _np.ndarray) -> _np.ndarray:
|
|||
B : numpy.ndarray, shape (...,3,3)
|
||||
Left Cauchy-Green deformation tensor.
|
||||
|
||||
Notes
|
||||
-----
|
||||
.. math::
|
||||
|
||||
\vb{B} = \vb{F} \vb{F}^\text{T}
|
||||
|
||||
"""
|
||||
return _np.matmul(F,_tensor.transpose(F))
|
||||
|
||||
|
||||
def deformation_Cauchy_Green_right(F: _np.ndarray) -> _np.ndarray:
|
||||
"""
|
||||
r"""
|
||||
Calculate right Cauchy-Green deformation tensor.
|
||||
|
||||
Parameters
|
||||
|
@ -45,12 +51,18 @@ def deformation_Cauchy_Green_right(F: _np.ndarray) -> _np.ndarray:
|
|||
C : numpy.ndarray, shape (...,3,3)
|
||||
Right Cauchy-Green deformation tensor.
|
||||
|
||||
Notes
|
||||
-----
|
||||
.. math::
|
||||
|
||||
\vb{C} = \vb{F}^\text{T} \vb{F}
|
||||
|
||||
"""
|
||||
return _np.matmul(_tensor.transpose(F),F)
|
||||
|
||||
|
||||
def equivalent_strain_Mises(epsilon: _np.ndarray) -> _np.ndarray:
|
||||
"""
|
||||
r"""
|
||||
Calculate the Mises equivalent of a strain tensor.
|
||||
|
||||
Parameters
|
||||
|
@ -63,12 +75,23 @@ def equivalent_strain_Mises(epsilon: _np.ndarray) -> _np.ndarray:
|
|||
epsilon_vM : numpy.ndarray, shape (...)
|
||||
Von Mises equivalent strain of epsilon.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The von Mises equivalent of a strain tensor is defined as:
|
||||
|
||||
.. math::
|
||||
|
||||
\epsilon_\text{vM} = \sqrt{\frac{2}{3}\,\epsilon^\prime_{ij} \epsilon^\prime_{ij}}
|
||||
|
||||
where :math:`\vb*{\epsilon}^\prime` is the deviatoric part
|
||||
of the strain tensor.
|
||||
|
||||
"""
|
||||
return _equivalent_Mises(epsilon,2.0/3.0)
|
||||
|
||||
|
||||
def equivalent_stress_Mises(sigma: _np.ndarray) -> _np.ndarray:
|
||||
"""
|
||||
r"""
|
||||
Calculate the Mises equivalent of a stress tensor.
|
||||
|
||||
Parameters
|
||||
|
@ -81,6 +104,17 @@ def equivalent_stress_Mises(sigma: _np.ndarray) -> _np.ndarray:
|
|||
sigma_vM : numpy.ndarray, shape (...)
|
||||
Von Mises equivalent stress of sigma.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The von Mises equivalent of a stress tensor is defined as:
|
||||
|
||||
.. math::
|
||||
|
||||
\sigma_\text{vM} = \sqrt{\frac{3}{2}\,\sigma^\prime_{ij} \sigma^\prime_{ij}}
|
||||
|
||||
where :math:`\vb*{\sigma}^\prime` is the deviatoric part
|
||||
of the stress tensor.
|
||||
|
||||
"""
|
||||
return _equivalent_Mises(sigma,3.0/2.0)
|
||||
|
||||
|
@ -105,7 +139,7 @@ def maximum_shear(T_sym: _np.ndarray) -> _np.ndarray:
|
|||
|
||||
|
||||
def rotation(T: _np.ndarray) -> _rotation.Rotation:
|
||||
"""
|
||||
r"""
|
||||
Calculate the rotational part of a tensor.
|
||||
|
||||
Parameters
|
||||
|
@ -118,23 +152,35 @@ def rotation(T: _np.ndarray) -> _rotation.Rotation:
|
|||
R : damask.Rotation, shape (...)
|
||||
Rotational part of the vector.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The rotational part is calculated from the polar decomposition:
|
||||
|
||||
.. math::
|
||||
|
||||
\vb{R} = \vb{T} \vb{U}^{-1} = \vb{V}^{-1} \vb{T}
|
||||
|
||||
where :math:`\vb{V}` and :math:`\vb{U}` are the left
|
||||
and right stretch tensor, respectively.
|
||||
|
||||
"""
|
||||
return _rotation.Rotation.from_matrix(_polar_decomposition(T,'R')[0])
|
||||
|
||||
|
||||
def strain(F: _np.ndarray,
|
||||
#t: _Literal['V', 'U'], should work, but rejected by SC
|
||||
t: str,
|
||||
m: float) -> _np.ndarray:
|
||||
"""
|
||||
r"""
|
||||
Calculate strain tensor (Seth–Hill family).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
F : numpy.ndarray, shape (...,3,3)
|
||||
Deformation gradient.
|
||||
t : {‘V’, ‘U’}
|
||||
Type of the polar decomposition, ‘V’ for left stretch tensor
|
||||
and ‘U’ for right stretch tensor.
|
||||
t : {'V', 'U'}
|
||||
Type of the polar decomposition, 'V' for left stretch tensor
|
||||
or 'U' for right stretch tensor.
|
||||
m : float
|
||||
Order of the strain.
|
||||
|
||||
|
@ -143,25 +189,25 @@ def strain(F: _np.ndarray,
|
|||
epsilon : numpy.ndarray, shape (...,3,3)
|
||||
Strain of F.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The strain is defined as:
|
||||
|
||||
.. math::
|
||||
|
||||
\vb*{\epsilon}_V^{(m)} = \frac{1}{2m} (\vb{V}^{2m} - \vb{I}) \\\\
|
||||
\vb*{\epsilon}_U^{(m)} = \frac{1}{2m} (\vb{U}^{2m} - \vb{I})
|
||||
|
||||
References
|
||||
----------
|
||||
https://en.wikipedia.org/wiki/Finite_strain_theory
|
||||
https://de.wikipedia.org/wiki/Verzerrungstensor
|
||||
| https://en.wikipedia.org/wiki/Finite_strain_theory
|
||||
| https://de.wikipedia.org/wiki/Verzerrungstensor
|
||||
|
||||
"""
|
||||
if t == 'V':
|
||||
w,n = _np.linalg.eigh(deformation_Cauchy_Green_left(F))
|
||||
elif t == 'U':
|
||||
w,n = _np.linalg.eigh(deformation_Cauchy_Green_right(F))
|
||||
|
||||
if m > 0.0:
|
||||
eps = 1.0/(2.0*abs(m)) * (+ _np.einsum('...j,...kj,...lj',w**m,n,n) - _np.eye(3))
|
||||
elif m < 0.0:
|
||||
eps = 1.0/(2.0*abs(m)) * (- _np.einsum('...j,...kj,...lj',w**m,n,n) + _np.eye(3))
|
||||
else:
|
||||
eps = _np.einsum('...j,...kj,...lj',0.5*_np.log(w),n,n)
|
||||
|
||||
return eps
|
||||
if t not in ['V', 'U']: raise ValueError('polar decomposition type not in {V, U}')
|
||||
w,n = _np.linalg.eigh(deformation_Cauchy_Green_left(F) if t=='V' else deformation_Cauchy_Green_right(F))
|
||||
return 0.5 * _np.einsum('...j,...kj,...lj',_np.log(w),n,n) if m == 0.0 \
|
||||
else 0.5/m * (_np.einsum('...j,...kj,...lj', w**m,n,n) - _np.eye(3))
|
||||
|
||||
|
||||
def stress_Cauchy(P: _np.ndarray,
|
||||
|
@ -212,7 +258,7 @@ def stress_second_Piola_Kirchhoff(P: _np.ndarray,
|
|||
|
||||
|
||||
def stretch_left(T: _np.ndarray) -> _np.ndarray:
|
||||
"""
|
||||
r"""
|
||||
Calculate left stretch of a tensor.
|
||||
|
||||
Parameters
|
||||
|
@ -225,12 +271,23 @@ def stretch_left(T: _np.ndarray) -> _np.ndarray:
|
|||
V : numpy.ndarray, shape (...,3,3)
|
||||
Left stretch tensor from Polar decomposition of T.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The left stretch tensor is calculated from the
|
||||
polar decomposition:
|
||||
|
||||
.. math::
|
||||
|
||||
\vb{V} = \vb{T} \vb{R}^\text{T}
|
||||
|
||||
where :math:`\vb{R}` is a rotation.
|
||||
|
||||
"""
|
||||
return _polar_decomposition(T,'V')[0]
|
||||
|
||||
|
||||
def stretch_right(T: _np.ndarray) -> _np.ndarray:
|
||||
"""
|
||||
r"""
|
||||
Calculate right stretch of a tensor.
|
||||
|
||||
Parameters
|
||||
|
@ -243,6 +300,17 @@ def stretch_right(T: _np.ndarray) -> _np.ndarray:
|
|||
U : numpy.ndarray, shape (...,3,3)
|
||||
Left stretch tensor from Polar decomposition of T.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The right stretch tensor is calculated from the
|
||||
polar decomposition:
|
||||
|
||||
.. math::
|
||||
|
||||
\vb{U} = \vb{R}^\text{T} \vb{T}
|
||||
|
||||
where :math:`\vb{R}` is a rotation.
|
||||
|
||||
"""
|
||||
return _polar_decomposition(T,'U')[0]
|
||||
|
||||
|
@ -257,8 +325,13 @@ def _polar_decomposition(T: _np.ndarray,
|
|||
T : numpy.ndarray, shape (...,3,3)
|
||||
Tensor of which the singular values are computed.
|
||||
requested : sequence of {'R', 'U', 'V'}
|
||||
Requested outputs: ‘R’ for the rotation tensor,
|
||||
‘V’ for left stretch tensor, and ‘U’ for right stretch tensor.
|
||||
Requested outputs: 'R' for the rotation tensor,
|
||||
'V' for left stretch tensor, and 'U' for right stretch tensor.
|
||||
|
||||
Returns
|
||||
-------
|
||||
VRU : tuple of numpy.ndarray, shape (...,3,3)
|
||||
Requested components of the singular value decomposition.
|
||||
|
||||
"""
|
||||
u, _, vh = _np.linalg.svd(T)
|
||||
|
@ -290,6 +363,11 @@ def _equivalent_Mises(T_sym: _np.ndarray,
|
|||
s : float
|
||||
Scaling factor (2/3 for strain, 3/2 for stress).
|
||||
|
||||
Returns
|
||||
-------
|
||||
eq : numpy.ndarray, shape (...)
|
||||
Scaled second invariant of the deviatoric part of T_sym.
|
||||
|
||||
"""
|
||||
d = _tensor.deviatoric(T_sym)
|
||||
return _np.sqrt(s*_np.sum(d**2.0,axis=(-1,-2)))
|
||||
|
|
|
@ -21,7 +21,7 @@ def from_random(size: _FloatSequence,
|
|||
Parameters
|
||||
----------
|
||||
size : sequence of float, len (3)
|
||||
Physical size of the seeding domain.
|
||||
Edge lengths of the seeding domain.
|
||||
N_seeds : int
|
||||
Number of seeds.
|
||||
cells : sequence of int, len (3), optional.
|
||||
|
@ -56,12 +56,12 @@ def from_Poisson_disc(size: _FloatSequence,
|
|||
periodic: bool = True,
|
||||
rng_seed: _Optional[_NumpyRngSeed] = None) -> _np.ndarray:
|
||||
"""
|
||||
Place seeds according to a Poisson disc distribution.
|
||||
Place seeds following a Poisson disc distribution.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
size : sequence of float, len (3)
|
||||
Physical size of the seeding domain.
|
||||
Edge lengths of the seeding domain.
|
||||
N_seeds : int
|
||||
Number of seeds.
|
||||
N_candidates : int
|
||||
|
@ -70,6 +70,7 @@ def from_Poisson_disc(size: _FloatSequence,
|
|||
Minimum acceptable distance to other seeds.
|
||||
periodic : bool, optional
|
||||
Calculate minimum distance for periodically repeated grid.
|
||||
Defaults to True.
|
||||
rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional
|
||||
A seed to initialize the BitGenerator. Defaults to None.
|
||||
If None, then fresh, unpredictable entropy will be pulled from the OS.
|
||||
|
@ -123,14 +124,36 @@ def from_grid(grid,
|
|||
Consider all material IDs except those in selection. Defaults to False.
|
||||
average : bool, optional
|
||||
Seed corresponds to center of gravity of material ID cloud.
|
||||
Defaults to False.
|
||||
periodic : bool, optional
|
||||
Center of gravity accounts for periodic boundaries.
|
||||
Defaults to True.
|
||||
|
||||
Returns
|
||||
-------
|
||||
coords, materials : numpy.ndarray, shape (:,3); numpy.ndarray, shape (:)
|
||||
Seed coordinates in 3D space, material IDs.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Recreate seeds from Voronoi tessellation.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import scipy.spatial
|
||||
>>> import damask
|
||||
>>> seeds = damask.seeds.from_random(np.ones(3),29,[128]*3)
|
||||
>>> (g := damask.Grid.from_Voronoi_tessellation([128]*3,np.ones(3),seeds))
|
||||
cells: 128 × 128 × 128
|
||||
size: 1.0 × 1.0 × 1.0 m³
|
||||
origin: 0.0 0.0 0.0 m
|
||||
# materials: 29
|
||||
>>> COG,matID = damask.seeds.from_grid(g,average=True)
|
||||
>>> distance,ID = scipy.spatial.KDTree(COG,boxsize=g.size).query(seeds)
|
||||
>>> np.max(distance) / np.linalg.norm(g.size/g.cells)
|
||||
7.8057356746350415
|
||||
>>> (ID == matID).all()
|
||||
True
|
||||
|
||||
"""
|
||||
material = grid.material.reshape((-1,1),order='F')
|
||||
mask = _np.full(grid.cells.prod(),True,dtype=bool) if selection is None else \
|
||||
|
|
|
@ -2,6 +2,7 @@ import subprocess
|
|||
import shlex
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
_marc_version = '2022.4'
|
||||
_marc_root = '/opt/msc'
|
||||
|
@ -54,7 +55,7 @@ class Marc:
|
|||
|
||||
def submit_job(self, model: str, job: str,
|
||||
compile: bool = False,
|
||||
optimization: str = '',
|
||||
optimization: Literal['', 'l', 'h'] = '',
|
||||
env = None):
|
||||
"""
|
||||
Assemble command line arguments and call Marc executable.
|
||||
|
@ -68,8 +69,8 @@ class Marc:
|
|||
compile : bool, optional
|
||||
Compile DAMASK_Marc user subroutine (and save for future use).
|
||||
Defaults to False.
|
||||
optimization : str, optional
|
||||
Optimization level '' (-O0), 'l' (-O1), or 'h' (-O3).
|
||||
optimization : {'', 'l', 'h'}, optional
|
||||
Optimization level '': -O0, 'l': -O1, or 'h': -O3.
|
||||
Defaults to ''.
|
||||
env : dict, optional
|
||||
Environment for execution.
|
||||
|
|
|
@ -8,8 +8,9 @@ import shlex as _shlex
|
|||
import re as _re
|
||||
import signal as _signal
|
||||
import fractions as _fractions
|
||||
from collections import abc as _abc
|
||||
from functools import reduce as _reduce, partial as _partial
|
||||
from collections import abc as _abc, OrderedDict as _OrderedDict
|
||||
from functools import reduce as _reduce, partial as _partial, wraps as _wraps
|
||||
import inspect
|
||||
from typing import Optional as _Optional, Callable as _Callable, Union as _Union, Iterable as _Iterable, \
|
||||
Dict as _Dict, List as _List, Tuple as _Tuple, Literal as _Literal, \
|
||||
Any as _Any, TextIO as _TextIO
|
||||
|
@ -540,10 +541,11 @@ def shapeblender(a: _Tuple[int, ...],
|
|||
|
||||
|
||||
def _docstringer(docstring: _Union[str, _Callable],
|
||||
extra_parameters: _Optional[str] = None,
|
||||
# extra_examples: _Optional[str] = None,
|
||||
# extra_notes: _Optional[str] = None,
|
||||
return_type: _Union[None, str, _Callable] = None) -> str:
|
||||
adopted_parameters: _Union[None, str, _Callable] = None,
|
||||
adopted_return: _Union[None, str, _Callable] = None,
|
||||
adopted_notes: _Union[None, str, _Callable] = None,
|
||||
adopted_examples: _Union[None, str, _Callable] = None,
|
||||
adopted_references: _Union[None, str, _Callable] = None) -> str:
|
||||
"""
|
||||
Extend a docstring.
|
||||
|
||||
|
@ -551,50 +553,85 @@ def _docstringer(docstring: _Union[str, _Callable],
|
|||
----------
|
||||
docstring : str or callable, optional
|
||||
Docstring (of callable) to extend.
|
||||
extra_parameters : str, optional
|
||||
Additional information to append to Parameters section.
|
||||
return_type : str or callable, optional
|
||||
Type of return variable.
|
||||
adopted_* : str or callable, optional
|
||||
Additional information to insert into/append to respective section.
|
||||
|
||||
Notes
|
||||
-----
|
||||
adopted_return fetches the typehint of a passed function instead of the docstring
|
||||
|
||||
"""
|
||||
docstring_ = str( docstring if isinstance(docstring,str)
|
||||
else docstring.__doc__ if hasattr(docstring,'__doc__')
|
||||
else '')
|
||||
d = dict(Parameters=extra_parameters,
|
||||
# Examples=extra_examples,
|
||||
# Notes=extra_notes,
|
||||
)
|
||||
for key,extra in [(k,v) for (k,v) in d.items() if v is not None]:
|
||||
if not (heading := _re.search(fr'^([ ]*){key}\s*\n\1{"-"*len(key)}',
|
||||
docstring_,flags=_re.MULTILINE)):
|
||||
raise RuntimeError(f"Docstring {docstring_} lacks a correctly formatted {key} section to insert values into")
|
||||
content = [line for line in extra.split('\n') if line.strip()]
|
||||
indent = len(heading.group(1))
|
||||
shift = min([len(line)-len(line.lstrip(' '))-indent for line in content])
|
||||
extra = '\n'.join([(line[shift:] if shift > 0 else
|
||||
f'{" "*-shift}{line}') for line in content])
|
||||
docstring_ = _re.sub(fr'(^([ ]*){key}\s*\n\2{"-"*len(key)}[\n ]*[A-Za-z0-9_ ]*: ([^\n]+\n)*)',
|
||||
fr'\1{extra}\n',
|
||||
docstring_,flags=_re.MULTILINE)
|
||||
docstring_: str = str( docstring if isinstance(docstring,str)
|
||||
else docstring.__doc__ if callable(docstring) and docstring.__doc__
|
||||
else '').rstrip()+'\n'
|
||||
sections = _OrderedDict(
|
||||
Parameters=adopted_parameters,
|
||||
Returns=adopted_return,
|
||||
Examples=adopted_examples,
|
||||
Notes=adopted_notes,
|
||||
References=adopted_references)
|
||||
|
||||
if return_type is None:
|
||||
return docstring_
|
||||
else:
|
||||
if isinstance(return_type,str):
|
||||
return_type_ = return_type
|
||||
for i, (key, adopted) in [(i,(k,v)) for (i,(k,v)) in enumerate(sections.items()) if v is not None]:
|
||||
section_regex = fr'^([ ]*){key}\s*\n\1*{"-"*len(key)}\s*\n'
|
||||
if key=='Returns':
|
||||
if callable(adopted):
|
||||
return_class = adopted.__annotations__.get('return','')
|
||||
return_type_ = (_sys.modules[adopted.__module__].__name__.split('.')[0]
|
||||
+'.'
|
||||
+(return_class.__name__ if not isinstance(return_class,str) else return_class))
|
||||
else:
|
||||
return_type_ = adopted
|
||||
docstring_ = _re.sub(fr'(^[ ]*{key}\s*\n\s*{"-"*len(key)}\s*\n[ ]*[A-Za-z0-9_ ]*: )(.*)\n',
|
||||
fr'\1{return_type_}\n',
|
||||
docstring_,flags=_re.MULTILINE)
|
||||
else:
|
||||
return_class = return_type.__annotations__.get('return','')
|
||||
return_type_ = (_sys.modules[return_type.__module__].__name__.split('.')[0]
|
||||
+'.'
|
||||
+(return_class.__name__ if not isinstance(return_class,str) else return_class)
|
||||
)
|
||||
section_content_regex = fr'{section_regex}(?P<content>.*?)\n *(\n|\Z)'
|
||||
adopted_: str = adopted.__doc__ if callable(adopted) else adopted #type: ignore
|
||||
try:
|
||||
if _re.search(fr'{section_regex}', adopted_, flags=_re.MULTILINE):
|
||||
adopted_ = _re.search(section_content_regex, #type: ignore
|
||||
adopted_,
|
||||
flags=_re.MULTILINE|_re.DOTALL).group('content')
|
||||
except AttributeError:
|
||||
raise RuntimeError(f"Function docstring passed for docstring section '{key}' is invalid:\n{docstring}")
|
||||
|
||||
docstring_indent, adopted_indent = (min([len(line)-len(line.lstrip()) for line in section.split('\n') if line.strip()])
|
||||
for section in [docstring_, adopted_])
|
||||
shift = adopted_indent - docstring_indent
|
||||
adopted_content = '\n'.join([(line[shift:] if shift > 0 else
|
||||
f'{" "*-shift}{line}') for line in adopted_.split('\n') if line.strip()])
|
||||
|
||||
if _re.search(section_regex, docstring_, flags=_re.MULTILINE):
|
||||
docstring_section_content = _re.search(section_content_regex, # type: ignore
|
||||
docstring_,
|
||||
flags=_re.MULTILINE|_re.DOTALL).group('content')
|
||||
a_items, d_items = (_re.findall('^[ ]*([A-Za-z0-9_ ]*?)[ ]*:',content,flags=_re.MULTILINE)
|
||||
for content in [adopted_content,docstring_section_content])
|
||||
for item in a_items:
|
||||
if item in d_items:
|
||||
adopted_content = _re.sub(fr'^([ ]*){item}.*?(?:(\n)\1([A-Za-z0-9_])|([ ]*\Z))',
|
||||
r'\1\3',
|
||||
adopted_content,
|
||||
flags=_re.MULTILINE|_re.DOTALL).rstrip(' \n')
|
||||
docstring_ = _re.sub(fr'(^[ ]*{key}\s*\n\s*{"-"*len(key)}\s*\n.*?)\n *(\Z|\n)',
|
||||
fr'\1\n{adopted_content}\n\2',
|
||||
docstring_,
|
||||
flags=_re.MULTILINE|_re.DOTALL)
|
||||
else:
|
||||
section_title = f'{" "*(shift+docstring_indent)}{key}\n{" "*(shift+docstring_indent)}{"-"*len(key)}\n'
|
||||
section_matches = [_re.search(
|
||||
fr'[ ]*{list(sections.keys())[index]}\s*\n\s*{"-"*len(list(sections.keys())[index])}\s*', docstring_)
|
||||
for index in range(i,len(sections))]
|
||||
subsequent_section = '\\Z' if not any(section_matches) else \
|
||||
'\n'+next(item for item in section_matches if item is not None).group(0)
|
||||
docstring_ = _re.sub(fr'({subsequent_section})',
|
||||
fr'\n{section_title}{adopted_content}\n\1',
|
||||
docstring_)
|
||||
return docstring_
|
||||
|
||||
return _re.sub(r'(^([ ]*)Returns\s*\n\2-------\s*\n[ ]*[A-Za-z0-9_ ]*: )(.*)\n',
|
||||
fr'\1{return_type_}\n',
|
||||
docstring_,flags=_re.MULTILINE)
|
||||
|
||||
def extend_docstring(docstring: _Union[None, str, _Callable] = None,
|
||||
extra_parameters: _Optional[str] = None) -> _Callable:
|
||||
**kwargs) -> _Callable:
|
||||
"""
|
||||
Decorator: Extend the function's docstring.
|
||||
|
||||
|
@ -602,8 +639,8 @@ def extend_docstring(docstring: _Union[None, str, _Callable] = None,
|
|||
----------
|
||||
docstring : str or callable, optional
|
||||
Docstring to extend. Defaults to that of decorated function.
|
||||
extra_parameters : str, optional
|
||||
Additional information to append to Parameters section.
|
||||
adopted_* : str or callable, optional
|
||||
Additional information to insert into/append to respective section.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
@ -611,13 +648,55 @@ def extend_docstring(docstring: _Union[None, str, _Callable] = None,
|
|||
|
||||
"""
|
||||
def _decorator(func):
|
||||
if 'adopted_return' not in kwargs: kwargs['adopted_return'] = func
|
||||
func.__doc__ = _docstringer(func.__doc__ if docstring is None else docstring,
|
||||
extra_parameters,
|
||||
func if isinstance(docstring,_Callable) else None,
|
||||
)
|
||||
**kwargs)
|
||||
return func
|
||||
return _decorator
|
||||
|
||||
def pass_on(keyword: str,
|
||||
target: _Callable,
|
||||
wrapped: _Callable = None) -> _Callable: # type: ignore
|
||||
"""
|
||||
Decorator: Combine signatures of 'wrapped' and 'target' functions and pass on output of 'target' as 'keyword' argument.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
keyword : str
|
||||
Keyword added to **kwargs of the decorated function
|
||||
passing on the result of 'target'.
|
||||
target : callable
|
||||
The output of this function is passed to the
|
||||
decorated function as 'keyword' argument.
|
||||
wrapped: callable, optional
|
||||
Signature of 'wrapped' function combined with
|
||||
that of 'target' yields the overall signature of decorated function.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The keywords used by 'target' will be prioritized
|
||||
if they overlap with those of the decorated function.
|
||||
Functions 'target' and 'wrapped' are assumed to only have keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
@_wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
kw_wrapped = set(kwargs.keys()) - set(inspect.getfullargspec(target).args)
|
||||
kwargs_wrapped = {kw: kwargs.pop(kw) for kw in kw_wrapped}
|
||||
kwargs_wrapped[keyword] = target(**kwargs)
|
||||
return func(*args, **kwargs_wrapped)
|
||||
args_ = [] if wrapped is None or 'self' not in inspect.signature(wrapped).parameters \
|
||||
else [inspect.signature(wrapped).parameters['self']]
|
||||
for f in [target] if wrapped is None else [target,wrapped]:
|
||||
for param in inspect.signature(f).parameters.values():
|
||||
if param.name != keyword \
|
||||
and param.name not in [p.name for p in args_]+['self','cls', 'args', 'kwargs']:
|
||||
args_.append(param.replace(kind=inspect._ParameterKind.KEYWORD_ONLY))
|
||||
wrapper.__signature__ = inspect.Signature(parameters=args_,return_annotation=inspect.signature(func).return_annotation)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
def DREAM3D_base_group(fname: _Union[str, _Path]) -> str:
|
||||
"""
|
||||
|
@ -800,7 +879,7 @@ class ProgressBar:
|
|||
prefix: str,
|
||||
bar_length: int):
|
||||
"""
|
||||
Set current time as basis for ETA estimation.
|
||||
New progress bar.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
|
|
@ -4,7 +4,7 @@ warn_redundant_casts = True
|
|||
ignore_missing_imports = True
|
||||
[mypy-h5py.*]
|
||||
ignore_missing_imports = True
|
||||
[mypy-vtk.*]
|
||||
[mypy-vtkmodules.*]
|
||||
ignore_missing_imports = True
|
||||
[mypy-PIL.*]
|
||||
ignore_missing_imports = True
|
||||
|
|
|
@ -26,4 +26,3 @@ install_requires =
|
|||
vtk>=8.1
|
||||
matplotlib>=3.0 # requires numpy, pillow
|
||||
pyyaml>=3.12
|
||||
setup_requires = setuptools
|
||||
|
|
|
@ -60,9 +60,9 @@ def update(request):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def ref_path_base():
|
||||
"""Directory containing reference results."""
|
||||
return Path(__file__).parent/'reference'
|
||||
def res_path_base():
|
||||
"""Directory containing testing resources."""
|
||||
return Path(__file__).parent/'resources'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
27972d6a0955e4e6e27a6ac5762abda8
|
|
@ -1 +0,0 @@
|
|||
dd71d25ccb52c3fdfd2ab727fc852a98
|
Before Width: | Height: | Size: 147 B After Width: | Height: | Size: 147 B |
Before Width: | Height: | Size: 138 B After Width: | Height: | Size: 138 B |
|
@ -0,0 +1 @@
|
|||
n10-id1_scaled.vtk binary
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue