diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 17728632d..b7f7f8b0b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,14 +2,10 @@ stages: - prepareAll - python - - preprocessing - - postprocessing - - compilePETSc - - prepareGrid + - deprecated + - compile - grid - - compileMarc - marc - - example - performance - createPackage - createDocumentation @@ -20,12 +16,12 @@ stages: ################################################################################################### before_script: - - if [ $(awk "/$CI_PIPELINE_ID/{print NR}" $TESTROOT/GitLabCI.queue)x == 'x' ]; - then echo $CI_PIPELINE_ID >> $TESTROOT/GitLabCI.queue; + - if [ $(awk "/$CI_PIPELINE_ID/{print NR}" $LOCAL_HOME/GitLabCI.queue)x == 'x' ]; + then echo $CI_PIPELINE_ID >> $LOCAL_HOME/GitLabCI.queue; fi - - while [ $(awk "/$CI_PIPELINE_ID/{print NR}" $TESTROOT/GitLabCI.queue) != 1 ]; + - while [ $(awk "/$CI_PIPELINE_ID/{print NR}" $LOCAL_HOME/GitLabCI.queue) != 1 ]; do sleep 5m; - echo -e "Currently queued pipelines:\n$(cat $TESTROOT/GitLabCI.queue)\n"; + echo -e "Currently queued pipelines:\n$(cat $LOCAL_HOME/GitLabCI.queue)\n"; done - source $DAMASKROOT/env/DAMASK.sh - cd $DAMASKROOT/PRIVATE/testing @@ -45,61 +41,55 @@ variables: # =============================================================================================== # Shortcut names # =============================================================================================== - DAMASKROOT: "$TESTROOT/GitLabCI_Pipeline_$CI_PIPELINE_ID/DAMASK" + DAMASKROOT: "$LOCAL_HOME/GitLabCI_Pipeline_$CI_PIPELINE_ID/DAMASK" + TESTROOT: "$LOCAL_HOME/GitLabCI_Pipeline_$CI_PIPELINE_ID/tests" # =============================================================================================== # Names of module files to load # =============================================================================================== # ++++++++++++ Compiler +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - IntelCompiler17_8: "Compiler/Intel/17.8 Libraries/IMKL/2017" - IntelCompiler18_4: "Compiler/Intel/18.4 Libraries/IMKL/2018" - GNUCompiler8_2: "Compiler/GNU/8.2" + IntelCompiler19_1: "Compiler/Intel/19.1.2 Libraries/IMKL/2020" + GNUCompiler10: "Compiler/GNU/10" # ------------ Defaults ---------------------------------------------- - IntelCompiler: "$IntelCompiler18_4" - GNUCompiler: "$GNUCompiler8_2" + IntelCompiler: "$IntelCompiler19_1" + GNUCompiler: "$GNUCompiler10" # ++++++++++++ MPI ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - IMPI2018Intel18_4: "MPI/Intel/18.4/IntelMPI/2018" - MPICH3_3GNU8_2: "MPI/GNU/8.2/MPICH/3.3" + IMPI2020Intel19_1: "MPI/Intel/19.1.2/IntelMPI/2019" + OMPI4_0GNU10: "MPI/GNU/10/OpenMPI/4.0.5" # ------------ Defaults ---------------------------------------------- - MPICH_Intel: "$IMPI2018Intel18_4" - MPICH_GNU: "$MPICH3_3GNU8_2" + MPI_Intel: "$IMPI2020Intel19_1" + MPI_GNU: "$OMPI4_0GNU10" # ++++++++++++ PETSc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - PETSc3_10_3IMPI2018Intel18_4: "Libraries/PETSc/3.10.3/Intel-18.4-IntelMPI-2018" - PETSc3_10_3MPICH3_3GNU8_2: "Libraries/PETSc/3.10.3/GNU-8.2-MPICH-3.3" + PETSc3_14_0IMPI2020Intel19_1: "Libraries/PETSc/3.14.0/Intel-19.1.2-IntelMPI-2019" + PETSc3_14_0OMPI4_0GNU10: "Libraries/PETSc/3.14.0/GNU-10-OpenMPI-4.0.5" # ------------ Defaults ---------------------------------------------- - PETSc_MPICH_Intel: "$PETSc3_10_3IMPI2018Intel18_4" - PETSc_MPICH_GNU: "$PETSc3_10_3MPICH3_3GNU8_2" + PETSc_Intel: "$PETSc3_14_0IMPI2020Intel19_1" + PETSc_GNU: "$PETSc3_14_0OMPI4_0GNU10" # ++++++++++++ commercial FEM ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - MSC2019_1: "FEM/MSC/2019.1" + MSC2020: "FEM/MSC/2020" # ------------ Defaults ---------------------------------------------- - MSC: "$MSC2019_1" - IntelMarc: "$IntelCompiler17_8" - HDF5Marc: "HDF5/1.10.5/Intel-17.8" - # ++++++++++++ Documentation ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - Doxygen1_8_17: "Documentation/Doxygen/1.8.17" - # ------------ Defaults ---------------------------------------------- - Doxygen: "$Doxygen1_8_17" - + MSC: "$MSC2020" + IntelMarc: "$IntelCompiler19_1" + HDF5Marc: "HDF5/1.12.0/Intel-19.1.2" ################################################################################################### checkout: stage: prepareAll before_script: - - echo $CI_PIPELINE_ID >> $TESTROOT/GitLabCI.queue - - while [ $(awk "/$CI_PIPELINE_ID/{print NR}" $TESTROOT/GitLabCI.queue) != 1 ]; + - echo $CI_PIPELINE_ID >> $LOCAL_HOME/GitLabCI.queue + - while [ $(awk "/$CI_PIPELINE_ID/{print NR}" $LOCAL_HOME/GitLabCI.queue) != 1 ]; do sleep 5m; - echo -e "Currently queued pipelines:\n$(cat $TESTROOT/GitLabCI.queue)\n"; + echo -e "Currently queued pipelines:\n$(cat $LOCAL_HOME/GitLabCI.queue)\n"; done script: - mkdir -p $DAMASKROOT + - mkdir -p $TESTROOT - cd $DAMASKROOT - - if [ -d DAMASK ]; then rm -rf DAMASK; fi # there might be some leftovers from a failed clone - git clone -q git@magit1.mpie.de:damask/DAMASK.git . - git checkout $CI_COMMIT_SHA - git submodule update --init - source env/DAMASK.sh - make processing - - mkdir /tmp/$CI_PIPELINE_ID except: - master - release @@ -109,78 +99,77 @@ Pytest_python: stage: python script: - cd $DAMASKROOT/python - - pytest --basetemp=/tmp/${CI_PIPELINE_ID}/python + - pytest --basetemp=${TESTROOT}/python -v except: - master - release ################################################################################################### Pre_SeedGeneration: - stage: preprocessing + stage: deprecated script: PreProcessing_SeedGeneration/test.py except: - master - release Pre_GeomGeneration: - stage: preprocessing + stage: deprecated script: PreProcessing_GeomGeneration/test.py except: - master - release Pre_GeomModification: - stage: preprocessing + stage: deprecated script: PreProcessing_GeomModification/test.py except: - master - release Pre_General: - stage: preprocessing + stage: deprecated script: PreProcessing/test.py except: - master - release -################################################################################################### Post_General: - stage: postprocessing + stage: deprecated script: PostProcessing/test.py except: - master - release Post_GeometryReconstruction: - stage: postprocessing + stage: deprecated script: spectral_geometryReconstruction/test.py except: - master - release Post_addCurl: - stage: postprocessing + stage: deprecated script: addCurl/test.py except: - master - release Post_addDivergence: - stage: postprocessing + stage: deprecated script: addDivergence/test.py except: - master - release Post_addGradient: - stage: postprocessing + stage: deprecated script: addGradient/test.py except: - master - release Post_OrientationAverageMisorientation: - stage: postprocessing + stage: deprecated script: - OrientationAverageMisorientation/test.py except: @@ -188,61 +177,76 @@ Post_OrientationAverageMisorientation: - release ################################################################################################### -grid_mech_compile_Intel: - stage: compilePETSc +compile_grid_Intel: + stage: compile script: - - module load $IntelCompiler $MPICH_Intel $PETSc_MPICH_Intel - - cp -r grid_mech_compile grid_mech_compile_Intel - - grid_mech_compile_Intel/test.py + - module load $IntelCompiler $MPI_Intel $PETSc_Intel - cd pytest - - pytest -k 'compile and grid' --basetemp=/tmp/${CI_PIPELINE_ID}/compile_grid_Intel + - pytest -k 'compile and grid' --basetemp=${TESTROOT}/compile_grid_Intel except: - master - release -Compile_FEM_Intel: - stage: compilePETSc +compile_mesh_Intel: + stage: compile script: - - module load $IntelCompiler $MPICH_Intel $PETSc_MPICH_Intel - - cp -r FEM_compile FEM_compile_Intel - - FEM_compile_Intel/test.py + - module load $IntelCompiler $MPI_Intel $PETSc_Intel - cd pytest - - pytest -k 'compile and mesh' --basetemp=/tmp/${CI_PIPELINE_ID}/compile_mesh_Intel + - pytest -k 'compile and mesh' --basetemp=${TESTROOT}/compile_mesh_Intel except: - master - release -grid_mech_compile_GNU: - stage: compilePETSc +compile_grid_GNU: + stage: compile script: - - module load $GNUCompiler $MPICH_GNU $PETSc_MPICH_GNU - - cp -r grid_mech_compile grid_mech_compile_GNU - - grid_mech_compile_GNU/test.py + - module load $GNUCompiler $MPI_GNU $PETSc_GNU - cd pytest - - pytest -k 'compile and grid' --basetemp=/tmp/${CI_PIPELINE_ID}/compile_grid_GNU + - pytest -k 'compile and grid' --basetemp=${TESTROOT}/compile_grid_GNU except: - master - release -Compile_FEM_GNU: - stage: compilePETSc +compile_mesh_GNU: + stage: compile script: - - module load $GNUCompiler $MPICH_GNU $PETSc_MPICH_GNU - - cp -r FEM_compile FEM_compile_GNU - - FEM_compile_GNU/test.py + - module load $GNUCompiler $MPI_GNU $PETSc_GNU - cd pytest - - pytest -k 'compile and mesh' --basetemp=/tmp/${CI_PIPELINE_ID}/compile_mesh_GNU + - pytest -k 'compile and mesh' --basetemp=${TESTROOT}/compile_mesh_GNU except: - master - release -################################################################################################### -Compile_Intel_Prepare: - stage: prepareGrid +compile_MARC: + stage: compile script: - - module load $IntelCompiler $MPICH_Intel $PETSc_MPICH_Intel - - cd $DAMASKROOT - - make clean grid processing + - module load $IntelMarc $HDF5Marc $MSC + - cd pytest + - pytest -k 'compile and Marc' --basetemp=${TESTROOT}/compile_Marc + except: + - master + - release + +setup_grid: + stage: compile + script: + - module load $IntelCompiler $MPI_Intel $PETSc_Intel + - BUILD_DIR=$(mktemp -d) + - cd ${BUILD_DIR} + - cmake -DDAMASK_SOLVER=GRID -DCMAKE_INSTALL_PREFIX=${DAMASKROOT} ${DAMASKROOT} + - make -j2 all install + except: + - master + - release + +setup_mesh: + stage: compile + script: + - module load $IntelCompiler $MPI_Intel $PETSc_Intel + - BUILD_DIR=$(mktemp -d) + - cd ${BUILD_DIR} + - cmake -DDAMASK_SOLVER=MESH -DCMAKE_INSTALL_PREFIX=${DAMASKROOT} ${DAMASKROOT} + - make -j2 all install except: - master - release @@ -251,9 +255,9 @@ Compile_Intel_Prepare: Pytest_grid: stage: grid script: - - module load $IntelCompiler $MPICH_Intel $PETSc_MPICH_Intel + - module load $IntelCompiler $MPI_Intel $PETSc_Intel - cd pytest - - pytest -m 'not compile' --basetemp=/tmp/${CI_PIPELINE_ID}/fortran + - pytest -k 'not compile' --basetemp=${TESTROOT}/fortran -v except: - master - release @@ -265,13 +269,6 @@ Thermal: - master - release -grid_parsingArguments: - stage: grid - script: grid_parsingArguments/test.py - except: - - master - - release - Nonlocal_Damage_DetectChanges: stage: grid script: Nonlocal_Damage_DetectChanges/test.py @@ -279,22 +276,6 @@ Nonlocal_Damage_DetectChanges: - master - release -grid_all_restart: - stage: grid - script: grid_all_restart/test.py - except: - - master - - release - -grid_all_restartMPI: - stage: grid - script: - - module load $IntelCompiler $MPICH_Intel $PETSc_MPICH_Intel - - grid_all_restartMPI/test.py - except: - - master - - release - Plasticity_DetectChanges: stage: grid script: Plasticity_DetectChanges/test.py @@ -309,46 +290,8 @@ Phenopowerlaw_singleSlip: - master - release -################################################################################################### -Marc_compileIfort: - stage: compileMarc - script: - - module load $IntelMarc $HDF5Marc $MSC - - Marc_compileIfort/test.py - - cd pytest - - pytest -k 'compile and Marc' - except: - - master - - release ################################################################################################### -Hex_elastic: - stage: marc - script: - - module load $IntelMarc $HDF5Marc $MSC - - Hex_elastic/test.py - except: - - master - - release - -CubicFCC_elastic: - stage: marc - script: - - module load $IntelMarc $HDF5Marc $MSC - - CubicFCC_elastic/test.py - except: - - master - - release - -CubicBCC_elastic: - stage: marc - script: - - module load $IntelMarc $HDF5Marc $MSC - - CubicBCC_elastic/test.py - except: - - master - - release - J2_plasticBehavior: stage: marc script: @@ -367,25 +310,17 @@ Marc_elementLib: - master - release -################################################################################################### -grid_all_example: - stage: example - script: grid_all_example/test.py - except: - - master - - release - ################################################################################################### SpectralRuntime: stage: performance script: - - module load $IntelCompiler $MPICH_Intel $PETSc_MPICH_Intel + - module load $IntelCompiler $MPI_Intel $PETSc_Intel - cd $DAMASKROOT - make clean grid processing OPTIMIZATION=AGGRESSIVE - - cd $TESTROOT/performance # location of old results + - cd $LOCAL_HOME/performance # location of old results - git checkout . # undo any changes (i.e. run time data from non-development branch) - cd $DAMASKROOT/PRIVATE/testing - - SpectralAll_runtime/test.py -d $TESTROOT/performance + - SpectralAll_runtime/test.py -d $LOCAL_HOME/performance except: - master - release @@ -401,20 +336,10 @@ createTar: - release ################################################################################################### -Marc: +Python: stage: createDocumentation script: - - module load $IntelCompiler $MPICH_Intel $PETSc_MPICH_Intel $Doxygen - - $DAMASKROOT/PRIVATE/documenting/runDoxygen.sh $DAMASKROOT marc - except: - - master - - release - -GridSolver: - stage: createDocumentation - script: - - module load $IntelCompiler $MPICH_Intel $PETSc_MPICH_Intel $Doxygen - - $DAMASKROOT/PRIVATE/documenting/runDoxygen.sh $DAMASKROOT grid + - echo 'tbd one matesting1' except: - master - release @@ -423,11 +348,11 @@ GridSolver: backupData: stage: saveDocumentation script: - - cd $TESTROOT/performance # location of new runtime results + - cd $LOCAL_HOME/performance # location of new runtime results - git commit -am"${CI_PIPELINE_ID}_${CI_COMMIT_SHA}" - mkdir $BACKUP/${CI_PIPELINE_ID}_${CI_COMMIT_SHA} - - mv $TESTROOT/performance/time.png $BACKUP/${CI_PIPELINE_ID}_${CI_COMMIT_SHA}/ - - mv $TESTROOT/performance/memory.png $BACKUP/${CI_PIPELINE_ID}_${CI_COMMIT_SHA}/ + - mv $LOCAL_HOME/performance/time.png $BACKUP/${CI_PIPELINE_ID}_${CI_COMMIT_SHA}/ + - mv $LOCAL_HOME/performance/memory.png $BACKUP/${CI_PIPELINE_ID}_${CI_COMMIT_SHA}/ - mv $DAMASKROOT/PRIVATE/documenting/DAMASK_* $BACKUP/${CI_PIPELINE_ID}_${CI_COMMIT_SHA}/ only: - development @@ -457,8 +382,8 @@ removeData: before_script: - echo "Removing data and lock of pipeline $CI_PIPELINE_ID" script: - - rm -rf $TESTROOT/GitLabCI_Pipeline_$CI_PIPELINE_ID - - sed -i "/$CI_PIPELINE_ID/d" $TESTROOT/GitLabCI.queue # in case pipeline was manually (web GUI) restarted and releaseLock was performed already + - rm -rf $LOCAL_HOME/GitLabCI_Pipeline_$CI_PIPELINE_ID + - sed -i "/$CI_PIPELINE_ID/d" $LOCAL_HOME/GitLabCI.queue # in case pipeline was manually (web GUI) restarted and releaseLock was performed already except: - master - release @@ -469,7 +394,7 @@ removeLock: before_script: - echo "Removing lock of pipeline $CI_PIPELINE_ID" when: always - script: sed -i "/$CI_PIPELINE_ID/d" $TESTROOT/GitLabCI.queue + script: sed -i "/$CI_PIPELINE_ID/d" $LOCAL_HOME/GitLabCI.queue except: - master - release diff --git a/PRIVATE b/PRIVATE index 1e8c66897..751f96155 160000 --- a/PRIVATE +++ b/PRIVATE @@ -1 +1 @@ -Subproject commit 1e8c66897820468ab46958d995005e2b69204d0e +Subproject commit 751f96155294e449b31c5d4913fea7bc02ce81b1 diff --git a/VERSION b/VERSION index 2e3222238..689a4ba32 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v3.0.0-alpha-556-gf379aecd8 +v3.0.0-alpha-868-gb8344973d diff --git a/cmake/Compiler-Intel.cmake b/cmake/Compiler-Intel.cmake index 6d96ff42d..719ed885b 100644 --- a/cmake/Compiler-Intel.cmake +++ b/cmake/Compiler-Intel.cmake @@ -98,11 +98,12 @@ set (DEBUG_FLAGS "${DEBUG_FLAGS} -ftrapuv") set (DEBUG_FLAGS "${DEBUG_FLAGS} -fpe-all=0") # ... capture all floating-point exceptions, sets -ftz automatically -set (DEBUG_FLAGS "${DEBUG_FLAGS} -warn") +# disable due to compiler bug https://community.intel.com/t5/Intel-Fortran-Compiler/false-positive-stand-f18-and-IEEE-SELECTED-REAL-KIND/m-p/1227336 +#set (DEBUG_FLAGS "${DEBUG_FLAGS} -warn") # enables warnings ... -set (DEBUG_FLAGS "${DEBUG_FLAGS} errors") +#set (DEBUG_FLAGS "${DEBUG_FLAGS} errors") # ... warnings are changed to errors -set (DEBUG_FLAGS "${DEBUG_FLAGS},stderrors") +#set (DEBUG_FLAGS "${DEBUG_FLAGS},stderrors") # ... warnings about Fortran standard violations are changed to errors set (DEBUG_FLAGS "${DEBUG_FLAGS} -debug-parameters all") diff --git a/env/CONFIG b/env/CONFIG index 380197583..52057526a 100644 --- a/env/CONFIG +++ b/env/CONFIG @@ -2,4 +2,4 @@ set DAMASK_NUM_THREADS = 4 set MSC_ROOT = /opt/msc -set MARC_VERSION = 2019.1 +set MSC_VERSION = 2020 diff --git a/env/DAMASK.csh b/env/DAMASK.csh index 5529eac23..cc61449d2 100644 --- a/env/DAMASK.csh +++ b/env/DAMASK.csh @@ -51,4 +51,4 @@ else setenv PYTHONPATH $DAMASK_ROOT/python:$PYTHONPATH endif setenv MSC_ROOT -setenv MARC_VERSION +setenv MSC_VERSION diff --git a/examples/.gitignore b/examples/.gitignore index 3cb6b9820..93d78295b 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -3,6 +3,3 @@ *.xdmf *.sta *.vt* -*.geom -*.seeds -postProc diff --git a/examples/ConfigFiles/Phase_Isotropic_AluminumIsotropic.yaml b/examples/ConfigFiles/Phase_Isotropic_AluminumIsotropic.yaml index 446efc194..7b05140cb 100644 --- a/examples/ConfigFiles/Phase_Isotropic_AluminumIsotropic.yaml +++ b/examples/ConfigFiles/Phase_Isotropic_AluminumIsotropic.yaml @@ -1,16 +1,16 @@ # Kuo, J. C., Mikrostrukturmechanik von Bikristallen mit Kippkorngrenzen. Shaker-Verlag 2004. http://edoc.mpg.de/204079 Aluminum: - elasticity: {C_11: 110.9e9, C_12: 58.34e9, type: hooke} - generic: + mechanics: + lattice: aP + elasticity: {C_11: 110.9e9, C_12: 58.34e9, type: hooke} output: [F, P, Fe, Fp, Lp] - lattice: iso - plasticity: - type: isotropic - output: [xi] - xi_0: 31e6 - xi_inf: 63e6 - dot_gamma_0: 0.001 - n: 20 - M: 3 - h_0: 75e6 - a: 2.25 + plasticity: + type: isotropic + output: [xi] + xi_0: 31e6 + xi_inf: 63e6 + dot_gamma_0: 0.001 + n: 20 + M: 3 + h_0: 75e6 + a: 2.25 diff --git a/examples/ConfigFiles/Phase_Isotropic_FreeSurface.yaml b/examples/ConfigFiles/Phase_Isotropic_FreeSurface.yaml index 20148c4fb..4d9690f44 100644 --- a/examples/ConfigFiles/Phase_Isotropic_FreeSurface.yaml +++ b/examples/ConfigFiles/Phase_Isotropic_FreeSurface.yaml @@ -1,17 +1,17 @@ # Maiti and Eisenlohr 2018 Scripta Materialia Air: - elasticity: {C_11: 10e9, C_12: 0.0, type: hooke} - generic: + mechanics: + lattice: aP + elasticity: {C_11: 10e9, C_12: 0.0, type: hooke} output: [F, P, Fe, Fp, Lp] - lattice: iso - plasticity: - type: isotropic - output: [xi] - xi_0: 0.3e6 - xi_inf: 0.6e6 - dot_gamma_0: 0.001 - n: 5 - M: 3 - h_0: 1e6 - a: 2 - dilatation: true + plasticity: + type: isotropic + output: [xi] + xi_0: 0.3e6 + xi_inf: 0.6e6 + dot_gamma_0: 0.001 + n: 5 + M: 3 + h_0: 1e6 + a: 2 + dilatation: true diff --git a/examples/ConfigFiles/Phase_Phenopowerlaw_BCC-Ferrite.yaml b/examples/ConfigFiles/Phase_Phenopowerlaw_BCC-Ferrite.yaml index 4940ca8bd..ce3bbadb7 100644 --- a/examples/ConfigFiles/Phase_Phenopowerlaw_BCC-Ferrite.yaml +++ b/examples/ConfigFiles/Phase_Phenopowerlaw_BCC-Ferrite.yaml @@ -2,15 +2,16 @@ # Tasan et.al. 2015 International Journal of Plasticity # Diehl et.al. 2015 Meccanica Ferrite: - elasticity: {C_11: 233.3e9, C_12: 135.5e9, C_44: 118.0e9, type: hooke} - lattice: bcc - plasticity: - N_sl: [12, 12] - a_sl: 2.0 - dot_gamma_0_sl: 0.001 - h_0_sl_sl: 1000.0e6 - h_sl_sl: [1, 1, 1.4, 1.4, 1.4, 1.4] - n_sl: 20 - type: phenopowerlaw - xi_0_sl: [95.e6, 96.e6] - xi_inf_sl: [222.e6, 412.7e6] + mechanics: + lattice: cI + elasticity: {C_11: 233.3e9, C_12: 135.5e9, C_44: 118.0e9, type: hooke} + plasticity: + N_sl: [12, 12] + a_sl: 2.0 + dot_gamma_0_sl: 0.001 + h_0_sl_sl: 1000.0e6 + h_sl_sl: [1, 1, 1.4, 1.4, 1.4, 1.4] + n_sl: 20 + type: phenopowerlaw + xi_0_sl: [95.e6, 96.e6] + xi_inf_sl: [222.e6, 412.7e6] diff --git a/examples/ConfigFiles/Phase_Phenopowerlaw_BCC-Martensite.yaml b/examples/ConfigFiles/Phase_Phenopowerlaw_BCC-Martensite.yaml index b207d7b34..ab79ceeb1 100644 --- a/examples/ConfigFiles/Phase_Phenopowerlaw_BCC-Martensite.yaml +++ b/examples/ConfigFiles/Phase_Phenopowerlaw_BCC-Martensite.yaml @@ -2,15 +2,16 @@ # Tasan et.al. 2015 International Journal of Plasticity # Diehl et.al. 2015 Meccanica Martensite: - elasticity: {C_11: 417.4e9, C_12: 242.4e9, C_44: 211.1e9, type: hooke} - lattice: bcc - plasticity: - N_sl: [12, 12] - a_sl: 2.0 - dot_gamma_0_sl: 0.001 - h_0_sl_sl: 563.0e9 - h_sl_sl: [1, 1, 1.4, 1.4, 1.4, 1.4] - n_sl: 20 - type: phenopowerlaw - xi_0_sl: [405.8e6, 456.7e6] - xi_inf_sl: [872.9e6, 971.2e6] + mechanics: + lattice: cI + elasticity: {C_11: 417.4e9, C_12: 242.4e9, C_44: 211.1e9, type: hooke} + plasticity: + N_sl: [12, 12] + a_sl: 2.0 + dot_gamma_0_sl: 0.001 + h_0_sl_sl: 563.0e9 + h_sl_sl: [1, 1, 1.4, 1.4, 1.4, 1.4] + n_sl: 20 + type: phenopowerlaw + xi_0_sl: [405.8e6, 456.7e6] + xi_inf_sl: [872.9e6, 971.2e6] diff --git a/examples/FEM/polyXtal/material.yaml b/examples/FEM/polyXtal/material.yaml index 5c00f17ad..c7d17657d 100644 --- a/examples/FEM/polyXtal/material.yaml +++ b/examples/FEM/polyXtal/material.yaml @@ -1,26 +1,26 @@ homogenization: SX: N_constituents: 1 - mech: {type: none} + mechanics: {type: none} phase: Aluminum: - elasticity: {C_11: 106.75e9, C_12: 60.41e9, C_44: 28.34e9, type: hooke} - generic: - output: [F, P, Fe, Fp, Lp] - lattice: fcc - plasticity: - N_sl: [12] - a_sl: 2.25 - atol_xi: 1.0 - dot_gamma_0_sl: 0.001 - h_0_sl_sl: 75e6 - h_sl_sl: [1, 1, 1.4, 1.4, 1.4, 1.4] - n_sl: 20 - output: [xi_sl] - type: phenopowerlaw - xi_0_sl: [31e6] - xi_inf_sl: [63e6] + mechanics: + lattice: cF + output: [F, P, F_e, F_p, L_p] + elasticity: {C_11: 106.75e9, C_12: 60.41e9, C_44: 28.34e9, type: hooke} + plasticity: + N_sl: [12] + a_sl: 2.25 + atol_xi: 1.0 + dot_gamma_0_sl: 0.001 + h_0_sl_sl: 75e6 + h_sl_sl: [1, 1, 1.4, 1.4, 1.4, 1.4] + n_sl: 20 + output: [xi_sl] + type: phenopowerlaw + xi_0_sl: [31e6] + xi_inf_sl: [63e6] material: - constituents: diff --git a/examples/SpectralMethod/Polycrystal/material.yaml b/examples/SpectralMethod/Polycrystal/material.yaml index 0717d4eab..b560a919d 100644 --- a/examples/SpectralMethod/Polycrystal/material.yaml +++ b/examples/SpectralMethod/Polycrystal/material.yaml @@ -2,7 +2,7 @@ homogenization: SX: N_constituents: 1 - mech: {type: none} + mechanics: {type: none} material: - homogenization: SX @@ -108,19 +108,19 @@ material: phase: Aluminum: - elasticity: {C_11: 106.75e9, C_12: 60.41e9, C_44: 28.34e9, type: hooke} - generic: - output: [F, P, Fe, Fp, Lp, O] - lattice: fcc - plasticity: - N_sl: [12] - a_sl: 2.25 - atol_xi: 1.0 - dot_gamma_0_sl: 0.001 - h_0_sl_sl: 75e6 - h_sl_sl: [1, 1, 1.4, 1.4, 1.4, 1.4] - n_sl: 20 - output: [xi_sl] - type: phenopowerlaw - xi_0_sl: [31e6] - xi_inf_sl: [63e6] + lattice: cF + mechanics: + output: [F, P, F_e, F_p, L_p, O] + elasticity: {C_11: 106.75e9, C_12: 60.41e9, C_44: 28.34e9, type: hooke} + plasticity: + N_sl: [12] + a_sl: 2.25 + atol_xi: 1.0 + dot_gamma_0_sl: 0.001 + h_0_sl_sl: 75e6 + h_sl_sl: [1, 1, 1.4, 1.4, 1.4, 1.4] + n_sl: 20 + output: [xi_sl] + type: phenopowerlaw + xi_0_sl: [31e6] + xi_inf_sl: [63e6] diff --git a/examples/SpectralMethod/Polycrystal/shearXY.yaml b/examples/SpectralMethod/Polycrystal/shearXY.yaml index 39966d181..559bdfe56 100644 --- a/examples/SpectralMethod/Polycrystal/shearXY.yaml +++ b/examples/SpectralMethod/Polycrystal/shearXY.yaml @@ -1,5 +1,5 @@ step: - - mech: + - mechanics: dot_F: [0, 0, 0, 1e-3, 0, 0, 0, 0, 0] diff --git a/examples/SpectralMethod/Polycrystal/shearZX.yaml b/examples/SpectralMethod/Polycrystal/shearZX.yaml index 4395ecf17..df7b887e5 100644 --- a/examples/SpectralMethod/Polycrystal/shearZX.yaml +++ b/examples/SpectralMethod/Polycrystal/shearZX.yaml @@ -1,6 +1,6 @@ --- step: - - mech: + - mechanics: dot_F: [0, 0, 1e-3, 0, 0, 0, 0, 0, 0] diff --git a/examples/SpectralMethod/Polycrystal/tensionX.yaml b/examples/SpectralMethod/Polycrystal/tensionX.yaml index 6e86fdcf2..2f1d11f91 100644 --- a/examples/SpectralMethod/Polycrystal/tensionX.yaml +++ b/examples/SpectralMethod/Polycrystal/tensionX.yaml @@ -1,7 +1,7 @@ --- step: - - mech: + - mechanics: dot_F: [1.0e-3, 0, 0, 0, x, 0, 0, 0, x] @@ -12,7 +12,7 @@ step: t: 10 N: 40 f_out: 4 - - mech: + - mechanics: dot_F: [1.0e-3, 0, 0, 0, x, 0, 0, 0, x] diff --git a/installation/mods_MarcMentat/apply_DAMASK_modifications.py b/installation/mods_MarcMentat/apply_DAMASK_modifications.py index 38a6078b3..407c33558 100755 --- a/installation/mods_MarcMentat/apply_DAMASK_modifications.py +++ b/installation/mods_MarcMentat/apply_DAMASK_modifications.py @@ -7,15 +7,15 @@ from pathlib import Path import damask -marc_version = float(damask.environment.options['MARC_VERSION']) -if int(marc_version) == marc_version: - marc_version = int(marc_version) +msc_version = float(damask.environment.options['MSC_VERSION']) +if int(msc_version) == msc_version: + msc_version = int(msc_version) msc_root = Path(damask.environment.options['MSC_ROOT']) damask_root = damask.environment.root_dir parser = argparse.ArgumentParser( description='Apply DAMASK modification to MSC.Marc/Mentat', - epilog = f'MSC_ROOT={msc_root} and MARC_VERSION={marc_version} (from {damask_root}/env/CONFIG)') + epilog = f'MSC_ROOT={msc_root} and MSC_VERSION={msc_version} (from {damask_root}/env/CONFIG)') parser.add_argument('--editor', dest='editor', metavar='string', default='vi', help='Name of the editor for MSC.Mentat (executable)') @@ -23,8 +23,8 @@ parser.add_argument('--editor', dest='editor', metavar='string', default='vi', def copy_and_replace(in_file,dst): with open(in_file) as f: content = f.read() - content = content.replace('%INSTALLDIR%',msc_root) - content = content.replace('%VERSION%',str(marc_version)) + content = content.replace('%INSTALLDIR%',str(msc_root)) + content = content.replace('%VERSION%',str(msc_version)) content = content.replace('%EDITOR%', parser.parse_args().editor) with open(dst/Path(in_file).name,'w') as f: f.write(content) @@ -32,36 +32,36 @@ def copy_and_replace(in_file,dst): print('adapting Marc tools...\n') -src = damask_root/f'installation/mods_MarcMentat/{marc_version}/Marc_tools' -dst = msc_root/f'marc{marc_version}/tools' +src = damask_root/f'installation/mods_MarcMentat/{msc_version}/Marc_tools' +dst = msc_root/f'marc{msc_version}/tools' for in_file in glob.glob(str(src/'*damask*')) + [str(src/'include_linux64')]: copy_and_replace(in_file,dst) print('adapting Mentat scripts and menus...\n') -src = damask_root/f'installation/mods_MarcMentat/{marc_version}/Mentat_bin' -dst = msc_root/f'mentat{marc_version}/bin' +src = damask_root/f'installation/mods_MarcMentat/{msc_version}/Mentat_bin' +dst = msc_root/f'mentat{msc_version}/bin' for in_file in glob.glob(str(src/'*[!.original]')): copy_and_replace(in_file,dst) -src = damask_root/f'installation/mods_MarcMentat/{marc_version}/Mentat_menus' -dst = msc_root/f'mentat{marc_version}/menus' +src = damask_root/f'installation/mods_MarcMentat/{msc_version}/Mentat_menus' +dst = msc_root/f'mentat{msc_version}/menus' for in_file in glob.glob(str(src/'job_run.ms')): copy_and_replace(in_file,dst) print('compiling Mentat menu binaries...') -executable = str(msc_root/f'mentat{marc_version}/bin/mentat') -menu_file = str(msc_root/f'mentat{marc_version}/menus/linux64/main.msb') +executable = str(msc_root/f'mentat{msc_version}/bin/mentat') +menu_file = str(msc_root/f'mentat{msc_version}/menus/linux64/main.msb') os.system(f'xvfb-run {executable} -compile {menu_file}') print('setting file access rights...\n') -for pattern in [msc_root/f'marc{marc_version}/tools/*damask*', - msc_root/f'mentat{marc_version}/bin/submit?', - msc_root/f'mentat{marc_version}/bin/kill?']: +for pattern in [msc_root/f'marc{msc_version}/tools/*damask*', + msc_root/f'mentat{msc_version}/bin/submit?', + msc_root/f'mentat{msc_version}/bin/kill?']: for f in glob.glob(str(pattern)): os.chmod(f,0o755) diff --git a/processing/post/DADF5_postResults.py b/processing/post/DADF5_postResults.py index 02eb72d87..be4f78569 100755 --- a/processing/post/DADF5_postResults.py +++ b/processing/post/DADF5_postResults.py @@ -20,9 +20,9 @@ parser.add_argument('filenames', nargs='+', parser.add_argument('-d','--dir', dest='dir',default='postProc',metavar='string', help='name of subdirectory relative to the location of the DADF5 file to hold output') parser.add_argument('--mat', nargs='+', - help='labels for materialpoint',dest='mat') + help='labels for homogenization',dest='mat') parser.add_argument('--con', nargs='+', - help='labels for constituent',dest='con') + help='labels for phase',dest='con') options = parser.parse_args() @@ -41,15 +41,15 @@ for filename in options.filenames: table = damask.Table(np.ones(np.product(results.grid),dtype=int)*int(inc[3:]),{'inc':(1,)})\ .add('pos',coords.reshape(-1,3)) - results.pick('materialpoints',False) - results.pick('constituents', True) + results.pick('homogenizations',False) + results.pick('phases',True) for label in options.con: x = results.get_dataset_location(label) if len(x) != 0: table = table.add(label,results.read_dataset(x,0,plain=True).reshape(results.grid.prod(),-1)) - results.pick('constituents', False) - results.pick('materialpoints',True) + results.pick('phases',False) + results.pick('homogenizations',True) for label in options.mat: x = results.get_dataset_location(label) if len(x) != 0: diff --git a/processing/post/addCompatibilityMismatch.py b/processing/post/addCompatibilityMismatch.py index 0e7d3ea42..1b4425e06 100755 --- a/processing/post/addCompatibilityMismatch.py +++ b/processing/post/addCompatibilityMismatch.py @@ -13,85 +13,6 @@ import damask scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) -def volTetrahedron(coords): - """ - Return the volume of the tetrahedron with given vertices or sides. - - If vertices are given they must be in a NumPy array with shape (4,3): the - position vectors of the 4 vertices in 3 dimensions; if the six sides are - given, they must be an array of length 6. If both are given, the sides - will be used in the calculation. - - This method implements - Tartaglia's formula using the Cayley-Menger determinant: - |0 1 1 1 1 | - |1 0 s1^2 s2^2 s3^2| - 288 V^2 = |1 s1^2 0 s4^2 s5^2| - |1 s2^2 s4^2 0 s6^2| - |1 s3^2 s5^2 s6^2 0 | - where s1, s2, ..., s6 are the tetrahedron side lengths. - - from http://codereview.stackexchange.com/questions/77593/calculating-the-volume-of-a-tetrahedron - """ - # The indexes of rows in the vertices array corresponding to all - # possible pairs of vertices - vertex_pair_indexes = np.array(((0, 1), (0, 2), (0, 3), - (1, 2), (1, 3), (2, 3))) - - # Get all the squares of all side lengths from the differences between - # the 6 different pairs of vertex positions - vertices = np.concatenate((coords[0],coords[1],coords[2],coords[3])).reshape(4,3) - vertex1, vertex2 = vertex_pair_indexes[:,0], vertex_pair_indexes[:,1] - sides_squared = np.sum((vertices[vertex1] - vertices[vertex2])**2,axis=-1) - - - # Set up the Cayley-Menger determinant - M = np.zeros((5,5)) - # Fill in the upper triangle of the matrix - M[0,1:] = 1 - # The squared-side length elements can be indexed using the vertex - # pair indices (compare with the determinant illustrated above) - M[tuple(zip(*(vertex_pair_indexes + 1)))] = sides_squared - - # The matrix is symmetric, so we can fill in the lower triangle by - # adding the transpose - M = M + M.T - return np.sqrt(np.linalg.det(M) / 288) - - -def volumeMismatch(size,F,nodes): - """ - Calculates the volume mismatch. - - volume mismatch is defined as the difference between volume of reconstructed - (compatible) cube and determinant of deformation gradient at Fourier point. - """ - coords = np.empty([8,3]) - vMismatch = np.empty(F.shape[:3]) - -#-------------------------------------------------------------------------------------------------- -# calculate actual volume and volume resulting from deformation gradient - for k in range(grid[0]): - for j in range(grid[1]): - for i in range(grid[2]): - coords[0,0:3] = nodes[k, j, i ,0:3] - coords[1,0:3] = nodes[k ,j, i+1,0:3] - coords[2,0:3] = nodes[k ,j+1,i+1,0:3] - coords[3,0:3] = nodes[k, j+1,i ,0:3] - coords[4,0:3] = nodes[k+1,j, i ,0:3] - coords[5,0:3] = nodes[k+1,j, i+1,0:3] - coords[6,0:3] = nodes[k+1,j+1,i+1,0:3] - coords[7,0:3] = nodes[k+1,j+1,i ,0:3] - vMismatch[k,j,i] = \ - ( abs(volTetrahedron([coords[6,0:3],coords[0,0:3],coords[7,0:3],coords[3,0:3]])) \ - + abs(volTetrahedron([coords[6,0:3],coords[0,0:3],coords[7,0:3],coords[4,0:3]])) \ - + abs(volTetrahedron([coords[6,0:3],coords[0,0:3],coords[2,0:3],coords[3,0:3]])) \ - + abs(volTetrahedron([coords[6,0:3],coords[0,0:3],coords[2,0:3],coords[1,0:3]])) \ - + abs(volTetrahedron([coords[6,0:3],coords[4,0:3],coords[1,0:3],coords[5,0:3]])) \ - + abs(volTetrahedron([coords[6,0:3],coords[4,0:3],coords[1,0:3],coords[0,0:3]]))) \ - /np.linalg.det(F[k,j,i,0:3,0:3]) - return vMismatch/(size.prod()/grid.prod()) - def shapeMismatch(size,F,nodes,centres): """ @@ -101,42 +22,23 @@ def shapeMismatch(size,F,nodes,centres): the corners of reconstructed (combatible) volume element and the vectors calculated by deforming the initial volume element with the current deformation gradient. """ - sMismatch = np.empty(F.shape[:3]) - -#-------------------------------------------------------------------------------------------------- -# initial positions delta = size/grid*.5 - coordsInitial = np.vstack((delta * np.array((-1,-1,-1)), - delta * np.array((+1,-1,-1)), - delta * np.array((+1,+1,-1)), - delta * np.array((-1,+1,-1)), - delta * np.array((-1,-1,+1)), - delta * np.array((+1,-1,+1)), - delta * np.array((+1,+1,+1)), - delta * np.array((-1,+1,+1)))) -#-------------------------------------------------------------------------------------------------- -# compare deformed original and deformed positions to actual positions - for k in range(grid[0]): - for j in range(grid[1]): - for i in range(grid[2]): - sMismatch[k,j,i] = \ - + np.linalg.norm(nodes[k, j, i ,0:3] - centres[k,j,i,0:3] - np.dot(F[k,j,i,:,:], coordsInitial[0,0:3]))\ - + np.linalg.norm(nodes[k+1,j, i ,0:3] - centres[k,j,i,0:3] - np.dot(F[k,j,i,:,:], coordsInitial[1,0:3]))\ - + np.linalg.norm(nodes[k+1,j+1,i ,0:3] - centres[k,j,i,0:3] - np.dot(F[k,j,i,:,:], coordsInitial[2,0:3]))\ - + np.linalg.norm(nodes[k, j+1,i ,0:3] - centres[k,j,i,0:3] - np.dot(F[k,j,i,:,:], coordsInitial[3,0:3]))\ - + np.linalg.norm(nodes[k, j, i+1,0:3] - centres[k,j,i,0:3] - np.dot(F[k,j,i,:,:], coordsInitial[4,0:3]))\ - + np.linalg.norm(nodes[k+1,j, i+1,0:3] - centres[k,j,i,0:3] - np.dot(F[k,j,i,:,:], coordsInitial[5,0:3]))\ - + np.linalg.norm(nodes[k+1,j+1,i+1,0:3] - centres[k,j,i,0:3] - np.dot(F[k,j,i,:,:], coordsInitial[6,0:3]))\ - + np.linalg.norm(nodes[k ,j+1,i+1,0:3] - centres[k,j,i,0:3] - np.dot(F[k,j,i,:,:], coordsInitial[7,0:3])) - return sMismatch + return + np.linalg.norm(nodes[:-1,:-1,:-1] -centres - np.dot(F,delta * np.array((-1,-1,-1))),axis=-1)\ + + np.linalg.norm(nodes[+1:,:-1,:-1] -centres - np.dot(F,delta * np.array((+1,-1,-1))),axis=-1)\ + + np.linalg.norm(nodes[+1:,+1:,:-1] -centres - np.dot(F,delta * np.array((+1,+1,-1))),axis=-1)\ + + np.linalg.norm(nodes[:-1,+1:,:-1] -centres - np.dot(F,delta * np.array((-1,+1,-1))),axis=-1)\ + + np.linalg.norm(nodes[:-1,:-1,+1:] -centres - np.dot(F,delta * np.array((-1,-1,+1))),axis=-1)\ + + np.linalg.norm(nodes[+1:,:-1,+1:] -centres - np.dot(F,delta * np.array((+1,-1,+1))),axis=-1)\ + + np.linalg.norm(nodes[+1:,+1:,+1:] -centres - np.dot(F,delta * np.array((+1,+1,+1))),axis=-1)\ + + np.linalg.norm(nodes[:-1,+1:,+1:] -centres - np.dot(F,delta * np.array((-1,+1,+1))),axis=-1) # -------------------------------------------------------------------- # MAIN # -------------------------------------------------------------------- -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ +parser = OptionParser(usage='%prog options [ASCIItable(s)]', description = """ Add column(s) containing the shape and volume mismatch resulting from given deformation gradient. Operates on periodic three-dimensional x,y,z-ordered data sets. @@ -155,10 +57,6 @@ parser.add_option('--no-shape','-s', dest = 'shape', action = 'store_false', help = 'omit shape mismatch') -parser.add_option('--no-volume','-v', - dest = 'volume', - action = 'store_false', - help = 'omit volume mismatch') parser.set_defaults(pos = 'pos', defgrad = 'f', shape = True, @@ -185,10 +83,4 @@ for name in filenames: shapeMismatch.reshape(-1,1,order='F'), scriptID+' '+' '.join(sys.argv[1:])) - if options.volume: - volumeMismatch = volumeMismatch(size,F,nodes) - table = table.add('volMismatch(({}))'.format(options.defgrad), - volumeMismatch.reshape(-1,1,order='F'), - scriptID+' '+' '.join(sys.argv[1:])) - - table.save((sys.stdout if name is None else name), legacy=True) + table.save((sys.stdout if name is None else name)) diff --git a/processing/post/addCurl.py b/processing/post/addCurl.py index 699fc945f..d91c2be92 100755 --- a/processing/post/addCurl.py +++ b/processing/post/addCurl.py @@ -55,4 +55,4 @@ for name in filenames: curl.reshape(tuple(grid)+(-1,)).reshape(-1,np.prod(shape),order='F'), scriptID+' '+' '.join(sys.argv[1:])) - table.save((sys.stdout if name is None else name), legacy=True) + table.save((sys.stdout if name is None else name)) diff --git a/processing/post/addDerivative.py b/processing/post/addDerivative.py deleted file mode 100755 index 99016f4ef..000000000 --- a/processing/post/addDerivative.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -from io import StringIO -from optparse import OptionParser - -import numpy as np - -import damask - - -scriptName = os.path.splitext(os.path.basename(__file__))[0] -scriptID = ' '.join([scriptName,damask.version]) - -def derivative(coordinates,what): - - result = np.empty_like(what) - - # use differentiation by interpolation - # as described in http://www2.math.umd.edu/~dlevy/classes/amsc466/lecture-notes/differentiation-chap.pdf - - result[1:-1,:] = + what[1:-1,:] * (2.*coordinates[1:-1]-coordinates[:-2]-coordinates[2:]) / \ - ((coordinates[1:-1]-coordinates[:-2])*(coordinates[1:-1]-coordinates[2:])) \ - + what[2:,:] * (coordinates[1:-1]-coordinates[:-2]) / \ - ((coordinates[2:]-coordinates[1:-1])*(coordinates[2:]-coordinates[:-2])) \ - + what[:-2,:] * (coordinates[1:-1]-coordinates[2:]) / \ - ((coordinates[:-2]-coordinates[1:-1])*(coordinates[:-2]-coordinates[2:])) \ - - result[0,:] = (what[0,:] - what[1,:]) / \ - (coordinates[0] - coordinates[1]) - result[-1,:] = (what[-1,:] - what[-2,:]) / \ - (coordinates[-1] - coordinates[-2]) - - return result - - -# -------------------------------------------------------------------- -# MAIN -# -------------------------------------------------------------------- - -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ -Add column(s) containing numerical derivative of requested column(s) with respect to given coordinates. - -""", version = scriptID) - -parser.add_option('-c','--coordinates', - dest = 'coordinates', - type = 'string', metavar='string', - help = 'heading of coordinate column') -parser.add_option('-l','--label', - dest = 'labels', - action = 'extend', metavar = '', - help = 'heading of column(s) to differentiate') - - -(options,filenames) = parser.parse_args() -if filenames == []: filenames = [None] - -if options.coordinates is None: - parser.error('no coordinate column specified.') -if options.labels is None: - parser.error('no data column specified.') - -for name in filenames: - damask.util.report(scriptName,name) - - table = damask.Table.load(StringIO(''.join(sys.stdin.read())) if name is None else name) - for label in options.labels: - table = table.add('d({})/d({})'.format(label,options.coordinates), - derivative(table.get(options.coordinates),table.get(label)), - scriptID+' '+' '.join(sys.argv[1:])) - - table.save((sys.stdout if name is None else name), legacy=True) diff --git a/processing/post/addDisplacement.py b/processing/post/addDisplacement.py index a6cff86ab..c3ba4a8af 100755 --- a/processing/post/addDisplacement.py +++ b/processing/post/addDisplacement.py @@ -16,7 +16,7 @@ scriptID = ' '.join([scriptName,damask.version]) # MAIN # -------------------------------------------------------------------- -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ +parser = OptionParser(usage='%prog options [ASCIItable(s)]', description = """ Add displacments resulting from deformation gradient field. Operates on periodic three-dimensional x,y,z-ordered data sets. Outputs at cell centers or cell nodes (into separate file). @@ -60,7 +60,7 @@ for name in filenames: .add('fluct({}).{}'.format(options.f,options.pos), damask.grid_filters.node_displacement_fluct(size,F).reshape(-1,3,order='F'), scriptID+' '+' '.join(sys.argv[1:]))\ - .save((sys.stdout if name is None else os.path.splitext(name)[0]+'_nodal.txt'), legacy=True) + .save((sys.stdout if name is None else os.path.splitext(name)[0]+'_nodal.txt')) else: table.add('avg({}).{}'.format(options.f,options.pos), damask.grid_filters.cell_displacement_avg(size,F).reshape(-1,3,order='F'), @@ -68,4 +68,4 @@ for name in filenames: .add('fluct({}).{}'.format(options.f,options.pos), damask.grid_filters.cell_displacement_fluct(size,F).reshape(-1,3,order='F'), scriptID+' '+' '.join(sys.argv[1:]))\ - .save((sys.stdout if name is None else name), legacy=True) + .save((sys.stdout if name is None else name)) diff --git a/processing/post/addDivergence.py b/processing/post/addDivergence.py index 208a0f7b6..6e5629285 100755 --- a/processing/post/addDivergence.py +++ b/processing/post/addDivergence.py @@ -55,4 +55,4 @@ for name in filenames: div.reshape(tuple(grid)+(-1,)).reshape(-1,np.prod(shape)//3,order='F'), scriptID+' '+' '.join(sys.argv[1:])) - table.save((sys.stdout if name is None else name), legacy=True) + table.save((sys.stdout if name is None else name)) diff --git a/processing/post/addEuclideanDistance.py b/processing/post/addEuclideanDistance.py index fc43542bd..0c18bdccc 100755 --- a/processing/post/addEuclideanDistance.py +++ b/processing/post/addEuclideanDistance.py @@ -184,4 +184,4 @@ for name in filenames: distance[i,:], scriptID+' '+' '.join(sys.argv[1:])) - table.save((sys.stdout if name is None else name), legacy=True) + table.save((sys.stdout if name is None else name)) diff --git a/processing/post/addGradient.py b/processing/post/addGradient.py index d049b65d7..147773734 100755 --- a/processing/post/addGradient.py +++ b/processing/post/addGradient.py @@ -55,4 +55,4 @@ for name in filenames: grad.reshape(tuple(grid)+(-1,)).reshape(-1,np.prod(shape)*3,order='F'), scriptID+' '+' '.join(sys.argv[1:])) - table.save((sys.stdout if name is None else name), legacy=True) + table.save((sys.stdout if name is None else name)) diff --git a/processing/post/addOrientations.py b/processing/post/addOrientations.py deleted file mode 100755 index 6a02cca08..000000000 --- a/processing/post/addOrientations.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -from io import StringIO -from optparse import OptionParser - -import numpy as np - -import damask - - -scriptName = os.path.splitext(os.path.basename(__file__))[0] -scriptID = ' '.join([scriptName,damask.version]) - -# -------------------------------------------------------------------- -# MAIN -# -------------------------------------------------------------------- - -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ -Add quaternion and/or Bunge Euler angle representation of crystal lattice orientation. -Orientation is given by quaternion, Euler angles, rotation matrix, or crystal frame coordinates -(i.e. component vectors of rotation matrix). -Additional (globally fixed) rotations of the lab frame and/or crystal frame can be applied. - -""", version = scriptID) - -representations = ['quaternion', 'rodrigues', 'eulers', 'matrix', 'axisangle'] - - -parser.add_option('-o', - '--output', - dest = 'output', - action = 'extend', metavar = '', - help = 'output orientation formats {{{}}}'.format(', '.join(representations))) -parser.add_option('-d', - '--degrees', - dest = 'degrees', - action = 'store_true', - help = 'all angles in degrees') -parser.add_option('-R', - '--labrotation', - dest='labrotation', - type = 'float', nargs = 4, metavar = ' '.join(['float']*4), - help = 'axis and angle of additional lab frame rotation [%default]') -parser.add_option('-r', - '--crystalrotation', - dest='crystalrotation', - type = 'float', nargs = 4, metavar = ' '.join(['float']*4), - help = 'axis and angle of additional crystal frame rotation [%default]') -parser.add_option('--eulers', - dest = 'eulers', - metavar = 'string', - help = 'Euler angles label') -parser.add_option('--rodrigues', - dest = 'rodrigues', - metavar = 'string', - help = 'Rodrigues vector label') -parser.add_option('--matrix', - dest = 'matrix', - metavar = 'string', - help = 'orientation matrix label') -parser.add_option('--quaternion', - dest = 'quaternion', - metavar = 'string', - help = 'quaternion label') -parser.add_option('-x', - dest = 'x', - metavar = 'string', - help = 'label of lab x vector (expressed in crystal coords)') -parser.add_option('-y', - dest = 'y', - metavar = 'string', - help = 'label of lab y vector (expressed in crystal coords)') -parser.add_option('-z', - dest = 'z', - metavar = 'string', - help = 'label of lab z vector (expressed in crystal coords)') -parser.add_option('--lattice', - dest = 'lattice', - metavar = 'string', - help = 'lattice structure to reduce rotation into fundamental zone') - -parser.set_defaults(output = [], - labrotation = (1.,1.,1.,0.), # no rotation about (1,1,1) - crystalrotation = (1.,1.,1.,0.), # no rotation about (1,1,1) - lattice = None, - ) - -(options, filenames) = parser.parse_args() -if filenames == []: filenames = [None] - -if options.output == [] or (not set(options.output).issubset(set(representations))): - parser.error('output must be chosen from {}.'.format(', '.join(representations))) - -input = [options.eulers is not None, - options.rodrigues is not None, - options.x is not None and \ - options.y is not None and \ - options.z is not None, - options.matrix is not None, - options.quaternion is not None, - ] - -if np.sum(input) != 1: parser.error('needs exactly one input format.') - -r = damask.Rotation.from_axis_angle(np.array(options.crystalrotation),options.degrees,normalize=True) -R = damask.Rotation.from_axis_angle(np.array(options.labrotation),options.degrees,normalize=True) - -for name in filenames: - damask.util.report(scriptName,name) - - table = damask.Table.load(StringIO(''.join(sys.stdin.read())) if name is None else name) - - if options.eulers is not None: - label = options.eulers - print(np.max(table.get(options.eulers),axis=0)) - o = damask.Rotation.from_Eulers(table.get(options.eulers), options.degrees) - elif options.rodrigues is not None: - label = options.rodrigues - o = damask.Rotation.from_Rodrigues(table.get(options.rodrigues)) - elif options.matrix is not None: - label = options.matrix - o = damask.Rotation.from_matrix(table.get(options.matrix).reshape(-1,3,3)) - elif options.x is not None: - label = '<{},{},{}>'.format(options.x,options.y,options.z) - M = np.block([table.get(options.x),table.get(options.y),table.get(options.z)]).reshape(-1,3,3) - o = damask.Rotation.from_matrix(M/np.linalg.norm(M,axis=0)) - elif options.quaternion is not None: - label = options.quaternion - o = damask.Rotation.from_quaternion(table.get(options.quaternion)) - - o = r.broadcast_to(o.shape) @ o @ R.broadcast_to(o.shape) - - #if options.lattice is not None: - # o = damask.Orientation(rotation = o,lattice = options.lattice).reduced().rotation - - - if 'rodrigues' in options.output: - table = table.add('ro({})'.format(label),o.as_Rodrigues(), scriptID+' '+' '.join(sys.argv[1:])) - if 'eulers' in options.output: - table = table.add('eu({})'.format(label),o.as_Eulers(options.degrees), scriptID+' '+' '.join(sys.argv[1:])) - if 'quaternion' in options.output: - table = table.add('qu({})'.format(label),o.as_quaternion(), scriptID+' '+' '.join(sys.argv[1:])) - if 'matrix' in options.output: - table = table.add('om({})'.format(label),o.as_matrix(), scriptID+' '+' '.join(sys.argv[1:])) - if 'axisangle' in options.output: - table = table.add('om({})'.format(label),o.as_axisangle(options.degrees), scriptID+' '+' '.join(sys.argv[1:])) - - table.save((sys.stdout if name is None else name), legacy=True) diff --git a/processing/post/addSchmidfactors.py b/processing/post/addSchmidfactors.py index 8f43308cb..8e1e4ffb7 100755 --- a/processing/post/addSchmidfactors.py +++ b/processing/post/addSchmidfactors.py @@ -14,91 +14,16 @@ scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) slipSystems = { -'fcc': - np.array([ - [+0,+1,-1 , +1,+1,+1], - [-1,+0,+1 , +1,+1,+1], - [+1,-1,+0 , +1,+1,+1], - [+0,-1,-1 , -1,-1,+1], - [+1,+0,+1 , -1,-1,+1], - [-1,+1,+0 , -1,-1,+1], - [+0,-1,+1 , +1,-1,-1], - [-1,+0,-1 , +1,-1,-1], - [+1,+1,+0 , +1,-1,-1], - [+0,+1,+1 , -1,+1,-1], - [+1,+0,-1 , -1,+1,-1], - [-1,-1,+0 , -1,+1,-1], - ],'d'), -'bcc': - np.array([ - [+1,-1,+1 , +0,+1,+1], - [-1,-1,+1 , +0,+1,+1], - [+1,+1,+1 , +0,-1,+1], - [-1,+1,+1 , +0,-1,+1], - [-1,+1,+1 , +1,+0,+1], - [-1,-1,+1 , +1,+0,+1], - [+1,+1,+1 , -1,+0,+1], - [+1,-1,+1 , -1,+0,+1], - [-1,+1,+1 , +1,+1,+0], - [-1,+1,-1 , +1,+1,+0], - [+1,+1,+1 , -1,+1,+0], - [+1,+1,-1 , -1,+1,+0], - [-1,+1,+1 , +2,+1,+1], - [+1,+1,+1 , -2,+1,+1], - [+1,+1,-1 , +2,-1,+1], - [+1,-1,+1 , +2,+1,-1], - [+1,-1,+1 , +1,+2,+1], - [+1,+1,-1 , -1,+2,+1], - [+1,+1,+1 , +1,-2,+1], - [-1,+1,+1 , +1,+2,-1], - [+1,+1,-1 , +1,+1,+2], - [+1,-1,+1 , -1,+1,+2], - [-1,+1,+1 , +1,-1,+2], - [+1,+1,+1 , +1,+1,-2], - ],'d'), -'hex': - np.array([ - [+2,-1,-1,+0 , +0,+0,+0,+1], - [-1,+2,-1,+0 , +0,+0,+0,+1], - [-1,-1,+2,+0 , +0,+0,+0,+1], - [+2,-1,-1,+0 , +0,+1,-1,+0], - [-1,+2,-1,+0 , -1,+0,+1,+0], - [-1,-1,+2,+0 , +1,-1,+0,+0], - [-1,+1,+0,+0 , +1,+1,-2,+0], - [+0,-1,+1,+0 , -2,+1,+1,+0], - [+1,+0,-1,+0 , +1,-2,+1,+0], - [-1,+2,-1,+0 , +1,+0,-1,+1], - [-2,+1,+1,+0 , +0,+1,-1,+1], - [-1,-1,+2,+0 , -1,+1,+0,+1], - [+1,-2,+1,+0 , -1,+0,+1,+1], - [+2,-1,-1,+0 , +0,-1,+1,+1], - [+1,+1,-2,+0 , +1,-1,+0,+1], - [-2,+1,+1,+3 , +1,+0,-1,+1], - [-1,-1,+2,+3 , +1,+0,-1,+1], - [-1,-1,+2,+3 , +0,+1,-1,+1], - [+1,-2,+1,+3 , +0,+1,-1,+1], - [+1,-2,+1,+3 , -1,+1,+0,+1], - [+2,-1,-1,+3 , -1,+1,+0,+1], - [+2,-1,-1,+3 , -1,+0,+1,+1], - [+1,+1,-2,+3 , -1,+0,+1,+1], - [+1,+1,-2,+3 , +0,-1,+1,+1], - [-1,+2,-1,+3 , +0,-1,+1,+1], - [-1,+2,-1,+3 , +1,-1,+0,+1], - [-2,+1,+1,+3 , +1,-1,+0,+1], - [-1,-1,+2,+3 , +1,+1,-2,+2], - [+1,-2,+1,+3 , -1,+2,-1,+2], - [+2,-1,-1,+3 , -2,+1,+1,+2], - [+1,+1,-2,+3 , -1,-1,+2,+2], - [-1,+2,-1,+3 , +1,-2,+1,+2], - [-2,+1,+1,+3 , +2,-1,-1,+2], - ],'d'), +'fcc': damask.lattice.kinematics['cF']['slip'][:12], +'bcc': damask.lattice.kinematics['cI']['slip'], +'hex': damask.lattice.kinematics['hP']['slip'], } # -------------------------------------------------------------------- # MAIN # -------------------------------------------------------------------- -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ +parser = OptionParser(usage='%prog options [ASCIItable(s)]', description = """ Add columns listing Schmid factors (and optional trace vector of selected system) for given Euler angles. """, version = scriptID) @@ -156,11 +81,11 @@ elif options.lattice == 'hex': # convert 4 Miller index notation of hex to orthogonal 3 Miller index notation for i in range(len(slip_direction)): slip_direction[i] = np.array([slipSystems['hex'][i,0]*1.5, - (slipSystems['hex'][i,0] + 2.*slipSystems['hex'][i,1])*0.5*np.sqrt(3), + (slipSystems['hex'][i,0] + 2.*slipSystems['hex'][i,1])*0.5*np.sqrt(3), slipSystems['hex'][i,3]*options.CoverA, ]) slip_normal[i] = np.array([slipSystems['hex'][i,4], - (slipSystems['hex'][i,4] + 2.*slipSystems['hex'][i,5])/np.sqrt(3), + (slipSystems['hex'][i,4] + 2.*slipSystems['hex'][i,5])/np.sqrt(3), slipSystems['hex'][i,7]/options.CoverA, ]) @@ -189,4 +114,4 @@ for name in filenames: for i,label in enumerate(labels): table = table.add(label,S[:,i],scriptID+' '+' '.join(sys.argv[1:])) - table.save((sys.stdout if name is None else name), legacy=True) + table.save((sys.stdout if name is None else name)) diff --git a/processing/post/permuteData.py b/processing/post/permuteData.py deleted file mode 100755 index 073ccfd9f..000000000 --- a/processing/post/permuteData.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -from io import StringIO -from optparse import OptionParser - -import numpy as np - -import damask - - -scriptName = os.path.splitext(os.path.basename(__file__))[0] -scriptID = ' '.join([scriptName,damask.version]) - - -# -------------------------------------------------------------------- -# MAIN -# -------------------------------------------------------------------- - -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ -Permute all values in given column(s). - -""", version = scriptID) - -parser.add_option('-l','--label', - dest = 'label', - action = 'extend', metavar = '', - help ='column(s) to permute') -parser.add_option('-u', '--unique', - dest = 'unique', - action = 'store_true', - help = 'shuffle unique values as group') -parser.add_option('-r', '--rnd', - dest = 'randomSeed', - type = 'int', metavar = 'int', - help = 'seed of random number generator [%default]') - -parser.set_defaults(label = [], - unique = False, - randomSeed = None, - ) - -(options,filenames) = parser.parse_args() -if filenames == []: filenames = [None] - -for name in filenames: - damask.util.report(scriptName,name) - - table = damask.Table.load(StringIO(''.join(sys.stdin.read())) if name is None else name) - - randomSeed = int(os.urandom(4).hex(), 16) if options.randomSeed is None else options.randomSeed # random seed per file - rng = np.random.default_rng(randomSeed) - - for label in options.label: - data = table.get(label) - uniques,inverse = np.unique(data,return_inverse=True,axis=0) if options.unique else (data,np.arange(len(data))) - rng.shuffle(uniques) - table = table.set(label,uniques[inverse], scriptID+' '+' '.join(sys.argv[1:])) - - table.save((sys.stdout if name is None else name), legacy=True) diff --git a/processing/pre/geom_fromDREAM3D.py b/processing/pre/geom_fromDREAM3D.py index eca837455..eb4fe443e 100755 --- a/processing/pre/geom_fromDREAM3D.py +++ b/processing/pre/geom_fromDREAM3D.py @@ -14,7 +14,7 @@ scriptID = ' '.join([scriptName,damask.version]) # MAIN #-------------------------------------------------------------------------------------------------- -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [DREAM.3Dfile(s)]', description = """ +parser = OptionParser(usage='%prog options [DREAM.3Dfile(s)]', description = """ Converts DREAM.3D file. Input can be cell data (direct pointwise takeover) or grain data (individual grains are segmented). Requires orientation data as quaternion. diff --git a/processing/pre/geom_fromMinimalSurface.py b/processing/pre/geom_fromMinimalSurface.py deleted file mode 100755 index eb0cdcc3b..000000000 --- a/processing/pre/geom_fromMinimalSurface.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -from optparse import OptionParser - -import damask - - -scriptName = os.path.splitext(os.path.basename(__file__))[0] -scriptID = ' '.join([scriptName,damask.version]) - - -minimal_surfaces = list(damask.Geom._minimal_surface.keys()) - -# -------------------------------------------------------------------- -# MAIN -# -------------------------------------------------------------------- - -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [geomfile]', description = """ -Generate a bicontinuous structure of given type. - -""", version = scriptID) - - -parser.add_option('-t','--type', - dest = 'type', - choices = minimal_surfaces, metavar = 'string', - help = 'type of minimal surface [primitive] {%s}' %(','.join(minimal_surfaces))) -parser.add_option('-f','--threshold', - dest = 'threshold', - type = 'float', metavar = 'float', - help = 'threshold value defining minimal surface [%default]') -parser.add_option('-g', '--grid', - dest = 'grid', - type = 'int', nargs = 3, metavar = 'int int int', - help = 'a,b,c grid of hexahedral box [%default]') -parser.add_option('-s', '--size', - dest = 'size', - type = 'float', nargs = 3, metavar = 'float float float', - help = 'x,y,z size of hexahedral box [%default]') -parser.add_option('-p', '--periods', - dest = 'periods', - type = 'int', metavar = 'int', - help = 'number of repetitions of unit cell [%default]') -parser.add_option('--m', - dest = 'microstructure', - type = 'int', nargs = 2, metavar = 'int int', - help = 'two microstructure indices to be used [%default]') - -parser.set_defaults(type = minimal_surfaces[0], - threshold = 0.0, - periods = 1, - grid = (16,16,16), - size = (1.0,1.0,1.0), - microstructure = (1,2), - ) - -(options,filename) = parser.parse_args() - - -name = None if filename == [] else filename[0] -damask.util.report(scriptName,name) - -geom=damask.Geom.from_minimal_surface(options.grid,options.size,options.type,options.threshold, - options.periods,options.microstructure) -damask.util.croak(geom) - -geom.save_ASCII(sys.stdout if name is None else name) diff --git a/processing/pre/geom_fromOsteonGeometry.py b/processing/pre/geom_fromOsteonGeometry.py index 0b6d48001..51aca3056 100755 --- a/processing/pre/geom_fromOsteonGeometry.py +++ b/processing/pre/geom_fromOsteonGeometry.py @@ -17,7 +17,7 @@ scriptID = ' '.join([scriptName,damask.version]) # MAIN # -------------------------------------------------------------------- -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [geomfile]', description = """ +parser = OptionParser(usage='%prog options [geomfile]', description = """ Generate description of an osteon enclosing the Harvesian canal and separated by interstitial tissue. The osteon phase is lamellar with a twisted plywood structure. Its fiber orientation is oscillating by +/- amplitude within one period. diff --git a/processing/pre/geom_fromTable.py b/processing/pre/geom_fromTable.py deleted file mode 100755 index 9ff4ffdc2..000000000 --- a/processing/pre/geom_fromTable.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -from optparse import OptionParser - -import damask - - -scriptName = os.path.splitext(os.path.basename(__file__))[0] -scriptID = ' '.join([scriptName,damask.version]) - - -# -------------------------------------------------------------------- -# MAIN -# -------------------------------------------------------------------- - -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [ASCIItable(s)]', description = """ -Converts ASCII table. Input can be microstructure or orientation (as quaternion). For the latter, -phase information can be given additionally. - -""", version = scriptID) - -parser.add_option('--coordinates', - dest = 'pos', - type = 'string', metavar = 'string', - help = 'coordinates label (%default)') -parser.add_option('--phase', - dest = 'phase', - type = 'string', metavar = 'string', - help = 'phase label') -parser.add_option('--microstructure', - dest = 'microstructure', - type = 'string', metavar = 'string', - help = 'microstructure label') -parser.add_option('-q', '--quaternion', - dest = 'quaternion', - type = 'string', metavar='string', - help = 'quaternion label') - -parser.set_defaults(pos= 'pos') - -(options,filenames) = parser.parse_args() -if filenames == []: filenames = [None] - -for name in filenames: - damask.util.report(scriptName,name) - - labels = [] - for l in [options.quaternion,options.phase,options.microstructure]: - if l is not None: labels.append(l) - - t = damask.Table.load(name) - geom = damask.Geom.from_table(t,options.pos,labels) - damask.util.croak(geom) - - geom.save_ASCII(sys.stdout if name is None else os.path.splitext(name)[0]+'.geom') diff --git a/processing/pre/geom_grainGrowth.py b/processing/pre/geom_grainGrowth.py index 249cb07f5..5c751d662 100755 --- a/processing/pre/geom_grainGrowth.py +++ b/processing/pre/geom_grainGrowth.py @@ -15,8 +15,8 @@ scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) -getInterfaceEnergy = lambda A,B: np.float32((A*B != 0)*(A != B)*1.0) # 1.0 if A & B are distinct & nonzero, 0.0 otherwise -struc = ndimage.generate_binary_structure(3,1) # 3D von Neumann neighborhood +getInterfaceEnergy = lambda A,B: np.float32((A != B)*1.0) # 1.0 if A & B are distinct, 0.0 otherwise +struc = ndimage.generate_binary_structure(3,1) # 3D von Neumann neighborhood #-------------------------------------------------------------------------------------------------- @@ -62,7 +62,7 @@ if filenames == []: filenames = [None] for name in filenames: damask.util.report(scriptName,name) - geom = damask.Geom.load_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + geom = damask.Geom.load(StringIO(''.join(sys.stdin.read())) if name is None else name) grid_original = geom.grid damask.util.croak(geom) @@ -174,4 +174,4 @@ for name in filenames: origin = geom.origin, comments = geom.comments + [scriptID + ' ' + ' '.join(sys.argv[1:])], )\ - .save_ASCII(sys.stdout if name is None else name) + .save(sys.stdout if name is None else name) diff --git a/processing/pre/mentat_pbcOnBoxMesh.py b/processing/pre/mentat_pbcOnBoxMesh.py index 3677a5efa..4a4f3d642 100755 --- a/processing/pre/mentat_pbcOnBoxMesh.py +++ b/processing/pre/mentat_pbcOnBoxMesh.py @@ -11,8 +11,6 @@ import numpy as np import damask -sys.path.append(str(damask.solver.Marc().library_path)) - scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) @@ -30,11 +28,11 @@ def parseMFD(dat): # lines that start with a space are numerical data if line[0] == ' ': formatted[section]['els'].append([]) - + # grab numbers nums = re.split(r'\s+', line.strip()) - - for num in nums: + + for num in nums: # floating point has format ' -x.xxxxxxxxxxxxe+yy' # scientific notation is used for float if (len(num) >= 4) and (num[-4] == 'e'): @@ -47,7 +45,7 @@ def parseMFD(dat): else: formatted[section]['els'].append([]) formatted[section]['els'][-1] = line - + else: # Not in a section, we are looking for a =beg= now search = re.search(r'=beg=\s+(\d+)\s\((.*?)\)', line) if search is not None: # found start of a new section @@ -60,7 +58,7 @@ def parseMFD(dat): section += 1 formatted.append({'label': '', 'uid': -2, 'els': []}) # make dummy section to store unrecognized data formatted[section]['els'].append(line) - + return formatted def asMFD(mfd_data): @@ -93,14 +91,14 @@ def add_servoLinks(mfd_data,active=[True,True,True]): # directions on which to 'max': np.zeros(3,dtype='d'), 'delta': np.zeros(3,dtype='d'), } - + mfd_dict = {} for i in range(len(mfd_data)): mfd_dict[mfd_data[i]['label']] = i NodeCoords = np.array(mfd_data[mfd_dict['nodes']]['els'][1::4])[:,1:4] - Nnodes = NodeCoords.shape[0] - + Nnodes = NodeCoords.shape[0] + box['min'] = NodeCoords.min(axis=0) # find the bounding box box['max'] = NodeCoords.max(axis=0) box['delta'] = box['max']-box['min'] @@ -131,7 +129,7 @@ def add_servoLinks(mfd_data,active=[True,True,True]): # directions on which to elif (key[base[coord]] == "%.8e"%box['max'][coord]): # compare to max of bounding box (i.e. is on outer face?) Nmax += 1 # count outer (front) face membership maxFlag[coord] = True # remember face membership (for linked nodes) - + if Nmin > 0: # node is on a back face # prepare for any non-existing entries in the data structure if key['x'] not in baseNode.keys(): @@ -140,17 +138,17 @@ def add_servoLinks(mfd_data,active=[True,True,True]): # directions on which to baseNode[key['x']][key['y']] = {} if key['z'] not in baseNode[key['x']][key['y']].keys(): baseNode[key['x']][key['y']][key['z']] = 0 - + baseNode[key['x']][key['y']][key['z']] = node+1 # remember the base node id if Nmax > 0 and Nmax >= Nmin: # node is on at least as many front than back faces if any([maxFlag[i] and active[i] for i in range(3)]): linkNodes.append({'id': node+1,'coord': NodeCoords[node], 'faceMember': [maxFlag[i] and active[i] for i in range(3)]}) - + mfd_data[mfd_dict['entities']]['els'][0][0] += len(linkNodes) * 3 - + baseCorner = baseNode["%.8e"%box['min'][0]]["%.8e"%box['min'][1]]["%.8e"%box['min'][2]] # detect ultimate base node - + links = {'uid': 1705, 'label': 'links', 'els': [[7,0],[9,0]]} linkID = 0 for node in linkNodes: # loop over all linked nodes @@ -165,7 +163,7 @@ def add_servoLinks(mfd_data,active=[True,True,True]): # directions on which to for dof in [1,2,3]: tied_node = node['id'] nterms = 1 + nLinks - + linkID += 1 # Link header links['els'].append('link{0}\n'.format(linkID)) @@ -173,10 +171,10 @@ def add_servoLinks(mfd_data,active=[True,True,True]): # directions on which to links['els'].append([0]) links['els'].append([0]) links['els'].append([0, 0, 0, tied_node]) - + # these need to be put in groups of four link_payload = [dof, 0, nterms] - + # Individual node contributions (node, dof, coef.) for i in range(nterms): if i == nLinks: @@ -190,7 +188,7 @@ def add_servoLinks(mfd_data,active=[True,True,True]): # directions on which to link_payload.append(1.0 - nLinks) else: link_payload.append(1.0) - + # Needs to be formatted 4 data points per row, character width of 20, so 80 total for j in range(0, len(link_payload), 4): links['els'].append(link_payload[j:j+4]) @@ -206,9 +204,9 @@ def add_servoLinks(mfd_data,active=[True,True,True]): # directions on which to #-------------------------------------------------------------------------------------------------- # MAIN -#-------------------------------------------------------------------------------------------------- +#-------------------------------------------------------------------------------------------------- -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [file[s]]', description = """ +parser = OptionParser(usage='%prog options [file[s]]', description = """ Set up servo linking to achieve periodic boundary conditions for a regular hexahedral mesh. Use *py_connection to operate on model presently opened in MSC.Mentat. """, version = scriptID) @@ -235,6 +233,7 @@ if remote and filenames != []: if filenames == []: filenames = [None] if remote: + sys.path.append(str(damask.solver.Marc().library_path)) import py_mentat damask.util.report(scriptName, 'waiting to connect...') diff --git a/processing/pre/mentat_spectralBox.py b/processing/pre/mentat_spectralBox.py index 7d78cb973..d182a6d54 100755 --- a/processing/pre/mentat_spectralBox.py +++ b/processing/pre/mentat_spectralBox.py @@ -9,7 +9,6 @@ import damask scriptName = os.path.splitext(os.path.basename(__file__))[0] scriptID = ' '.join([scriptName,damask.version]) -sys.path.append(str(damask.solver.Marc().library_path)) #------------------------------------------------------------------------------------------------- def outMentat(cmd,locals): @@ -168,7 +167,7 @@ def initial_conditions(material): # MAIN #-------------------------------------------------------------------------------------------------- -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [file[s]]', description = """ +parser = OptionParser(usage='%prog options [file[s]]', description = """ Generate MSC.Marc FE hexahedral mesh from geom file. """, version = scriptID) @@ -185,9 +184,10 @@ parser.set_defaults(port = None, if options.port is not None: try: + sys.path.append(str(damask.solver.Marc().library_path)) import py_mentat except ImportError: - parser.error('no valid Mentat release found.') + parser.error('no valid Mentat release found') # --- loop over input files ------------------------------------------------------------------------ @@ -196,7 +196,7 @@ if filenames == []: filenames = [None] for name in filenames: damask.util.report(scriptName,name) - geom = damask.Geom.load_ASCII(StringIO(''.join(sys.stdin.read())) if name is None else name) + geom = damask.Geom.load(StringIO(''.join(sys.stdin.read())) if name is None else name) material = geom.material.flatten(order='F') cmds = [\ diff --git a/processing/pre/seeds_fromDistribution.py b/processing/pre/seeds_fromDistribution.py index 375f33ad3..1f20db995 100755 --- a/processing/pre/seeds_fromDistribution.py +++ b/processing/pre/seeds_fromDistribution.py @@ -164,7 +164,7 @@ class myThread (threading.Thread): # MAIN # -------------------------------------------------------------------- -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [file[s]]', description = """ +parser = OptionParser(usage='%prog options [file[s]]', description = """ Monte Carlo simulation to produce seed file that gives same size distribution like given geometry file. """, version = scriptID) diff --git a/processing/pre/seeds_fromPokes.py b/processing/pre/seeds_fromPokes.py index 887d76392..67acf0f6b 100755 --- a/processing/pre/seeds_fromPokes.py +++ b/processing/pre/seeds_fromPokes.py @@ -16,7 +16,7 @@ scriptID = ' '.join([scriptName,damask.version]) # MAIN # -------------------------------------------------------------------- -parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [file[s]]', description = """ +parser = OptionParser(usage='%prog options [file[s]]', description = """ Create seeds file by poking at 45 degree through given geom file. Mimics APS Beamline 34-ID-E DAXM poking. diff --git a/python/damask/__init__.py b/python/damask/__init__.py index 600f64138..a1ccfa3f6 100644 --- a/python/damask/__init__.py +++ b/python/damask/__init__.py @@ -1,4 +1,12 @@ -"""Tools for pre and post processing of DAMASK simulations.""" +""" +Tools for pre and post processing of DAMASK simulations. + +Modules that contain only one class (of the same name), +are prefixed by a '_'. For example, '_colormap' contains +a class called 'Colormap' which is imported as 'damask.Colormap'. + +""" + from pathlib import Path as _Path import re as _re @@ -12,15 +20,16 @@ from ._environment import Environment as _ # noqa environment = _() from . import util # noqa from . import seeds # noqa +from . import tensor # noqa from . import mechanics # noqa from . import solver # noqa from . import grid_filters # noqa -from ._lattice import Symmetry, Lattice# noqa -from ._table import Table # noqa +from . import lattice # noqa from ._rotation import Rotation # noqa +from ._orientation import Orientation # noqa +from ._table import Table # noqa from ._vtk import VTK # noqa from ._colormap import Colormap # noqa -from ._orientation import Orientation # noqa from ._config import Config # noqa from ._configmaterial import ConfigMaterial # noqa from ._geom import Geom # noqa diff --git a/python/damask/_asciitable.py b/python/damask/_asciitable.py index 9d762369a..de7596aea 100644 --- a/python/damask/_asciitable.py +++ b/python/damask/_asciitable.py @@ -385,38 +385,3 @@ class ASCIItable(): self.data = np.loadtxt(self.__IO__['in'],usecols=use,ndmin=2) return labels_missing - -# ------------------------------------------------------------------ - def data_write(self): - """Write current data array and report alive output back.""" - if len(self.data) == 0: return True - - if isinstance(self.data[0],list): - return self.output_write(['\t'.join(map(self._quote,items)) for items in self.data]) - else: - return self.output_write( '\t'.join(map(self._quote,self.data))) - -# ------------------------------------------------------------------ - def data_writeArray(self): - """Write whole numpy array data.""" - for row in self.data: - try: - output = list(map(repr,row)) - except Exception: - output = [repr(row)] - - try: - self.__IO__['out'].write('\t'.join(output) + '\n') - except Exception: - pass - -# ------------------------------------------------------------------ - def data_append(self, - what): - if isinstance(what, str): - self.data += [what] - else: - try: - for item in what: self.data_append(item) - except TypeError: - self.data += [str(what)] diff --git a/python/damask/_colormap.py b/python/damask/_colormap.py index 6803f4c60..aaa3fdc4a 100644 --- a/python/damask/_colormap.py +++ b/python/damask/_colormap.py @@ -22,6 +22,19 @@ _ref_white = np.array([.95047, 1.00000, 1.08883]) # - support NaN color (paraview) class Colormap(mpl.colors.ListedColormap): + """ + Enhance matplotlib colormap functionality to be used within DAMASK. + + References + ---------- + [1] DAMASK colormap theory + https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf + [2] DAMASK colormaps first use + https://doi.org/10.1016/j.ijplas.2012.09.012 + [3] Matplotlib colormaps overview + https://matplotlib.org/tutorials/colors/colormaps.html + + """ def __add__(self,other): """Concatenate colormaps.""" @@ -36,6 +49,17 @@ class Colormap(mpl.colors.ListedColormap): """Return inverted colormap.""" return self.reversed() + def __repr__(self): + """Show colormap as matplotlib figure.""" + fig = plt.figure(self.name,figsize=(5,.5)) + ax1 = fig.add_axes([0, 0, 1, 1]) + ax1.set_axis_off() + ax1.imshow(np.linspace(0,1,self.N).reshape(1,-1), + aspect='auto', cmap=self, interpolation='nearest') + plt.show(block = False) + return self.name + + @staticmethod def from_range(low,high,name='DAMASK colormap',N=256,model='rgb'): """ @@ -126,40 +150,16 @@ class Colormap(mpl.colors.ListedColormap): """ # matplotlib presets - for cat in Colormap._predefined_mpl: - for n in cat[1]: - if n == name: - colormap = cm.__dict__[name] - if isinstance(colormap,mpl.colors.LinearSegmentedColormap): - return Colormap(np.array(list(map(colormap,np.linspace(0,1,N)))),name=name) - else: - return Colormap(np.array(colormap.colors),name=name) - - # DAMASK presets - definition = Colormap._predefined_DAMASK[name] - return Colormap.from_range(definition['low'],definition['high'],name,N) - - - @staticmethod - def list_predefined(): - """ - List predefined colormaps by category. - - References - ---------- - [1] DAMASK colormap theory - https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf - [2] DAMASK colormaps first use - https://doi.org/10.1016/j.ijplas.2012.09.012 - [3] Matplotlib colormaps overview - https://matplotlib.org/tutorials/colors/colormaps.html - - """ - print('DAMASK colormaps') - print(' '+', '.join(Colormap._predefined_DAMASK.keys())) - for cat in Colormap._predefined_mpl: - print(f'{cat[0]}') - print(' '+', '.join(cat[1])) + try: + colormap = cm.__dict__[name] + return Colormap(np.array(list(map(colormap,np.linspace(0,1,N))) + if isinstance(colormap,mpl.colors.LinearSegmentedColormap) else + colormap.colors), + name=name) + except KeyError: + # DAMASK presets + definition = Colormap._predefined_DAMASK[name] + return Colormap.from_range(definition['low'],definition['high'],name,N) def shade(self,field,bounds=None,gap=None): @@ -168,9 +168,9 @@ class Colormap(mpl.colors.ListedColormap): Parameters ---------- - field : numpy.array of shape(:,:) + field : numpy.array of shape (:,:) Data to be shaded. - bounds : iterable of len(2), optional + bounds : iterable of len (2), optional Colormap value range (low,high). gap : field.dtype, optional Transparent value. NaN will always be rendered transparent. @@ -203,18 +203,6 @@ class Colormap(mpl.colors.ListedColormap): mode='RGBA') - def show(self,aspect=10,vertical=False): - """Show colormap as matplotlib figure.""" - fig = plt.figure(figsize=(5/aspect,5) if vertical else (5,5/aspect)) - ax1 = fig.add_axes([0, 0, 1, 1]) - ax1.set_axis_off() - ax1.imshow(np.linspace(1 if vertical else 0, - 0 if vertical else 1, - self.N).reshape((-1,1) if vertical else (1,-1)), - aspect='auto', cmap=self, interpolation='nearest') - plt.show() - - def reversed(self,name=None): """ Make a reversed instance of the colormap. @@ -235,7 +223,6 @@ class Colormap(mpl.colors.ListedColormap): return Colormap(np.array(rev.colors),rev.name[:-4] if rev.name.endswith('_r_r') else rev.name) - def save_paraview(self,fname=None): """ Write colormap to JSON file for Paraview. @@ -247,13 +234,13 @@ class Colormap(mpl.colors.ListedColormap): consist of the name of the colormap and extension '.json'. """ - if fname is not None: + if fname is None: + fhandle = None + else: try: fhandle = open(fname,'w') except TypeError: fhandle = fname - else: - fhandle = None colors = [] for i,c in enumerate(np.round(self.colors,6).tolist()): @@ -266,11 +253,9 @@ class Colormap(mpl.colors.ListedColormap): 'DefaultMap':True, 'RGBPoints':colors }] - if fhandle is None: - with open(self.name.replace(' ','_')+'.json', 'w') as f: - json.dump(out, f,indent=4) - else: - json.dump(out,fhandle,indent=4) + + with open(self.name.replace(' ','_')+'.json', 'w') if fhandle is None else fhandle as f: + json.dump(out, f,indent=4) def save_ASCII(self,fname=None): @@ -284,22 +269,19 @@ class Colormap(mpl.colors.ListedColormap): consist of the name of the colormap and extension '.txt'. """ - if fname is not None: + if fname is None: + fhandle = None + else: try: fhandle = open(fname,'w') except TypeError: fhandle = fname - else: - fhandle = None labels = {'RGBA':4} if self.colors.shape[1] == 4 else {'RGB': 3} t = Table(self.colors,labels,f'Creator: {util.execution_stamp("Colormap")}') - if fhandle is None: - with open(self.name.replace(' ','_')+'.txt', 'w') as f: - t.save(f) - else: - t.save(fhandle) + with open(self.name.replace(' ','_')+'.txt', 'w') if fhandle is None else fhandle as f: + t.save(f) def save_GOM(self,fname=None): @@ -313,24 +295,21 @@ class Colormap(mpl.colors.ListedColormap): consist of the name of the colormap and extension '.legend'. """ - if fname is not None: + if fname is None: + fhandle = None + else: try: fhandle = open(fname,'w') except TypeError: fhandle = fname - else: - fhandle = None # ToDo: test in GOM GOM_str = '1 1 {name} 9 {name} '.format(name=self.name.replace(" ","_")) \ + '0 1 0 3 0 0 -1 9 \\ 0 0 0 255 255 255 0 0 255 ' \ + f'30 NO_UNIT 1 1 64 64 64 255 1 0 0 0 0 0 0 3 0 {len(self.colors)}' \ + ' '.join([f' 0 {c[0]} {c[1]} {c[2]} 255 1' for c in reversed((self.colors*255).astype(int))]) \ + '\n' - if fhandle is None: - with open(self.name.replace(' ','_')+'.legend', 'w') as f: - f.write(GOM_str) - else: - fhandle.write(GOM_str) + with open(self.name.replace(' ','_')+'.legend', 'w') if fhandle is None else fhandle as f: + f.write(GOM_str) def save_gmsh(self,fname=None): @@ -344,22 +323,19 @@ class Colormap(mpl.colors.ListedColormap): consist of the name of the colormap and extension '.msh'. """ - if fname is not None: + if fname is None: + fhandle = None + else: try: fhandle = open(fname,'w') except TypeError: fhandle = fname - else: - fhandle = None # ToDo: test in gmsh gmsh_str = 'View.ColorTable = {\n' \ +'\n'.join([f'{c[0]},{c[1]},{c[2]},' for c in self.colors[:,:3]*255]) \ +'\n}\n' - if fhandle is None: - with open(self.name.replace(' ','_')+'.msh', 'w') as f: - f.write(gmsh_str) - else: - fhandle.write(gmsh_str) + with open(self.name.replace(' ','_')+'.msh', 'w') if fhandle is None else fhandle as f: + f.write(gmsh_str) @staticmethod @@ -387,7 +363,6 @@ class Colormap(mpl.colors.ListedColormap): if msh_sat[2] < - np.pi/3.0: hSpin *= -1.0 return msh_sat[2] + hSpin - lo = np.array(low) hi = np.array(high) @@ -407,28 +382,28 @@ class Colormap(mpl.colors.ListedColormap): return (1.0 - frac) * lo + frac * hi - _predefined_mpl= [('Perceptually Uniform Sequential', [ - 'viridis', 'plasma', 'inferno', 'magma', 'cividis']), - ('Sequential', [ + _predefined_mpl= {'Perceptually Uniform Sequential': [ + 'viridis', 'plasma', 'inferno', 'magma', 'cividis'], + 'Sequential': [ 'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds', 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu', - 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn']), - ('Sequential (2)', [ + 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn'], + 'Sequential (2)': [ 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia', - 'hot', 'afmhot', 'gist_heat', 'copper']), - ('Diverging', [ + 'hot', 'afmhot', 'gist_heat', 'copper'], + 'Diverging': [ 'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu', - 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']), - ('Cyclic', ['twilight', 'twilight_shifted', 'hsv']), - ('Qualitative', [ + 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic'], + 'Cyclic': ['twilight', 'twilight_shifted', 'hsv'], + 'Qualitative': [ 'Pastel1', 'Pastel2', 'Paired', 'Accent', 'Dark2', 'Set1', 'Set2', 'Set3', - 'tab10', 'tab20', 'tab20b', 'tab20c']), - ('Miscellaneous', [ + 'tab10', 'tab20', 'tab20b', 'tab20c'], + 'Miscellaneous': [ 'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern', 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', - 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar'])] + 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar']} _predefined_DAMASK = {'orientation': {'low': [0.933334,0.878432,0.878431], 'high': [0.250980,0.007843,0.000000]}, @@ -437,6 +412,9 @@ class Colormap(mpl.colors.ListedColormap): 'stress': {'low': [0.878432,0.874511,0.949019], 'high': [0.000002,0.000000,0.286275]}} + predefined = dict(**{'DAMASK':list(_predefined_DAMASK)},**_predefined_mpl) + + @staticmethod def _hsv2rgb(hsv): """ diff --git a/python/damask/_config.py b/python/damask/_config.py index c9130d7aa..24245f4bd 100644 --- a/python/damask/_config.py +++ b/python/damask/_config.py @@ -1,6 +1,7 @@ from io import StringIO import abc +import numpy as np import yaml class NiceDumper(yaml.SafeDumper): @@ -15,6 +16,11 @@ class NiceDumper(yaml.SafeDumper): def increase_indent(self, flow=False, indentless=False): return super().increase_indent(flow, False) + def represent_data(self, data): + """Cast Config objects and its subclasses to dict.""" + return self.represent_data(dict(data)) if isinstance(data, dict) and type(data) != dict else \ + super().represent_data(data) + class Config(dict): """YAML-based configuration.""" @@ -64,7 +70,20 @@ class Config(dict): kwargs['width'] = 256 if 'default_flow_style' not in kwargs: kwargs['default_flow_style'] = None - fhandle.write(yaml.dump(dict(self),Dumper=NiceDumper,**kwargs)) + if 'sort_keys' not in kwargs: + kwargs['sort_keys'] = False + + def array_representer(dumper, data): + """Convert numpy array to list of native types.""" + return dumper.represent_list([d.item() for d in data]) + + NiceDumper.add_representer(np.ndarray, array_representer) + + try: + fhandle.write(yaml.dump(self,Dumper=NiceDumper,**kwargs)) + except TypeError: # compatibility with old pyyaml + del kwargs['sort_keys'] + fhandle.write(yaml.dump(self,Dumper=NiceDumper,**kwargs)) @property diff --git a/python/damask/_configmaterial.py b/python/damask/_configmaterial.py index a7ecec108..6de2283f4 100644 --- a/python/damask/_configmaterial.py +++ b/python/damask/_configmaterial.py @@ -2,10 +2,9 @@ import copy import numpy as np -from . import grid_filters from . import Config -from . import Lattice from . import Rotation +from . import Orientation class ConfigMaterial(Config): """Material configuration.""" @@ -25,8 +24,22 @@ class ConfigMaterial(Config): super().save(fname,**kwargs) + @classmethod + def load(cls,fname='material.yaml'): + """ + Load from yaml file. + + Parameters + ---------- + fname : file, str, or pathlib.Path, optional + Filename or file for writing. Defaults to 'material.yaml'. + + """ + return super(ConfigMaterial,cls).load(fname) + + @staticmethod - def from_table(table,coordinates=None,constituents={},**kwargs): + def from_table(table,constituents={},**kwargs): """ Load from an ASCII table. @@ -34,10 +47,6 @@ class ConfigMaterial(Config): ---------- table : damask.Table Table that contains material information. - coordinates : str, optional - Label of spatial coordiates. Used for sorting and performing a - sanity check. Default to None, in which case no sorting or checking is - peformed. constituents : dict, optional Entries for 'constituents'. The key is the name and the value specifies the label of the data column in the table @@ -54,7 +63,7 @@ class ConfigMaterial(Config): pos pos pos qu qu qu qu phase homog 0 0 0 0 0.19 0.8 0.24 -0.51 Aluminum SX 1 1 0 0 0.8 0.19 0.24 -0.51 Steel SX - >>> cm.from_table(t,'pos',{'O':'qu','phase':'phase'},homogenization='homog') + >>> cm.from_table(t,{'O':'qu','phase':'phase'},homogenization='homog') material: - constituents: - O: [0.19, 0.8, 0.24, -0.51] @@ -68,19 +77,14 @@ class ConfigMaterial(Config): homogenization: SX """ - if coordinates is not None: - t = table.sort_by([f'{i}_{coordinates}' for i in range(3,0,-1)]) - grid_filters.coord0_check(t.get(coordinates)) - else: - t = table - - constituents_ = {k:t.get(v) for k,v in constituents.items()} - kwargs_ = {k:t.get(v) for k,v in kwargs.items()} + constituents_ = {k:table.get(v) for k,v in constituents.items()} + kwargs_ = {k:table.get(v) for k,v in kwargs.items()} _,idx = np.unique(np.hstack(list({**constituents_,**kwargs_}.values())),return_index=True,axis=0) - constituents_ = {k:v[idx].squeeze() for k,v in constituents_.items()} - kwargs_ = {k:v[idx].squeeze() for k,v in kwargs_.items()} + idx = np.sort(idx) + constituents_ = {k:np.atleast_1d(v[idx].squeeze()) for k,v in constituents_.items()} + kwargs_ = {k:np.atleast_1d(v[idx].squeeze()) for k,v in kwargs_.items()} return ConfigMaterial().material_add(constituents_,**kwargs_) @@ -148,7 +152,7 @@ class ConfigMaterial(Config): for k,v in self['phase'].items(): if 'lattice' in v: try: - Lattice(v['lattice']) + Orientation(lattice=v['lattice']) except KeyError: s = v['lattice'] print(f"Invalid lattice: '{s}' in phase '{k}'") @@ -222,26 +226,25 @@ class ConfigMaterial(Config): return dup - def material_add(self,constituents,**kwargs): + def material_add(self,constituents=None,**kwargs): """ Add material entries. Parameters ---------- - constituents : dict - Entries for 'constituents'. The key is the name and the value specifies - the label of the data column in the table + constituents : dict, optional + Entries for 'constituents' as key-value pair. **kwargs - Keyword arguments where the key is the name and the value specifies - the label of the data column in the table + Key-value pairs. Examples -------- >>> import damask - >>> m = damask.ConfigMaterial() >>> O = damask.Rotation.from_random(3).as_quaternion() >>> phase = ['Aluminum','Steel','Aluminum'] - >>> m.material_add(constituents={'phase':phase,'O':O},homogenization='SX') + >>> m = damask.ConfigMaterial().material_add(constituents={'phase':phase,'O':O}, + ... homogenization='SX') + >>> m material: - constituents: - O: [0.577764, -0.146299, -0.617669, 0.513010] @@ -260,19 +263,31 @@ class ConfigMaterial(Config): homogenization: SX """ - c = [{'constituents':u} for u in ConfigMaterial._constituents(**constituents)] + length = -1 + for v in kwargs.values(): + if hasattr(v,'__len__') and not isinstance(v,str): + if length != -1 and len(v) != length: + raise ValueError('Cannot add entries of different length') + else: + length = len(v) + length = max(1,length) + + c = [{} for _ in range(length)] if constituents is None else \ + [{'constituents':u} for u in ConfigMaterial._constituents(**constituents)] + if len(c) == 1: c = [copy.deepcopy(c[0]) for _ in range(length)] + + if length != 1 and length != len(c): + raise ValueError('Cannot add entries of different length') + for k,v in kwargs.items(): if hasattr(v,'__len__') and not isinstance(v,str): - if len(v) != len(c): - raise ValueError('len mismatch 1') for i,vv in enumerate(v): - c[i][k] = [w.item() for w in vv] if isinstance(vv,np.ndarray) else vv.item() + c[i][k] = vv.item() if isinstance(vv,np.generic) else vv else: for i in range(len(c)): c[i][k] = v dup = copy.deepcopy(self) - if 'material' not in dup: dup['material'] = [] - dup['material'] +=c + dup['material'] = dup['material'] + c if 'material' in dup else c return dup @@ -280,6 +295,7 @@ class ConfigMaterial(Config): @staticmethod def _constituents(N=1,**kwargs): """Construct list of constituents.""" + N_material=1 for v in kwargs.values(): if hasattr(v,'__len__') and not isinstance(v,str): N_material = len(v) @@ -288,9 +304,9 @@ class ConfigMaterial(Config): for k,v in kwargs.items(): if hasattr(v,'__len__') and not isinstance(v,str): if len(v) != N_material: - raise ValueError('len mismatch 2') + raise ValueError('Cannot add entries of different length') for i,vv in enumerate(np.array(v)): - m[i][0][k] = [w.item() for w in vv] if isinstance(vv,np.ndarray) else vv.item() + m[i][0][k] = vv.item() if isinstance(vv,np.generic) else vv else: for i in range(N_material): m[i][0][k] = v diff --git a/python/damask/_environment.py b/python/damask/_environment.py index d4b7abe51..7d93b83e0 100644 --- a/python/damask/_environment.py +++ b/python/damask/_environment.py @@ -3,14 +3,8 @@ from pathlib import Path class Environment: - def __init__(self): - """Do Nothing.""" - pass - @property def screen_size(self): - width = 1024 - height = 768 try: import wx _ = wx.App(False) # noqa @@ -23,7 +17,9 @@ class Environment: height = tk.winfo_screenheight() tk.destroy() except Exception as e: - pass + width = 1024 + height = 768 + return (width,height) @@ -32,7 +28,7 @@ class Environment: options = {} for item in ['DAMASK_NUM_THREADS', 'MSC_ROOT', - 'MARC_VERSION', + 'MSC_VERSION', ]: options[item] = os.environ[item] if item in os.environ else None @@ -43,8 +39,3 @@ class Environment: def root_dir(self): """Return DAMASK root path.""" return Path(__file__).parents[2] - - - # for compatibility - def rootDir(self): - return str(self.root_dir) diff --git a/python/damask/_geom.py b/python/damask/_geom.py index 784fd3139..fece522c3 100644 --- a/python/damask/_geom.py +++ b/python/damask/_geom.py @@ -2,8 +2,10 @@ import copy import multiprocessing as mp from functools import partial from os import path +import warnings import numpy as np +import pandas as pd import h5py from scipy import ndimage,spatial @@ -19,7 +21,7 @@ class Geom: def __init__(self,material,size,origin=[0.0,0.0,0.0],comments=[]): """ - New geometry definition from array of material, size, and origin. + New geometry definition from array of materials, size, and origin. Parameters ---------- @@ -33,28 +35,10 @@ class Geom: Comment lines. """ - if len(material.shape) != 3: - raise ValueError(f'Invalid material shape {material.shape}.') - elif material.dtype not in np.sctypes['float'] + np.sctypes['int']: - raise TypeError(f'Invalid material data type {material.dtype}.') - else: - self.material = np.copy(material) - - if self.material.dtype in np.sctypes['float'] and \ - np.all(self.material == self.material.astype(int).astype(float)): - self.material = self.material.astype(int) - - if len(size) != 3 or any(np.array(size) <= 0): - raise ValueError(f'Invalid size {size}.') - else: - self.size = np.array(size) - - if len(origin) != 3: - raise ValueError(f'Invalid origin {origin}.') - else: - self.origin = np.array(origin) - - self.comments = [str(c) for c in comments] if isinstance(comments,list) else [str(comments)] + self.material = material + self.size = size + self.origin = origin + self.comments = comments def __repr__(self): @@ -90,35 +74,90 @@ class Geom: """ message = [] if np.any(other.grid != self.grid): - message.append(util.delete(f'grid a b c: {util.srepr(other.grid," x ")}')) + message.append(util.deemph(f'grid a b c: {util.srepr(other.grid," x ")}')) message.append(util.emph( f'grid a b c: {util.srepr( self.grid," x ")}')) if not np.allclose(other.size,self.size): - message.append(util.delete(f'size x y z: {util.srepr(other.size," x ")}')) + message.append(util.deemph(f'size x y z: {util.srepr(other.size," x ")}')) message.append(util.emph( f'size x y z: {util.srepr( self.size," x ")}')) if not np.allclose(other.origin,self.origin): - message.append(util.delete(f'origin x y z: {util.srepr(other.origin," ")}')) + message.append(util.deemph(f'origin x y z: {util.srepr(other.origin," ")}')) message.append(util.emph( f'origin x y z: {util.srepr( self.origin," ")}')) if other.N_materials != self.N_materials: - message.append(util.delete(f'# materials: {other.N_materials}')) + message.append(util.deemph(f'# materials: {other.N_materials}')) message.append(util.emph( f'# materials: { self.N_materials}')) if np.nanmax(other.material) != np.nanmax(self.material): - message.append(util.delete(f'max material: {np.nanmax(other.material)}')) + message.append(util.deemph(f'max material: {np.nanmax(other.material)}')) message.append(util.emph( f'max material: {np.nanmax( self.material)}')) return util.return_message(message) + @property + def material(self): + """Material indices.""" + return self._material + + @material.setter + def material(self,material): + if len(material.shape) != 3: + raise ValueError(f'Invalid material shape {material.shape}.') + elif material.dtype not in np.sctypes['float'] + np.sctypes['int']: + raise TypeError(f'Invalid material data type {material.dtype}.') + else: + self._material = np.copy(material) + + if self.material.dtype in np.sctypes['float'] and \ + np.all(self.material == self.material.astype(int).astype(float)): + self._material = self.material.astype(int) + + + @property + def size(self): + """Physical size of geometry in meter.""" + return self._size + + @size.setter + def size(self,size): + if len(size) != 3 or any(np.array(size) <= 0): + raise ValueError(f'Invalid size {size}.') + else: + self._size = np.array(size) + + @property + def origin(self): + """Coordinates of geometry origin in meter.""" + return self._origin + + @origin.setter + def origin(self,origin): + if len(origin) != 3: + raise ValueError(f'Invalid origin {origin}.') + else: + self._origin = np.array(origin) + + @property + def comments(self): + """Comments/history of geometry.""" + return self._comments + + @comments.setter + def comments(self,comments): + self._comments = [str(c) for c in comments] if isinstance(comments,list) else [str(comments)] + + @property def grid(self): + """Grid dimension of geometry.""" return np.asarray(self.material.shape) @property def N_materials(self): + """Number of (unique) material indices within geometry.""" return np.unique(self.material).size @@ -131,7 +170,7 @@ class Geom: ---------- fname : str or or pathlib.Path Geometry file to read. - Valid extension is .vtr, it will be appended if not given. + Valid extension is .vtr, which will be appended if not given. """ v = VTK.load(fname if str(fname).endswith('.vtr') else str(fname)+'.vtr') @@ -150,12 +189,16 @@ class Geom: """ Read a geom file. + Storing geometry files in ASCII format is deprecated. + This function will be removed in a future version of DAMASK. + Parameters ---------- - fname : str or file handle + fname : str, pathlib.Path, or file handle Geometry file to read. """ + warnings.warn('Support for ASCII-based geom format will be removed in DAMASK 3.1.0', DeprecationWarning) try: f = open(fname) except TypeError: @@ -170,9 +213,8 @@ class Geom: if not keyword.startswith('head') or header_length < 3: raise TypeError('Header length information missing or invalid') - content = f.readlines() - comments = [] + content = f.readlines() for i,line in enumerate(content[:header_length]): items = line.split('#')[0].lower().strip().split() key = items[0] if items else '' @@ -185,7 +227,7 @@ class Geom: else: comments.append(line.strip()) - material = np.empty(grid.prod()) # initialize as flat array + material = np.empty(grid.prod()) # initialize as flat array i = 0 for line in content[header_length:]: items = line.split('#')[0].split() @@ -203,13 +245,12 @@ class Geom: if i != grid.prod(): raise TypeError(f'Invalid file: expected {grid.prod()} entries, found {i}') - if not np.any(np.mod(material,1) != 0.0): # no float present - material = material.astype('int') + if not np.any(np.mod(material,1) != 0.0): # no float present + material = material.astype('int') - (1 if material.min() > 0 else 0) return Geom(material.reshape(grid,order='F'),size,origin,comments) - @staticmethod def load_DREAM3D(fname,base_group,point_data=None,material='FeatureIds'): """ @@ -220,26 +261,26 @@ class Geom: fname : str Filename of the DREAM.3D file base_group : str - Name of the group (folder) below 'DataContainers'. For example - 'SyntheticVolumeDataContainer'. + Name of the group (folder) below 'DataContainers', + for example 'SyntheticVolumeDataContainer'. point_data : str, optional - Name of the group (folder) containing the point wise material data, - for example 'CellData'. Defaults to None, in which case points consecutively numbered. + Name of the group (folder) containing the pointwise material data, + for example 'CellData'. Defaults to None, in which case points are consecutively numbered. material : str, optional - Name of the dataset containing the material ID. Defaults to - 'FeatureIds'. + Name of the dataset containing the material ID. + Defaults to 'FeatureIds'. """ root_dir ='DataContainers' f = h5py.File(fname, 'r') g = path.join(root_dir,base_group,'_SIMPL_GEOMETRY') - size = f[path.join(g,'DIMENSIONS')][()] * f[path.join(g,'SPACING')][()] grid = f[path.join(g,'DIMENSIONS')][()] + size = f[path.join(g,'SPACING')][()] * grid origin = f[path.join(g,'ORIGIN')][()] - group_pointwise = path.join(root_dir,base_group,point_data) - ma = np.arange(1,np.product(grid)+1,dtype=int) if point_data is None else \ - np.reshape(f[path.join(group_pointwise,material)],grid.prod()) + ma = np.arange(grid.prod(),dtype=int) \ + if point_data is None else \ + np.reshape(f[path.join(root_dir,base_group,point_data,material)],grid.prod()) return Geom(ma.reshape(grid,order='F'),size,origin,util.execution_stamp('Geom','load_DREAM3D')) @@ -247,28 +288,29 @@ class Geom: @staticmethod def from_table(table,coordinates,labels): """ - Load an ASCII table. + Derive geometry from an ASCII table. Parameters ---------- table : damask.Table Table that contains material information. coordinates : str - Label of the column containing the spatial coordinates. + Label of the vector column containing the spatial coordinates. + Need to be ordered (1./x fast, 3./z slow). labels : str or list of str Label(s) of the columns containing the material definition. - Each unique combintation of values results in a material. + Each unique combintation of values results in one material ID. """ - t = table.sort_by([f'{i}_{coordinates}' for i in range(3,0,-1)]) - - grid,size,origin = grid_filters.cell_coord0_gridSizeOrigin(t.get(coordinates)) + grid,size,origin = grid_filters.cell_coord0_gridSizeOrigin(table.get(coordinates)) labels_ = [labels] if isinstance(labels,str) else labels - _,unique_inverse = np.unique(np.hstack([t.get(l) for l in labels_]),return_inverse=True,axis=0) - ma = unique_inverse.reshape(grid,order='F') + 1 + unique,unique_inverse = np.unique(np.hstack([table.get(l) for l in labels_]),return_inverse=True,axis=0) - return Geom(ma,size,origin,util.execution_stamp('Geom','from_table')) + ma = np.arange(grid.prod()) if len(unique) == grid.prod() else \ + np.arange(unique.size)[np.argsort(pd.unique(unique_inverse))][unique_inverse] + + return Geom(ma.reshape(grid,order='F'),size,origin,util.execution_stamp('Geom','from_table')) @staticmethod @@ -291,8 +333,8 @@ class Geom: weights : numpy.ndarray of shape (seeds.shape[0]) Weights of the seeds. Setting all weights to 1.0 gives a standard Voronoi tessellation. material : numpy.ndarray of shape (seeds.shape[0]), optional - Material ID of the seeds. Defaults to None, in which case materials are - consecutively numbered. + Material ID of the seeds. + Defaults to None, in which case materials are consecutively numbered. periodic : Boolean, optional Perform a periodic tessellation. Defaults to True. @@ -340,8 +382,8 @@ class Geom: seeds : numpy.ndarray of shape (:,3) Position of the seed points in meter. All points need to lay within the box. material : numpy.ndarray of shape (seeds.shape[0]), optional - Material ID of the seeds. Defaults to None, in which case materials are - consecutively numbered. + Material ID of the seeds. + Defaults to None, in which case materials are consecutively numbered. periodic : Boolean, optional Perform a periodic tessellation. Defaults to True. @@ -417,7 +459,7 @@ class Geom: Number of periods per unit cell. Defaults to 1. materials : (int, int), optional Material IDs. Defaults to (1,2). - + Notes ----- The following triply-periodic minimal surfaces are implemented: @@ -436,19 +478,19 @@ class Geom: References ---------- - Surface curvature in triply-periodic minimal surface architectures as - a distinct design parameter in preparing advanced tissue engineering scaffolds Sébastien B G Blanquer, Maike Werner, Markus Hannula, Shahriar Sharifi, Guillaume P R Lajoinie, David Eglin, Jari Hyttinen, André A Poot, and Dirk W Grijpma - 10.1088/1758-5090/aa6553 + Surface curvature in triply-periodic minimal surface architectures as + a distinct design parameter in preparing advanced tissue engineering scaffolds + https://doi.org/10.1088/1758-5090/aa6553 - Triply Periodic Bicontinuous Cubic Microdomain Morphologies by Symmetries Meinhard Wohlgemuth, Nataliya Yufa, James Hoffman, and Edwin L. Thomas - 10.1021/ma0019499 + Triply Periodic Bicontinuous Cubic Microdomain Morphologies by Symmetries + https://doi.org/10.1021/ma0019499 - Minisurf – A minimal surface generator for finite element modeling and additive manufacturing Meng-Ting Hsieh, Lorenzo Valdevit - 10.1016/j.simpa.2020.100026 + Minisurf – A minimal surface generator for finite element modeling and additive manufacturing + https://doi.org/10.1016/j.simpa.2020.100026 """ x,y,z = np.meshgrid(periods*2.0*np.pi*(np.arange(grid[0])+0.5)/grid[0], @@ -463,17 +505,17 @@ class Geom: def save(self,fname,compress=True): """ - Store as vtk rectilinear grid. + Store as VTK rectilinear grid. Parameters ---------- - fname : str or or pathlib.Path + fname : str or pathlib.Path Filename to write. Valid extension is .vtr, it will be appended if not given. compress : bool, optional Compress with zlib algorithm. Defaults to True. """ - v = VTK.from_rectilinearGrid(self.grid,self.size,self.origin) + v = VTK.from_rectilinear_grid(self.grid,self.size,self.origin) v.add(self.material.flatten(order='F'),'material') v.add_comments(self.comments) @@ -484,6 +526,9 @@ class Geom: """ Write a geom file. + Storing geometry files in ASCII format is deprecated. + This function will be removed in a future version of DAMASK. + Parameters ---------- fname : str or file handle @@ -492,6 +537,7 @@ class Geom: Compress geometry with 'x of y' and 'a to b'. """ + warnings.warn('Support for ASCII-based geom format will be removed in DAMASK 3.1.0', DeprecationWarning) header = [f'{len(self.comments)+4} header'] + self.comments \ + ['grid a {} b {} c {}'.format(*self.grid), 'size x {} y {} z {}'.format(*self.size), @@ -508,8 +554,7 @@ class Geom: def show(self): """Show on screen.""" - v = VTK.from_rectilinearGrid(self.grid,self.size,self.origin) - v.show() + VTK.from_rectilinear_grid(self.grid,self.size,self.origin).show() def add_primitive(self,dimension,center,exponent, @@ -519,20 +564,20 @@ class Geom: Parameters ---------- - dimension : int or float numpy.ndarray of shape(3) + dimension : int or float numpy.ndarray of shape (3) Dimension (diameter/side length) of the primitive. If given as integers, grid point locations (cell centers) are addressed. If given as floats, coordinates are addressed. - center : int or float numpy.ndarray of shape(3) + center : int or float numpy.ndarray of shape (3) Center of the primitive. If given as integers, grid point - locations (cell centers) are addressed. - If given as floats, coordinates are addressed. - exponent : numpy.ndarray of shape(3) or float + coordinates (cell centers) are addressed. + If given as floats, coordinates in space are addressed. + exponent : numpy.ndarray of shape (3) or float Exponents for the three axes. 0 gives octahedron (ǀxǀ^(2^0) + ǀyǀ^(2^0) + ǀzǀ^(2^0) < 1) 1 gives sphere (ǀxǀ^(2^1) + ǀyǀ^(2^1) + ǀzǀ^(2^1) < 1) fill : int, optional - Fill value for primitive. Defaults to material.max() + 1. + Fill value for primitive. Defaults to material.max()+1. R : damask.Rotation, optional Rotation of primitive. Defaults to no rotation. inverse : Boolean, optional @@ -542,25 +587,27 @@ class Geom: Repeat primitive over boundaries. Defaults to True. """ - # normalized 'radius' and center - r = np.array(dimension)/self.grid/2.0 if np.array(dimension).dtype in np.sctypes['int'] else \ - np.array(dimension)/self.size/2.0 - c = (np.array(center) + .5)/self.grid if np.array(center).dtype in np.sctypes['int'] else \ - (np.array(center) - self.origin)/self.size + # radius and center + r = np.array(dimension)/2.0*self.size/self.grid if np.array(dimension).dtype in np.sctypes['int'] else \ + np.array(dimension)/2.0 + c = (np.array(center) + .5)*self.size/self.grid if np.array(center).dtype in np.sctypes['int'] else \ + (np.array(center) - self.origin) - coords = grid_filters.cell_coord0(self.grid,np.ones(3)) \ - - ((np.ones(3)-(1./self.grid if np.array(center).dtype in np.sctypes['int'] else 0))*0.5 if periodic else c) # periodic center is always at CoG + coords = grid_filters.cell_coord0(self.grid,self.size, + -(0.5*(self.size + (self.size/self.grid + if np.array(center).dtype in np.sctypes['int'] else + 0)) if periodic else c)) coords_rot = R.broadcast_to(tuple(self.grid))@coords with np.errstate(all='ignore'): mask = np.sum(np.power(coords_rot/r,2.0**np.array(exponent)),axis=-1) > 1.0 if periodic: # translate back to center - mask = np.roll(mask,((c-np.ones(3)*.5)*self.grid).astype(int),(0,1,2)) + mask = np.roll(mask,((c/self.size-0.5)*self.grid).round().astype(int),(0,1,2)) - fill_ = np.full_like(self.material,np.nanmax(self.material)+1 if fill is None else fill) - - return Geom(material = np.where(np.logical_not(mask) if inverse else mask, self.material,fill_), + return Geom(material = np.where(np.logical_not(mask) if inverse else mask, + self.material, + np.nanmax(self.material)+1 if fill is None else fill), size = self.size, origin = self.origin, comments = self.comments+[util.execution_stamp('Geom','add_primitive')], @@ -687,12 +734,10 @@ class Geom: def renumber(self): - """Renumber sorted material indices to 1,...,N.""" - renumbered = np.empty(self.grid,dtype=self.material.dtype) - for i, oldID in enumerate(np.unique(self.material)): - renumbered = np.where(self.material == oldID, i+1, renumbered) + """Renumber sorted material indices as 0,...,N-1.""" + _,renumbered = np.unique(self.material,return_inverse=True) - return Geom(material = renumbered, + return Geom(material = renumbered.reshape(self.grid), size = self.size, origin = self.origin, comments = self.comments+[util.execution_stamp('Geom','renumber')], @@ -714,7 +759,7 @@ class Geom: if fill is None: fill = np.nanmax(self.material) + 1 dtype = float if np.isnan(fill) or int(fill) != fill or self.material.dtype==np.float else int - Eulers = R.as_Eulers(degrees=True) + Eulers = R.as_Euler_angles(degrees=True) material_in = self.material.copy() # These rotations are always applied in the reference coordinate system, i.e. (z,x,z) not (z,x',z'') @@ -783,17 +828,33 @@ class Geom: New material indices. """ - substituted = self.material.copy() - for from_ms,to_ms in zip(from_material,to_material): - substituted[self.material==from_ms] = to_ms + def mp(entry,mapper): + return mapper[entry] if entry in mapper else entry - return Geom(material = substituted, + mp = np.vectorize(mp) + mapper = dict(zip(from_material,to_material)) + + return Geom(material = mp(self.material,mapper).reshape(self.grid), size = self.size, origin = self.origin, comments = self.comments+[util.execution_stamp('Geom','substitute')], ) + def sort(self): + """Sort material indices such that min(material) is located at (0,0,0).""" + a = self.material.flatten(order='F') + from_ma = pd.unique(a) + sort_idx = np.argsort(from_ma) + ma = np.unique(a)[sort_idx][np.searchsorted(from_ma,a,sorter = sort_idx)] + + return Geom(material = ma.reshape(self.grid,order='F'), + size = self.size, + origin = self.origin, + comments = self.comments+[util.execution_stamp('Geom','sort')], + ) + + def vicinity_offset(self,vicinity=1,offset=None,trigger=[],periodic=True): """ Offset material index of points in the vicinity of xxx. @@ -809,7 +870,7 @@ class Geom: Defaults to 1. offset : int, optional Offset (positive or negative) to tag material indices, - defaults to material.max() + 1. + defaults to material.max()+1. trigger : list of ints, optional List of material indices that trigger a change. Defaults to [], meaning that any different neighbor triggers a change. @@ -820,15 +881,11 @@ class Geom: def tainted_neighborhood(stencil,trigger): me = stencil[stencil.shape[0]//2] - if len(trigger) == 0: - return np.any(stencil != me) - if me in trigger: - trigger = set(trigger) - trigger.remove(me) - trigger = list(trigger) - return np.any(np.in1d(stencil,np.array(trigger))) + return np.any(stencil != me + if len(trigger) == 0 else + np.in1d(stencil,np.array(list(set(trigger) - {me})))) - offset_ = np.nanmax(self.material) if offset is None else offset + offset_ = np.nanmax(self.material)+1 if offset is None else offset mask = ndimage.filters.generic_filter(self.material, tainted_neighborhood, size=1+2*vicinity, @@ -850,28 +907,31 @@ class Geom: ---------- periodic : bool, optional Show boundaries across periodicity. Defaults to True. - direction : string, optional - Directions to consider ('x','y','z'). Defaults to 'xyz'. + directions : iterable containing str, optional + Direction(s) along which the geometry is mirrored. + Valid entries are 'x', 'y', 'z'. Defaults to 'xyz'. """ + valid = ['x','y','z'] + if not set(directions).issubset(valid): + raise ValueError(f'Invalid direction {set(directions).difference(valid)} specified.') - # offset (for connectivity) o = [[0, self.grid[0]+1, np.prod(self.grid[:2]+1)+self.grid[0]+1, np.prod(self.grid[:2]+1)], [0, np.prod(self.grid[:2]+1), np.prod(self.grid[:2]+1)+1, 1], - [0, 1, self.grid[0]+1+1, self.grid[0]+1]] - + [0, 1, self.grid[0]+1+1, self.grid[0]+1]] # offset for connectivity + connectivity = [] for i,d in enumerate(['x','y','z']): - if d not in directions.lower(): continue + if d not in directions: continue mask = self.material != np.roll(self.material,1,i) for j in [0,1,2]: mask = np.concatenate((mask,np.take(mask,[0],j)*(i==j)),j) if i == 0 and not periodic: mask[0,:,:] = mask[-1,:,:] = False if i == 1 and not periodic: mask[:,0,:] = mask[:,-1,:] = False if i == 2 and not periodic: mask[:,:,0] = mask[:,:,-1] = False - + base_nodes = np.argwhere(mask.flatten(order='F')).reshape(-1,1) connectivity.append(np.block([base_nodes + o[i][k] for k in range(4)])) - + coords = grid_filters.node_coord0(self.grid,self.size,self.origin).reshape(-1,3,order='F') - return VTK.from_unstructuredGrid(coords,np.vstack(connectivity),'QUAD') + return VTK.from_unstructured_grid(coords,np.vstack(connectivity),'QUAD') diff --git a/python/damask/_lattice.py b/python/damask/_lattice.py deleted file mode 100644 index 143fa50f1..000000000 --- a/python/damask/_lattice.py +++ /dev/null @@ -1,646 +0,0 @@ -import numpy as np - - -class Symmetry: - """ - Symmetry-related operations for crystal systems. - - References - ---------- - https://en.wikipedia.org/wiki/Crystal_system - - """ - - crystal_systems = [None,'orthorhombic','tetragonal','hexagonal','cubic'] - - def __init__(self, system = None): - """ - Symmetry Definition. - - Parameters - ---------- - system : {None,'orthorhombic','tetragonal','hexagonal','cubic'}, optional - Name of the crystal system. Defaults to 'None'. - - """ - if system is not None and system.lower() not in self.crystal_systems: - raise KeyError(f'Crystal system "{system}" is unknown') - - self.system = system.lower() if isinstance(system,str) else system - - - def __copy__(self): - """Copy.""" - return self.__class__(self.system) - - copy = __copy__ - - - def __repr__(self): - """Readable string.""" - return f'{self.system}' - - - def __eq__(self, other): - """ - Equal to other. - - Parameters - ---------- - other : Symmetry - Symmetry to check for equality. - - """ - return self.system == other.system - - def __neq__(self, other): - """ - Not Equal to other. - - Parameters - ---------- - other : Symmetry - Symmetry to check for inequality. - - """ - return not self.__eq__(other) - - def __cmp__(self,other): - """ - Linear ordering. - - Parameters - ---------- - other : Symmetry - Symmetry to check for for order. - - """ - myOrder = self.crystal_systems.index(self.system) - otherOrder = self.crystal_systems.index(other.system) - return (myOrder > otherOrder) - (myOrder < otherOrder) - - - @property - def symmetry_operations(self): - """Symmetry operations as quaternions.""" - if self.system == 'cubic': - sym_quats = [ - [ 1.0, 0.0, 0.0, 0.0 ], - [ 0.0, 1.0, 0.0, 0.0 ], - [ 0.0, 0.0, 1.0, 0.0 ], - [ 0.0, 0.0, 0.0, 1.0 ], - [ 0.0, 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2) ], - [ 0.0, 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2) ], - [ 0.0, 0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2) ], - [ 0.0, 0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2) ], - [ 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], - [ 0.0, -0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], - [ 0.5, 0.5, 0.5, 0.5 ], - [-0.5, 0.5, 0.5, 0.5 ], - [-0.5, 0.5, 0.5, -0.5 ], - [-0.5, 0.5, -0.5, 0.5 ], - [-0.5, -0.5, 0.5, 0.5 ], - [-0.5, -0.5, 0.5, -0.5 ], - [-0.5, -0.5, -0.5, 0.5 ], - [-0.5, 0.5, -0.5, -0.5 ], - [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - [-0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2), 0.0 ], - [-0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2), 0.0 ], - [-0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0, 0.0 ], - [-0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0, 0.0 ], - ] - elif self.system == 'hexagonal': - sym_quats = [ - [ 1.0, 0.0, 0.0, 0.0 ], - [-0.5*np.sqrt(3), 0.0, 0.0, -0.5 ], - [ 0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], - [ 0.0, 0.0, 0.0, 1.0 ], - [-0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], - [-0.5*np.sqrt(3), 0.0, 0.0, 0.5 ], - [ 0.0, 1.0, 0.0, 0.0 ], - [ 0.0, -0.5*np.sqrt(3), 0.5, 0.0 ], - [ 0.0, 0.5, -0.5*np.sqrt(3), 0.0 ], - [ 0.0, 0.0, 1.0, 0.0 ], - [ 0.0, -0.5, -0.5*np.sqrt(3), 0.0 ], - [ 0.0, 0.5*np.sqrt(3), 0.5, 0.0 ], - ] - elif self.system == 'tetragonal': - sym_quats = [ - [ 1.0, 0.0, 0.0, 0.0 ], - [ 0.0, 1.0, 0.0, 0.0 ], - [ 0.0, 0.0, 1.0, 0.0 ], - [ 0.0, 0.0, 0.0, 1.0 ], - [ 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], - [ 0.0, -0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], - [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - ] - elif self.system == 'orthorhombic': - sym_quats = [ - [ 1.0,0.0,0.0,0.0 ], - [ 0.0,1.0,0.0,0.0 ], - [ 0.0,0.0,1.0,0.0 ], - [ 0.0,0.0,0.0,1.0 ], - ] - else: - sym_quats = [ - [ 1.0,0.0,0.0,0.0 ], - ] - return np.array(sym_quats) - - - def in_FZ(self,rho): - """ - Check whether given Rodrigues-Frank vector falls into fundamental zone. - - Fundamental zone in Rodrigues space is point symmetric around origin. - """ - if(rho.shape[-1] != 3): - raise ValueError('Input is not a Rodrigues-Frank vector field.') - - rho_abs = np.abs(rho) - - with np.errstate(invalid='ignore'): - # using '*'/prod for 'and' - if self.system == 'cubic': - return np.where(np.prod(np.sqrt(2)-1. >= rho_abs,axis=-1) * - (1. >= np.sum(rho_abs,axis=-1)),True,False) - elif self.system == 'hexagonal': - return np.where(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]),True,False) - elif self.system == 'tetragonal': - return np.where(np.prod(1. >= rho_abs[...,:2],axis=-1) * - (np.sqrt(2) >= rho_abs[...,0] + rho_abs[...,1]) * - (np.sqrt(2) >= rho_abs[...,2] + 1.),True,False) - elif self.system == 'orthorhombic': - return np.where(np.prod(1. >= rho_abs,axis=-1),True,False) - else: - return np.where(np.all(np.isfinite(rho_abs),axis=-1),True,False) - - - def in_disorientation_SST(self,rho): - """ - Check whether given Rodrigues-Frank vector (of misorientation) falls into standard stereographic triangle. - - References - ---------- - A. Heinz and P. Neumann, Acta Crystallographica Section A 47:780-789, 1991 - https://doi.org/10.1107/S0108767391006864 - - """ - if(rho.shape[-1] != 3): - raise ValueError('Input is not a Rodrigues-Frank vector field.') - - with np.errstate(invalid='ignore'): - # using '*' for 'and' - if self.system == 'cubic': - return np.where((rho[...,0] >= rho[...,1]) * \ - (rho[...,1] >= rho[...,2]) * \ - (rho[...,2] >= 0),True,False) - elif self.system == 'hexagonal': - return np.where((rho[...,0] >= rho[...,1]*np.sqrt(3)) * \ - (rho[...,1] >= 0) * \ - (rho[...,2] >= 0),True,False) - elif self.system == 'tetragonal': - return np.where((rho[...,0] >= rho[...,1]) * \ - (rho[...,1] >= 0) * \ - (rho[...,2] >= 0),True,False) - elif self.system == 'orthorhombic': - return np.where((rho[...,0] >= 0) * \ - (rho[...,1] >= 0) * \ - (rho[...,2] >= 0),True,False) - else: - return np.ones_like(rho[...,0],dtype=bool) - - - #ToDo: IPF color in separate function - def in_SST(self,vector,proper=False,color=False): - """ - Check whether given vector falls into standard stereographic triangle of own symmetry. - - proper considers only vectors with z >= 0, hence uses two neighboring SSTs. - Return inverse pole figure color if requested. - Bases are computed from - - >>> basis = {'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,1.]/np.sqrt(2.), # direction of green - ... [1.,1.,1.]/np.sqrt(3.)]).T), # direction of blue - ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # direction of green - ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # direction of blue - ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # direction of green - ... [1.,1.,0.]/np.sqrt(2.)]).T), # direction of blue - ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # direction of green - ... [0.,1.,0.]]).T), # direction of blue - ... } - - """ - if(vector.shape[-1] != 3): - raise ValueError('Input is not a 3D vector field.') - - if self.system == 'cubic': - basis = {'improper':np.array([ [-1. , 0. , 1. ], - [ np.sqrt(2.) , -np.sqrt(2.) , 0. ], - [ 0. , np.sqrt(3.) , 0. ] ]), - 'proper':np.array([ [ 0. , -1. , 1. ], - [-np.sqrt(2.) , np.sqrt(2.) , 0. ], - [ np.sqrt(3.) , 0. , 0. ] ]), - } - elif self.system == 'hexagonal': - basis = {'improper':np.array([ [ 0. , 0. , 1. ], - [ 1. , -np.sqrt(3.) , 0. ], - [ 0. , 2. , 0. ] ]), - 'proper':np.array([ [ 0. , 0. , 1. ], - [-1. , np.sqrt(3.) , 0. ], - [ np.sqrt(3.) , -1. , 0. ] ]), - } - elif self.system == 'tetragonal': - basis = {'improper':np.array([ [ 0. , 0. , 1. ], - [ 1. , -1. , 0. ], - [ 0. , np.sqrt(2.) , 0. ] ]), - 'proper':np.array([ [ 0. , 0. , 1. ], - [-1. , 1. , 0. ], - [ np.sqrt(2.) , 0. , 0. ] ]), - } - elif self.system == 'orthorhombic': - basis = {'improper':np.array([ [ 0., 0., 1.], - [ 1., 0., 0.], - [ 0., 1., 0.] ]), - 'proper':np.array([ [ 0., 0., 1.], - [-1., 0., 0.], - [ 0., 1., 0.] ]), - } - else: # direct exit for unspecified symmetry - if color: - return (np.ones_like(vector[...,0],bool),np.zeros_like(vector)) - else: - return np.ones_like(vector[...,0],bool) - - - b_i = np.broadcast_to(basis['improper'],vector.shape+(3,)) - if proper: - b_p = np.broadcast_to(basis['proper'], vector.shape+(3,)) - improper = np.all(np.around(np.einsum('...ji,...i',b_i,vector),12)>=0.0,axis=-1,keepdims=True) - theComponents = np.where(np.broadcast_to(improper,vector.shape), - np.around(np.einsum('...ji,...i',b_i,vector),12), - np.around(np.einsum('...ji,...i',b_p,vector),12)) - else: - vector_ = np.block([vector[...,0:2],np.abs(vector[...,2:3])]) # z component projects identical - theComponents = np.around(np.einsum('...ji,...i',b_i,vector_),12) - - in_SST = np.all(theComponents >= 0.0,axis=-1) - - if color: # have to return color array - with np.errstate(invalid='ignore',divide='ignore'): - rgb = (theComponents/np.linalg.norm(theComponents,axis=-1,keepdims=True))**0.5 # smoothen color ramps - rgb = np.minimum(1.,rgb) # limit to maximum intensity - rgb /= np.max(rgb,axis=-1,keepdims=True) # normalize to (HS)V = 1 - rgb[np.broadcast_to(~in_SST.reshape(vector[...,0].shape+(1,)),vector.shape)] = 0.0 - return (in_SST,rgb) - else: - return in_SST - - -# ****************************************************************************************** -class Lattice: # ToDo: Make a subclass of Symmetry! - """ - Bravais lattice. - - This contains only a mapping from Bravais lattice to symmetry - and orientation relationships. It could include twin and slip systems. - - References - ---------- - https://en.wikipedia.org/wiki/Bravais_lattice - - """ - - lattices = { - 'iso': {'system':None}, - 'triclinic':{'system':None}, - 'bct': {'system':'tetragonal'}, - 'hex': {'system':'hexagonal'}, - 'fcc': {'system':'cubic','c/a':1.0}, - 'bcc': {'system':'cubic','c/a':1.0}, - } - - - def __init__(self,lattice,c_over_a=None): - """ - New lattice of given type. - - Parameters - ---------- - lattice : str - Bravais lattice. - - """ - self.lattice = lattice - self.symmetry = Symmetry(self.lattices[lattice]['system']) - - # transition to subclass - self.system = self.symmetry.system - self.in_SST = self.symmetry.in_SST - self.in_FZ = self.symmetry.in_FZ - self.in_disorientation_SST = self.symmetry.in_disorientation_SST - - def __repr__(self): - """Report basic lattice information.""" - return f'Bravais lattice {self.lattice} ({self.symmetry} crystal system)' - - - # Kurdjomov--Sachs orientation relationship for fcc <-> bcc transformation - # from S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013 - # also see K. Kitahara et al., Acta Materialia 54:1279-1288, 2006 - _KS = {'mapping':{'fcc':0,'bcc':1}, - 'planes': np.array([ - [[ 1, 1, 1],[ 0, 1, 1]], - [[ 1, 1, 1],[ 0, 1, 1]], - [[ 1, 1, 1],[ 0, 1, 1]], - [[ 1, 1, 1],[ 0, 1, 1]], - [[ 1, 1, 1],[ 0, 1, 1]], - [[ 1, 1, 1],[ 0, 1, 1]], - [[ 1, -1, 1],[ 0, 1, 1]], - [[ 1, -1, 1],[ 0, 1, 1]], - [[ 1, -1, 1],[ 0, 1, 1]], - [[ 1, -1, 1],[ 0, 1, 1]], - [[ 1, -1, 1],[ 0, 1, 1]], - [[ 1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, 1],[ 0, 1, 1]], - [[ -1, 1, 1],[ 0, 1, 1]], - [[ -1, 1, 1],[ 0, 1, 1]], - [[ -1, 1, 1],[ 0, 1, 1]], - [[ -1, 1, 1],[ 0, 1, 1]], - [[ -1, 1, 1],[ 0, 1, 1]], - [[ 1, 1, -1],[ 0, 1, 1]], - [[ 1, 1, -1],[ 0, 1, 1]], - [[ 1, 1, -1],[ 0, 1, 1]], - [[ 1, 1, -1],[ 0, 1, 1]], - [[ 1, 1, -1],[ 0, 1, 1]], - [[ 1, 1, -1],[ 0, 1, 1]]],dtype='float'), - 'directions': np.array([ - [[ -1, 0, 1],[ -1, -1, 1]], - [[ -1, 0, 1],[ -1, 1, -1]], - [[ 0, 1, -1],[ -1, -1, 1]], - [[ 0, 1, -1],[ -1, 1, -1]], - [[ 1, -1, 0],[ -1, -1, 1]], - [[ 1, -1, 0],[ -1, 1, -1]], - [[ 1, 0, -1],[ -1, -1, 1]], - [[ 1, 0, -1],[ -1, 1, -1]], - [[ -1, -1, 0],[ -1, -1, 1]], - [[ -1, -1, 0],[ -1, 1, -1]], - [[ 0, 1, 1],[ -1, -1, 1]], - [[ 0, 1, 1],[ -1, 1, -1]], - [[ 0, -1, 1],[ -1, -1, 1]], - [[ 0, -1, 1],[ -1, 1, -1]], - [[ -1, 0, -1],[ -1, -1, 1]], - [[ -1, 0, -1],[ -1, 1, -1]], - [[ 1, 1, 0],[ -1, -1, 1]], - [[ 1, 1, 0],[ -1, 1, -1]], - [[ -1, 1, 0],[ -1, -1, 1]], - [[ -1, 1, 0],[ -1, 1, -1]], - [[ 0, -1, -1],[ -1, -1, 1]], - [[ 0, -1, -1],[ -1, 1, -1]], - [[ 1, 0, 1],[ -1, -1, 1]], - [[ 1, 0, 1],[ -1, 1, -1]]],dtype='float')} - - # Greninger--Troiano orientation relationship for fcc <-> bcc transformation - # from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 - _GT = {'mapping':{'fcc':0,'bcc':1}, - 'planes': np.array([ - [[ 1, 1, 1],[ 1, 0, 1]], - [[ 1, 1, 1],[ 1, 1, 0]], - [[ 1, 1, 1],[ 0, 1, 1]], - [[ -1, -1, 1],[ -1, 0, 1]], - [[ -1, -1, 1],[ -1, -1, 0]], - [[ -1, -1, 1],[ 0, -1, 1]], - [[ -1, 1, 1],[ -1, 0, 1]], - [[ -1, 1, 1],[ -1, 1, 0]], - [[ -1, 1, 1],[ 0, 1, 1]], - [[ 1, -1, 1],[ 1, 0, 1]], - [[ 1, -1, 1],[ 1, -1, 0]], - [[ 1, -1, 1],[ 0, -1, 1]], - [[ 1, 1, 1],[ 1, 1, 0]], - [[ 1, 1, 1],[ 0, 1, 1]], - [[ 1, 1, 1],[ 1, 0, 1]], - [[ -1, -1, 1],[ -1, -1, 0]], - [[ -1, -1, 1],[ 0, -1, 1]], - [[ -1, -1, 1],[ -1, 0, 1]], - [[ -1, 1, 1],[ -1, 1, 0]], - [[ -1, 1, 1],[ 0, 1, 1]], - [[ -1, 1, 1],[ -1, 0, 1]], - [[ 1, -1, 1],[ 1, -1, 0]], - [[ 1, -1, 1],[ 0, -1, 1]], - [[ 1, -1, 1],[ 1, 0, 1]]],dtype='float'), - 'directions': np.array([ - [[ -5,-12, 17],[-17, -7, 17]], - [[ 17, -5,-12],[ 17,-17, -7]], - [[-12, 17, -5],[ -7, 17,-17]], - [[ 5, 12, 17],[ 17, 7, 17]], - [[-17, 5,-12],[-17, 17, -7]], - [[ 12,-17, -5],[ 7,-17,-17]], - [[ -5, 12,-17],[-17, 7,-17]], - [[ 17, 5, 12],[ 17, 17, 7]], - [[-12,-17, 5],[ -7,-17, 17]], - [[ 5,-12,-17],[ 17, -7,-17]], - [[-17, -5, 12],[-17,-17, 7]], - [[ 12, 17, 5],[ 7, 17, 17]], - [[ -5, 17,-12],[-17, 17, -7]], - [[-12, -5, 17],[ -7,-17, 17]], - [[ 17,-12, -5],[ 17, -7,-17]], - [[ 5,-17,-12],[ 17,-17, -7]], - [[ 12, 5, 17],[ 7, 17, 17]], - [[-17, 12, -5],[-17, 7,-17]], - [[ -5,-17, 12],[-17,-17, 7]], - [[-12, 5,-17],[ -7, 17,-17]], - [[ 17, 12, 5],[ 17, 7, 17]], - [[ 5, 17, 12],[ 17, 17, 7]], - [[ 12, -5,-17],[ 7,-17,-17]], - [[-17,-12, 5],[-17,-7, 17]]],dtype='float')} - - # Greninger--Troiano' orientation relationship for fcc <-> bcc transformation - # from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 - _GTprime = {'mapping':{'fcc':0,'bcc':1}, - 'planes': np.array([ - [[ 7, 17, 17],[ 12, 5, 17]], - [[ 17, 7, 17],[ 17, 12, 5]], - [[ 17, 17, 7],[ 5, 17, 12]], - [[ -7,-17, 17],[-12, -5, 17]], - [[-17, -7, 17],[-17,-12, 5]], - [[-17,-17, 7],[ -5,-17, 12]], - [[ 7,-17,-17],[ 12, -5,-17]], - [[ 17, -7,-17],[ 17,-12, -5]], - [[ 17,-17, -7],[ 5,-17,-12]], - [[ -7, 17,-17],[-12, 5,-17]], - [[-17, 7,-17],[-17, 12, -5]], - [[-17, 17, -7],[ -5, 17,-12]], - [[ 7, 17, 17],[ 12, 17, 5]], - [[ 17, 7, 17],[ 5, 12, 17]], - [[ 17, 17, 7],[ 17, 5, 12]], - [[ -7,-17, 17],[-12,-17, 5]], - [[-17, -7, 17],[ -5,-12, 17]], - [[-17,-17, 7],[-17, -5, 12]], - [[ 7,-17,-17],[ 12,-17, -5]], - [[ 17, -7,-17],[ 5, -12,-17]], - [[ 17,-17, -7],[ 17, -5,-12]], - [[ -7, 17,-17],[-12, 17, -5]], - [[-17, 7,-17],[ -5, 12,-17]], - [[-17, 17, -7],[-17, 5,-12]]],dtype='float'), - 'directions': np.array([ - [[ 0, 1, -1],[ 1, 1, -1]], - [[ -1, 0, 1],[ -1, 1, 1]], - [[ 1, -1, 0],[ 1, -1, 1]], - [[ 0, -1, -1],[ -1, -1, -1]], - [[ 1, 0, 1],[ 1, -1, 1]], - [[ 1, -1, 0],[ 1, -1, -1]], - [[ 0, 1, -1],[ -1, 1, -1]], - [[ 1, 0, 1],[ 1, 1, 1]], - [[ -1, -1, 0],[ -1, -1, 1]], - [[ 0, -1, -1],[ 1, -1, -1]], - [[ -1, 0, 1],[ -1, -1, 1]], - [[ -1, -1, 0],[ -1, -1, -1]], - [[ 0, -1, 1],[ 1, -1, 1]], - [[ 1, 0, -1],[ 1, 1, -1]], - [[ -1, 1, 0],[ -1, 1, 1]], - [[ 0, 1, 1],[ -1, 1, 1]], - [[ -1, 0, -1],[ -1, -1, -1]], - [[ -1, 1, 0],[ -1, 1, -1]], - [[ 0, -1, 1],[ -1, -1, 1]], - [[ -1, 0, -1],[ -1, 1, -1]], - [[ 1, 1, 0],[ 1, 1, 1]], - [[ 0, 1, 1],[ 1, 1, 1]], - [[ 1, 0, -1],[ 1, -1, -1]], - [[ 1, 1, 0],[ 1, 1, -1]]],dtype='float')} - - # Nishiyama--Wassermann orientation relationship for fcc <-> bcc transformation - # from H. Kitahara et al., Materials Characterization 54:378-386, 2005 - _NW = {'mapping':{'fcc':0,'bcc':1}, - 'planes': np.array([ - [[ 1, 1, 1],[ 0, 1, 1]], - [[ 1, 1, 1],[ 0, 1, 1]], - [[ 1, 1, 1],[ 0, 1, 1]], - [[ -1, 1, 1],[ 0, 1, 1]], - [[ -1, 1, 1],[ 0, 1, 1]], - [[ -1, 1, 1],[ 0, 1, 1]], - [[ 1, -1, 1],[ 0, 1, 1]], - [[ 1, -1, 1],[ 0, 1, 1]], - [[ 1, -1, 1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]]],dtype='float'), - 'directions': np.array([ - [[ 2, -1, -1],[ 0, -1, 1]], - [[ -1, 2, -1],[ 0, -1, 1]], - [[ -1, -1, 2],[ 0, -1, 1]], - [[ -2, -1, -1],[ 0, -1, 1]], - [[ 1, 2, -1],[ 0, -1, 1]], - [[ 1, -1, 2],[ 0, -1, 1]], - [[ 2, 1, -1],[ 0, -1, 1]], - [[ -1, -2, -1],[ 0, -1, 1]], - [[ -1, 1, 2],[ 0, -1, 1]], - [[ 2, -1, 1],[ 0, -1, 1]], #It is wrong in the paper, but matrix is correct - [[ -1, 2, 1],[ 0, -1, 1]], - [[ -1, -1, -2],[ 0, -1, 1]]],dtype='float')} - - # Pitsch orientation relationship for fcc <-> bcc transformation - # from Y. He et al., Acta Materialia 53:1179-1190, 2005 - _Pitsch = {'mapping':{'fcc':0,'bcc':1}, - 'planes': np.array([ - [[ 0, 1, 0],[ -1, 0, 1]], - [[ 0, 0, 1],[ 1, -1, 0]], - [[ 1, 0, 0],[ 0, 1, -1]], - [[ 1, 0, 0],[ 0, -1, -1]], - [[ 0, 1, 0],[ -1, 0, -1]], - [[ 0, 0, 1],[ -1, -1, 0]], - [[ 0, 1, 0],[ -1, 0, -1]], - [[ 0, 0, 1],[ -1, -1, 0]], - [[ 1, 0, 0],[ 0, -1, -1]], - [[ 1, 0, 0],[ 0, -1, 1]], - [[ 0, 1, 0],[ 1, 0, -1]], - [[ 0, 0, 1],[ -1, 1, 0]]],dtype='float'), - 'directions': np.array([ - [[ 1, 0, 1],[ 1, -1, 1]], - [[ 1, 1, 0],[ 1, 1, -1]], - [[ 0, 1, 1],[ -1, 1, 1]], - [[ 0, 1, -1],[ -1, 1, -1]], - [[ -1, 0, 1],[ -1, -1, 1]], - [[ 1, -1, 0],[ 1, -1, -1]], - [[ 1, 0, -1],[ 1, -1, -1]], - [[ -1, 1, 0],[ -1, 1, -1]], - [[ 0, -1, 1],[ -1, -1, 1]], - [[ 0, 1, 1],[ -1, 1, 1]], - [[ 1, 0, 1],[ 1, -1, 1]], - [[ 1, 1, 0],[ 1, 1, -1]]],dtype='float')} - - # Bain orientation relationship for fcc <-> bcc transformation - # from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 - _Bain = {'mapping':{'fcc':0,'bcc':1}, - 'planes': np.array([ - [[ 1, 0, 0],[ 1, 0, 0]], - [[ 0, 1, 0],[ 0, 1, 0]], - [[ 0, 0, 1],[ 0, 0, 1]]],dtype='float'), - 'directions': np.array([ - [[ 0, 1, 0],[ 0, 1, 1]], - [[ 0, 0, 1],[ 1, 0, 1]], - [[ 1, 0, 0],[ 1, 1, 0]]],dtype='float')} - - - def relation_operations(self,model): - """ - Crystallographic orientation relationships for phase transformations. - - References - ---------- - S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013 - https://doi.org/10.1016/j.jallcom.2012.02.004 - - K. Kitahara et al., Acta Materialia 54(5):1279-1288, 2006 - https://doi.org/10.1016/j.actamat.2005.11.001 - - Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 - https://doi.org/10.1107/S0021889805038276 - - H. Kitahara et al., Materials Characterization 54(4-5):378-386, 2005 - https://doi.org/10.1016/j.matchar.2004.12.015 - - Y. He et al., Acta Materialia 53(4):1179-1190, 2005 - https://doi.org/10.1016/j.actamat.2004.11.021 - - """ - models={'KS':self._KS, 'GT':self._GT, 'GT_prime':self._GTprime, - 'NW':self._NW, 'Pitsch': self._Pitsch, 'Bain':self._Bain} - try: - relationship = models[model] - except KeyError : - raise KeyError(f'Orientation relationship "{model}" is unknown') - - if self.lattice not in relationship['mapping']: - raise ValueError(f'Relationship "{model}" not supported for lattice "{self.lattice}"') - - r = {'lattice':Lattice((set(relationship['mapping'])-{self.lattice}).pop()), # target lattice - 'rotations':[] } - - myPlane_id = relationship['mapping'][self.lattice] - otherPlane_id = (myPlane_id+1)%2 - myDir_id = myPlane_id +2 - otherDir_id = otherPlane_id +2 - - for miller in np.hstack((relationship['planes'],relationship['directions'])): - myPlane = miller[myPlane_id]/ np.linalg.norm(miller[myPlane_id]) - myDir = miller[myDir_id]/ np.linalg.norm(miller[myDir_id]) - myMatrix = np.array([myDir,np.cross(myPlane,myDir),myPlane]) - - otherPlane = miller[otherPlane_id]/ np.linalg.norm(miller[otherPlane_id]) - otherDir = miller[otherDir_id]/ np.linalg.norm(miller[otherDir_id]) - otherMatrix = np.array([otherDir,np.cross(otherPlane,otherDir),otherPlane]) - - r['rotations'].append(np.dot(otherMatrix.T,myMatrix)) - - r['rotations'] = np.array(r['rotations']) - - return r diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 0bb0e1bc8..05301561f 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -1,202 +1,1250 @@ import numpy as np -from . import Lattice from . import Rotation +from . import util +from . import tensor -class Orientation: # ToDo: make subclass of lattice and Rotation? +_parameter_doc = \ + """lattice : str + Either a crystal family out of [triclinic, monoclinic, orthorhombic, tetragonal, hexagonal, cubic] + or a Bravais lattice out of [aP, mP, mS, oP, oS, oI, oF, tP, tI, hP, cP, cI, cF]. + When specifying a Bravais lattice, additional lattice parameters might be required: + 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. + + """ + + +class Orientation(Rotation): """ - Crystallographic orientation. + Representation of crystallographic orientation as combination of rotation and either crystal family or Bravais lattice. - A crystallographic orientation contains a rotation and a lattice. + The crystal family is one of Orientation.crystal_families: + + - triclinic + - monoclinic + - orthorhombic + - tetragonal + - hexagonal + - cubic + + and enables symmetry-related operations such as + "equivalent", "reduced", "disorientation", "IPF_color", or "to_SST". + + The Bravais lattice is one of Orientation.lattice_symmetries: + + - aP : triclinic primitive + - mP : monoclinic primitive + - mS : ... base-centered + - oP : orthorhombic primitive + - oS : ... base-centered + - oI : ... body-centered + - oF : ... face-centered + - tP : tetragonal primitive + - tI : ... body-centered + - hP : hexagonal primitive + - cP : cubic primitive + - cI : ... body-centered + - cF : ... face-centered + + and inherits the corresponding crystal family. + Specifying a Bravais lattice, compared to just the crystal family, + extends the functionality of Orientation objects to include operations such as + "Schmid", "related", or "to_pole" that require a lattice type and its parameters. + + Examples + -------- + An array of 3 x 5 random orientations reduced to the fundamental zone of tetragonal symmetry: + + >>> damask.Orientation.from_random(shape=(3,5),lattice='tetragonal').reduced """ - __slots__ = ['rotation','lattice'] + crystal_families = ['triclinic', + 'monoclinic', + 'orthorhombic', + 'tetragonal', + 'hexagonal', + 'cubic'] - def __repr__(self): - """Report lattice type and orientation.""" - return self.lattice.__repr__()+'\n'+self.rotation.__repr__() + lattice_symmetries = { + 'aP': 'triclinic', - def __init__(self, rotation, lattice): + 'mP': 'monoclinic', + 'mS': 'monoclinic', + + 'oP': 'orthorhombic', + 'oS': 'orthorhombic', + 'oI': 'orthorhombic', + 'oF': 'orthorhombic', + + 'tP': 'tetragonal', + 'tI': 'tetragonal', + + 'hP': 'hexagonal', + + 'cP': 'cubic', + 'cI': 'cubic', + 'cF': 'cubic', + } + + + @util.extend_docstring(_parameter_doc) + def __init__(self, + rotation = None, + lattice = None, + a = None,b = None,c = None, + alpha = None,beta = None,gamma = None, + degrees = False, + **kwargs): """ - New orientation from rotation and lattice. + Initialize orientation object. Parameters ---------- - rotation : Rotation - Rotation specifying the lattice orientation. - lattice : Lattice - Lattice type of the crystal. + rotation : list, numpy.ndarray, Rotation, optional + Unit quaternion in positive real hemisphere. + Use .from_quaternion to perform a sanity check. + Defaults to no rotation. """ - if isinstance(lattice, Lattice): + from damask.lattice import kinematics + + Rotation.__init__(self) if rotation is None else Rotation.__init__(self,rotation=rotation) + + if ( lattice is not None + and lattice not in self.lattice_symmetries + and lattice not in self.crystal_families): + raise KeyError(f'Lattice "{lattice}" is unknown') + + self.family = None + self.lattice = None + self.a = None + self.b = None + self.c = None + self.alpha = None + self.beta = None + self.gamma = None + self.kinematics = None + + if lattice in self.lattice_symmetries: + self.family = self.lattice_symmetries[lattice] self.lattice = lattice - else: - self.lattice = Lattice(lattice) # assume string + self.a = 1 if a is None else a + self.b = b + self.c = c + self.alpha = (np.radians(alpha) if degrees else alpha) if alpha is not None else None + self.beta = (np.radians(beta) if degrees else beta) if beta is not None else None + self.gamma = (np.radians(gamma) if degrees else gamma) if gamma is not None else None - if isinstance(rotation, Rotation): - self.rotation = rotation - else: - self.rotation = Rotation.from_quaternion(rotation) # assume quaternion + self.a = float(self.a) if self.a is not None else \ + (self.b / self.ratio['b'] if self.b is not None and self.ratio['b'] is not None else + self.c / self.ratio['c'] if self.c is not None and self.ratio['c'] is not None else None) + self.b = float(self.b) if self.b is not None else \ + (self.a * self.ratio['b'] if self.a is not None and self.ratio['b'] is not None else + self.c / self.ratio['c'] * self.ratio['b'] + if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) + self.c = float(self.c) if self.c is not None else \ + (self.a * self.ratio['c'] if self.a is not None and self.ratio['c'] is not None else + self.b / self.ratio['b'] * self.ratio['c'] + if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) + self.alpha = self.alpha if self.alpha is not None else self.immutable['alpha'] if 'alpha' in self.immutable else None + self.beta = self.beta if self.beta is not None else self.immutable['beta'] if 'beta' in self.immutable else None + self.gamma = self.gamma if self.gamma is not None else self.immutable['gamma'] if 'gamma' in self.immutable else None - def __getitem__(self,item): - """Iterate over leading/leftmost dimension of Orientation array.""" - return self.__class__(self.rotation[item],self.lattice) + if \ + (self.a is None) \ + or (self.b is None or ('b' in self.immutable and self.b != self.immutable['b'] * self.a)) \ + or (self.c is None or ('c' in self.immutable and self.c != self.immutable['c'] * self.b)) \ + or (self.alpha is None or ('alpha' in self.immutable and self.alpha != self.immutable['alpha'])) \ + or (self.beta is None or ( 'beta' in self.immutable and self.beta != self.immutable['beta'])) \ + or (self.gamma is None or ('gamma' in self.immutable and self.gamma != self.immutable['gamma'])): + raise ValueError (f'Incompatible parameters {self.parameters} for crystal family {self.family}') + + if np.any(np.array([self.alpha,self.beta,self.gamma]) <= 0): + raise ValueError ('Lattice angles must be positive') + if np.any([np.roll([self.alpha,self.beta,self.gamma],r)[0] + > np.sum(np.roll([self.alpha,self.beta,self.gamma],r)[1:]) for r in range(3)]): + raise ValueError ('Each lattice angle must be less than sum of others') + + if self.lattice in kinematics: + master = kinematics[self.lattice] + self.kinematics = {} + for m in master: + self.kinematics[m] = {'direction':master[m][:,0:3],'plane':master[m][:,3:6]} \ + if master[m].shape[-1] == 6 else \ + {'direction':self.Bravais_to_Miller(uvtw=master[m][:,0:4]), + 'plane': self.Bravais_to_Miller(hkil=master[m][:,4:8])} + elif lattice in self.crystal_families: + self.family = lattice - # ToDo: Discuss vectorization/calling signature - def disorientation(self, - other, - SST = True, - symmetries = False): + def __repr__(self): + """Represent.""" + return '\n'.join(([] if self.lattice is None else [f'Bravais lattice {self.lattice}']) + + ([] if self.family is None else [f'Crystal family {self.family}']) + + [super().__repr__()]) + + + def __copy__(self,**kwargs): + """Copy.""" + return self.__class__(rotation=kwargs['rotation'] if 'rotation' in kwargs else self.quaternion, + lattice =kwargs['lattice'] if 'lattice' in kwargs else self.lattice + if self.lattice is not None else self.family, + a =kwargs['a'] if 'a' in kwargs else self.a, + b =kwargs['b'] if 'b' in kwargs else self.b, + c =kwargs['c'] if 'c' in kwargs else self.c, + alpha =kwargs['alpha'] if 'alpha' in kwargs else self.alpha, + beta =kwargs['beta'] if 'beta' in kwargs else self.beta, + gamma =kwargs['gamma'] if 'gamma' in kwargs else self.gamma, + degrees =kwargs['degrees'] if 'degrees' in kwargs else None, + ) + + copy = __copy__ + + + def __eq__(self,other): """ - Disorientation between myself and given other orientation. + Equal to other. - Rotation axis falls into SST if SST == True. - - Currently requires same symmetry for both orientations. - Look into A. Heinz and P. Neumann 1991 for cases with differing sym. + Parameters + ---------- + other : Orientation + Orientation to check for equality. """ - if self.lattice.symmetry != other.lattice.symmetry: - raise NotImplementedError('disorientation between different symmetry classes not supported yet.') + return super().__eq__(other) \ + and self.family == other.family \ + and self.lattice == other.lattice \ + and self.parameters == other.parameters - mySymEqs = self.equivalent if SST else self.equivalent[0] #ToDo: This is just me! # take all or only first sym operation - otherSymEqs = other.equivalent - for i,sA in enumerate(mySymEqs): - aInv = sA.rotation.inversed() - for j,sB in enumerate(otherSymEqs): - b = sB.rotation - r = b*aInv - for k in range(2): - r.inverse() - breaker = self.lattice.in_FZ(r.as_Rodrigues(vector=True)) \ - and (not SST or other.lattice.in_disorientation_SST(r.as_Rodrigues(vector=True))) - if breaker: break - if breaker: break - if breaker: break + def __matmul__(self,other): + """ + Rotation of vector, second or fourth order tensor, or rotation object. + + Parameters + ---------- + other : numpy.ndarray, Rotation, or Orientation + Vector, second or fourth order tensor, or rotation object that is rotated. + + Returns + ------- + other_rot : numpy.ndarray or Rotation + Rotated vector, second or fourth order tensor, or rotation object. + + """ + return self.copy(rotation=Rotation.__matmul__(self,Rotation(other.quaternion))) \ + if isinstance(other,self.__class__) else \ + Rotation.__matmul__(self,other) + + + @classmethod + @util.extended_docstring(Rotation.from_random,_parameter_doc) + def from_random(cls,**kwargs): + return cls(rotation=Rotation.from_random(**kwargs),**kwargs) + + + @classmethod + @util.extended_docstring(Rotation.from_quaternion,_parameter_doc) + def from_quaternion(cls,**kwargs): + return cls(rotation=Rotation.from_quaternion(**kwargs),**kwargs) + + + @classmethod + @util.extended_docstring(Rotation.from_Euler_angles,_parameter_doc) + def from_Euler_angles(cls,**kwargs): + return cls(rotation=Rotation.from_Euler_angles(**kwargs),**kwargs) + + + @classmethod + @util.extended_docstring(Rotation.from_axis_angle,_parameter_doc) + def from_axis_angle(cls,**kwargs): + return cls(rotation=Rotation.from_axis_angle(**kwargs),**kwargs) + + + @classmethod + @util.extended_docstring(Rotation.from_basis,_parameter_doc) + def from_basis(cls,**kwargs): + return cls(rotation=Rotation.from_basis(**kwargs),**kwargs) + + + @classmethod + @util.extended_docstring(Rotation.from_matrix,_parameter_doc) + def from_matrix(cls,**kwargs): + return cls(rotation=Rotation.from_matrix(**kwargs),**kwargs) + + + @classmethod + @util.extended_docstring(Rotation.from_Rodrigues_vector,_parameter_doc) + def from_Rodrigues_vector(cls,**kwargs): + return cls(rotation=Rotation.from_Rodrigues_vector(**kwargs),**kwargs) + + + @classmethod + @util.extended_docstring(Rotation.from_homochoric,_parameter_doc) + def from_homochoric(cls,**kwargs): + return cls(rotation=Rotation.from_homochoric(**kwargs),**kwargs) + + + @classmethod + @util.extended_docstring(Rotation.from_cubochoric,_parameter_doc) + def from_cubochoric(cls,**kwargs): + return cls(rotation=Rotation.from_cubochoric(**kwargs),**kwargs) + + + @classmethod + @util.extended_docstring(Rotation.from_spherical_component,_parameter_doc) + def from_spherical_component(cls,**kwargs): + return cls(rotation=Rotation.from_spherical_component(**kwargs),**kwargs) + + + @classmethod + @util.extended_docstring(Rotation.from_fiber_component,_parameter_doc) + def from_fiber_component(cls,**kwargs): + return cls(rotation=Rotation.from_fiber_component(**kwargs),**kwargs) + + + @classmethod + @util.extend_docstring(_parameter_doc) + def from_directions(cls,uvw,hkl,**kwargs): + """ + Initialize orientation object from two crystallographic directions. + + Parameters + ---------- + uvw : list, numpy.ndarray of shape (...,3) + lattice direction aligned with lab frame x-direction. + hkl : list, numpy.ndarray of shape (...,3) + lattice plane normal aligned with lab frame z-direction. + + """ + o = cls(**kwargs) + x = o.to_frame(uvw=uvw) + z = o.to_frame(hkl=hkl) + om = np.stack([x,np.cross(z,x),z],axis=-2) + return o.copy(rotation=Rotation.from_matrix(tensor.transpose(om/np.linalg.norm(om,axis=-1,keepdims=True)))) - return (Orientation(r,self.lattice), i,j, k == 1) if symmetries else r # disorientation ... - # ... own sym, other sym, - # self-->other: True, self<--other: False @property - def in_FZ(self): - """Check if orientations fall into Fundamental Zone.""" - return self.lattice.in_FZ(self.rotation.as_Rodrigues(vector=True)) + def symmetry_operations(self): + """Symmetry operations as Rotations.""" + if self.family == 'cubic': + sym_quats = [ + [ 1.0, 0.0, 0.0, 0.0 ], + [ 0.0, 1.0, 0.0, 0.0 ], + [ 0.0, 0.0, 1.0, 0.0 ], + [ 0.0, 0.0, 0.0, 1.0 ], + [ 0.0, 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2) ], + [ 0.0, 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2) ], + [ 0.0, 0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2) ], + [ 0.0, 0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2) ], + [ 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], + [ 0.0, -0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], + [ 0.5, 0.5, 0.5, 0.5 ], + [-0.5, 0.5, 0.5, 0.5 ], + [-0.5, 0.5, 0.5, -0.5 ], + [-0.5, 0.5, -0.5, 0.5 ], + [-0.5, -0.5, 0.5, 0.5 ], + [-0.5, -0.5, 0.5, -0.5 ], + [-0.5, -0.5, -0.5, 0.5 ], + [-0.5, 0.5, -0.5, -0.5 ], + [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], + [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], + [-0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2), 0.0 ], + [-0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2), 0.0 ], + [-0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0, 0.0 ], + [-0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0, 0.0 ], + ] + elif self.family == 'hexagonal': + sym_quats = [ + [ 1.0, 0.0, 0.0, 0.0 ], + [-0.5*np.sqrt(3), 0.0, 0.0, -0.5 ], + [ 0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], + [ 0.0, 0.0, 0.0, 1.0 ], + [-0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], + [-0.5*np.sqrt(3), 0.0, 0.0, 0.5 ], + [ 0.0, 1.0, 0.0, 0.0 ], + [ 0.0, -0.5*np.sqrt(3), 0.5, 0.0 ], + [ 0.0, 0.5, -0.5*np.sqrt(3), 0.0 ], + [ 0.0, 0.0, 1.0, 0.0 ], + [ 0.0, -0.5, -0.5*np.sqrt(3), 0.0 ], + [ 0.0, 0.5*np.sqrt(3), 0.5, 0.0 ], + ] + elif self.family == 'tetragonal': + sym_quats = [ + [ 1.0, 0.0, 0.0, 0.0 ], + [ 0.0, 1.0, 0.0, 0.0 ], + [ 0.0, 0.0, 1.0, 0.0 ], + [ 0.0, 0.0, 0.0, 1.0 ], + [ 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], + [ 0.0, -0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], + [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], + [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], + ] + elif self.family == 'orthorhombic': + sym_quats = [ + [ 1.0,0.0,0.0,0.0 ], + [ 0.0,1.0,0.0,0.0 ], + [ 0.0,0.0,1.0,0.0 ], + [ 0.0,0.0,0.0,1.0 ], + ] + elif self.family == 'monoclinic': + sym_quats = [ + [ 1.0,0.0,0.0,0.0 ], + [ 0.0,0.0,1.0,0.0 ], + ] + elif self.family == 'triclinic': + sym_quats = [ + [ 1.0,0.0,0.0,0.0 ], + ] + else: + raise KeyError(f'Crystal family "{self.family}" is unknown') + + return Rotation.from_quaternion(sym_quats,accept_homomorph=True) @property def equivalent(self): """ - Orientations which are symmetrically equivalent. + Orientations that are symmetrically equivalent. - One dimension (length according to number of symmetrically equivalent orientations) + One dimension (length corresponds to number of symmetrically equivalent orientations) is added to the left of the Rotation array. """ - o = self.lattice.symmetry.symmetry_operations - o = o.reshape(o.shape[:1]+(1,)*len(self.rotation.shape)+(4,)) - o = Rotation(np.broadcast_to(o,o.shape[:1]+self.rotation.quaternion.shape)) + if self.family is None: + raise ValueError('Missing crystal symmetry') - s = np.broadcast_to(self.rotation.quaternion,o.shape[:1]+self.rotation.quaternion.shape) + o = self.symmetry_operations.broadcast_to(self.symmetry_operations.shape+self.shape,mode='right') + return self.copy(rotation=o@Rotation(self.quaternion).broadcast_to(o.shape,mode='left')) - return self.__class__(o@Rotation(s),self.lattice) + + @property + def reduced(self): + """Select symmetrically equivalent orientation that falls into fundamental zone according to symmetry.""" + if self.family is None: + raise ValueError('Missing crystal symmetry') + + eq = self.equivalent + ok = eq.in_FZ + ok &= np.cumsum(ok,axis=0) == 1 + loc = np.where(ok) + sort = 0 if len(loc) == 1 else np.lexsort(loc[:0:-1]) + return eq[ok][sort].reshape(self.shape) + + + @property + def in_FZ(self): + """ + Check whether orientation falls into fundamental zone of own symmetry. + + Returns + ------- + in : numpy.ndarray of quaternion.shape + Boolean array indicating whether Rodrigues-Frank vector falls into fundamental zone. + + Notes + ----- + Fundamental zones in Rodrigues space are point-symmetric around origin. + + References + ---------- + A. Heinz and P. Neumann, Acta Crystallographica Section A 47:780-789, 1991 + https://doi.org/10.1107/S0108767391006864 + + """ + if self.family is None: + raise ValueError('Missing crystal symmetry') + + rho_abs = np.abs(self.as_Rodrigues_vector(compact=True)) + + with np.errstate(invalid='ignore'): + # using '*'/prod for 'and' + if self.family == 'cubic': + return (np.prod(np.sqrt(2)-1. >= rho_abs,axis=-1) * + (1. >= np.sum(rho_abs,axis=-1))).astype(np.bool) + elif 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(np.bool) + elif 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(np.bool) + elif self.family == 'orthorhombic': + return (np.prod(1. >= rho_abs,axis=-1)).astype(np.bool) + elif self.family == 'monoclinic': + return (1. >= rho_abs[...,1]).astype(np.bool) + else: + return np.all(np.isfinite(rho_abs),axis=-1) + + + @property + def in_disorientation_FZ(self): + """ + Check whether orientation falls into fundamental zone of disorientations. + + Returns + ------- + in : numpy.ndarray of quaternion.shape + Boolean array indicating whether Rodrigues-Frank vector falls into disorientation FZ. + + References + ---------- + A. Heinz and P. Neumann, Acta Crystallographica Section A 47:780-789, 1991 + https://doi.org/10.1107/S0108767391006864 + + """ + if self.family is None: + raise ValueError('Missing crystal symmetry') + + rho = self.as_Rodrigues_vector(compact=True) + + with np.errstate(invalid='ignore'): + if self.family == 'cubic': + return ((rho[...,0] >= rho[...,1]) & + (rho[...,1] >= rho[...,2]) & + (rho[...,2] >= 0)).astype(np.bool) + elif self.family == 'hexagonal': + return ((rho[...,0] >= rho[...,1]*np.sqrt(3)) & + (rho[...,1] >= 0) & + (rho[...,2] >= 0)).astype(np.bool) + elif self.family == 'tetragonal': + return ((rho[...,0] >= rho[...,1]) & + (rho[...,1] >= 0) & + (rho[...,2] >= 0)).astype(np.bool) + elif self.family == 'orthorhombic': + return ((rho[...,0] >= 0) & + (rho[...,1] >= 0) & + (rho[...,2] >= 0)).astype(np.bool) + elif self.family == 'monoclinic': + return ((rho[...,1] >= 0) & + (rho[...,2] >= 0)).astype(np.bool) + else: + return np.ones_like(rho[...,0],dtype=bool) + + + def relation_operations(self,model,return_lattice=False): + """ + Crystallographic orientation relationships for phase transformations. + + Parameters + ---------- + model : str + Name of orientation relationship. + return_lattice : bool, optional + Return the target lattice in addition. + + Returns + ------- + operations : Rotations + Rotations characterizing the orientation relationship. + + References + ---------- + S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013 + https://doi.org/10.1016/j.jallcom.2012.02.004 + + K. Kitahara et al., Acta Materialia 54(5):1279-1288, 2006 + https://doi.org/10.1016/j.actamat.2005.11.001 + + Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 + https://doi.org/10.1107/S0021889805038276 + + H. Kitahara et al., Materials Characterization 54(4-5):378-386, 2005 + https://doi.org/10.1016/j.matchar.2004.12.015 + + Y. He et al., Acta Materialia 53(4):1179-1190, 2005 + https://doi.org/10.1016/j.actamat.2004.11.021 + + """ + from damask.lattice import relations + + if model not in relations: + raise KeyError(f'Orientation relationship "{model}" is unknown') + r = relations[model] + + if self.lattice not in r: + raise KeyError(f'Relationship "{model}" not supported for lattice "{self.lattice}"') + + sl = self.lattice + ol = (set(r)-{sl}).pop() + m = r[sl] + o = r[ol] + + p_,_p = np.zeros(m.shape[:-1]+(3,)),np.zeros(o.shape[:-1]+(3,)) + p_[...,0,:] = m[...,0,:] if m.shape[-1] == 3 else self.Bravais_to_Miller(uvtw=m[...,0,0:4]) + p_[...,1,:] = m[...,1,:] if m.shape[-1] == 3 else self.Bravais_to_Miller(hkil=m[...,1,0:4]) + _p[...,0,:] = o[...,0,:] if o.shape[-1] == 3 else self.Bravais_to_Miller(uvtw=o[...,0,0:4]) + _p[...,1,:] = o[...,1,:] if o.shape[-1] == 3 else self.Bravais_to_Miller(hkil=o[...,1,0:4]) + + return (Rotation.from_parallel(p_,_p),ol) \ + if return_lattice else \ + Rotation.from_parallel(p_,_p) def related(self,model): """ - Orientations related by the given orientation relationship. + Orientations derived from the given relationship. One dimension (length according to number of related orientations) is added to the left of the Rotation array. """ - o = Rotation.from_matrix(self.lattice.relation_operations(model)['rotations']).as_quaternion() - o = o.reshape(o.shape[:1]+(1,)*len(self.rotation.shape)+(4,)) - o = Rotation(np.broadcast_to(o,o.shape[:1]+self.rotation.quaternion.shape)) - - s = np.broadcast_to(self.rotation.quaternion,o.shape[:1]+self.rotation.quaternion.shape) - - return self.__class__(o@Rotation(s),self.lattice.relation_operations(model)['lattice']) + o,lattice = self.relation_operations(model,return_lattice=True) + target = Orientation(lattice=lattice) + o = o.broadcast_to(o.shape+self.shape,mode='right') + return self.copy(rotation=o@Rotation(self.quaternion).broadcast_to(o.shape,mode='left'), + lattice=lattice, + b = self.b if target.ratio['b'] is None else self.a*target.ratio['b'], + c = self.c if target.ratio['c'] is None else self.a*target.ratio['c'], + alpha = None if 'alpha' in target.immutable else self.alpha, + beta = None if 'beta' in target.immutable else self.beta, + gamma = None if 'gamma' in target.immutable else self.gamma, + ) @property - def reduced(self): - """Transform orientation to fall into fundamental zone according to symmetry.""" - eq = self.equivalent - in_FZ = eq.in_FZ - - # remove duplicates (occur for highly symmetric orientations) - found = np.zeros_like(in_FZ[0],dtype=bool) - q = self.rotation.quaternion[0] - for s in range(in_FZ.shape[0]): - #something fishy... why does q needs to be initialized? - q = np.where(np.expand_dims(np.logical_and(in_FZ[s],~found),-1),eq.rotation.quaternion[s],q) - found = np.logical_or(in_FZ[s],found) - - return self.__class__(q,self.lattice) + def parameters(self): + """Return lattice parameters a, b, c, alpha, beta, gamma.""" + return (self.a,self.b,self.c,self.alpha,self.beta,self.gamma) - def inverse_pole(self,axis,proper=False,SST=True): - """Axis rotated according to orientation (using crystal symmetry to ensure location falls into SST).""" - if SST: - eq = self.equivalent - pole = eq.rotation @ np.broadcast_to(axis/np.linalg.norm(axis),eq.rotation.shape+(3,)) - in_SST = self.lattice.in_SST(pole,proper=proper) - - # remove duplicates (occur for highly symmetric orientations) - found = np.zeros_like(in_SST[0],dtype=bool) - p = pole[0] - for s in range(in_SST.shape[0]): - p = np.where(np.expand_dims(np.logical_and(in_SST[s],~found),-1),pole[s],p) - found = np.logical_or(in_SST[s],found) - - return p + @property + def immutable(self): + """Return immutable parameters of own lattice.""" + if self.family == 'triclinic': + return {} + if self.family == 'monoclinic': + return { + 'alpha': np.pi/2., + 'gamma': np.pi/2., + } + if self.family == 'orthorhombic': + return { + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + } + if self.family == 'tetragonal': + return { + 'b': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + } + if self.family == 'hexagonal': + return { + 'b': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': 2.*np.pi/3., + } + if self.family == 'cubic': + return { + 'b': 1.0, + 'c': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + } else: - return self.rotation @ np.broadcast_to(axis/np.linalg.norm(axis),self.rotation.shape+(3,)) + raise KeyError(f'Crystal family "{self.family}" is unknown') + @property + def ratio(self): + """Return axes ratios of own lattice.""" + _ratio = { 'hexagonal': {'c': np.sqrt(8./3.)}} + + return dict(b = self.immutable['b'] + if 'b' in self.immutable else + _ratio[self.family]['b'] if self.family in _ratio and 'b' in _ratio[self.family] else None, + c = self.immutable['c'] + if 'c' in self.immutable else + _ratio[self.family]['c'] if self.family in _ratio and 'c' in _ratio[self.family] else None, + ) + + + @property + def basis_real(self): + """ + Calculate orthogonal real space crystal basis. + + References + ---------- + C.T. Young and J.L. Lytton, J. Appl. Phys. 43:1408–1417, 1972 + "Computer Generation and Identification of Kikuchi Projections" + https://doi.org/10.1063/1.1661333 + + """ + if None in self.parameters: + raise KeyError('Missing crystal lattice parameters') + return np.array([ + [1,0,0], + [np.cos(self.gamma),np.sin(self.gamma),0], + [np.cos(self.beta), + (np.cos(self.alpha)-np.cos(self.beta)*np.cos(self.gamma)) /np.sin(self.gamma), + np.sqrt(1 - np.cos(self.alpha)**2 - np.cos(self.beta)**2 - np.cos(self.gamma)**2 + + 2 * np.cos(self.alpha) * np.cos(self.beta) * np.cos(self.gamma))/np.sin(self.gamma)], + ],dtype=float).T \ + * np.array([self.a,self.b,self.c]) + + + @property + def basis_reciprocal(self): + """Calculate reciprocal (dual) crystal basis.""" + return np.linalg.inv(self.basis_real.T) + + + def in_SST(self,vector,proper=False): + """ + Check whether given crystal frame vector falls into standard stereographic triangle of own symmetry. + + Parameters + ---------- + vector : numpy.ndarray of shape (...,3) + Vector to check. + proper : bool, optional + Consider only vectors with z >= 0, hence combine two neighboring SSTs. + Defaults to False. + + Returns + ------- + in : numpy.ndarray of shape (...) + Boolean array indicating whether vector falls into SST. + + References + ---------- + Bases are computed from + + >>> basis = { + ... 'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,1.]/np.sqrt(2.), # green + ... [1.,1.,1.]/np.sqrt(3.)]).T), # blue + ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # blue + ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [1.,1.,0.]/np.sqrt(2.)]).T), # blue + ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [0.,1.,0.]]).T), # blue + ... } + + """ + if not isinstance(vector,np.ndarray) or vector.shape[-1] != 3: + raise ValueError('Input is not a field of three-dimensional vectors.') + + if self.family == 'cubic': + basis = {'improper':np.array([ [-1. , 0. , 1. ], + [ np.sqrt(2.) , -np.sqrt(2.) , 0. ], + [ 0. , np.sqrt(3.) , 0. ] ]), + 'proper':np.array([ [ 0. , -1. , 1. ], + [-np.sqrt(2.) , np.sqrt(2.) , 0. ], + [ np.sqrt(3.) , 0. , 0. ] ]), + } + elif self.family == 'hexagonal': + basis = {'improper':np.array([ [ 0. , 0. , 1. ], + [ 1. , -np.sqrt(3.) , 0. ], + [ 0. , 2. , 0. ] ]), + 'proper':np.array([ [ 0. , 0. , 1. ], + [-1. , np.sqrt(3.) , 0. ], + [ np.sqrt(3.) , -1. , 0. ] ]), + } + elif self.family == 'tetragonal': + basis = {'improper':np.array([ [ 0. , 0. , 1. ], + [ 1. , -1. , 0. ], + [ 0. , np.sqrt(2.) , 0. ] ]), + 'proper':np.array([ [ 0. , 0. , 1. ], + [-1. , 1. , 0. ], + [ np.sqrt(2.) , 0. , 0. ] ]), + } + elif self.family == 'orthorhombic': + basis = {'improper':np.array([ [ 0., 0., 1.], + [ 1., 0., 0.], + [ 0., 1., 0.] ]), + 'proper':np.array([ [ 0., 0., 1.], + [-1., 0., 0.], + [ 0., 1., 0.] ]), + } + else: # direct exit for unspecified symmetry + return np.ones_like(vector[...,0],bool) + + if proper: + components_proper = np.around(np.einsum('...ji,...i', + np.broadcast_to(basis['proper'], vector.shape+(3,)), + vector), 12) + components_improper = np.around(np.einsum('...ji,...i', + np.broadcast_to(basis['improper'], vector.shape+(3,)), + vector), 12) + return np.all(components_proper >= 0.0,axis=-1) \ + | np.all(components_improper >= 0.0,axis=-1) + else: + components = np.around(np.einsum('...ji,...i', + np.broadcast_to(basis['improper'], vector.shape+(3,)), + np.block([vector[...,:2],np.abs(vector[...,2:3])])), 12) + + return np.all(components >= 0.0,axis=-1) + + + def IPF_color(self,vector,in_SST=True,proper=False): + """ + Map vector to RGB color within standard stereographic triangle of own symmetry. + + Parameters + ---------- + vector : numpy.ndarray of shape (...,3) + Vector to colorize. + in_SST : bool, optional + Consider symmetrically equivalent orientations such that poles are located in SST. + Defaults to True. + proper : bool, optional + Consider only vectors with z >= 0, hence combine two neighboring SSTs (with mirrored colors). + Defaults to False. + + Returns + ------- + rgb : numpy.ndarray of shape (...,3) + RGB array of IPF colors. + + Examples + -------- + Inverse pole figure color of the e_3 direction for a crystal in "Cube" orientation with cubic symmetry: + + >>> o = damask.Orientation(lattice='cubic') + >>> o.IPF_color([0,0,1]) + array([1., 0., 0.]) + + References + ---------- + Bases are computed from + + >>> basis = { + ... 'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,1.]/np.sqrt(2.), # green + ... [1.,1.,1.]/np.sqrt(3.)]).T), # blue + ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # blue + ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [1.,1.,0.]/np.sqrt(2.)]).T), # blue + ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [0.,1.,0.]]).T), # blue + ... } + + """ + if np.array(vector).shape[-1] != 3: + raise ValueError('Input is not a field of three-dimensional vectors.') + + vector_ = self.to_SST(vector,proper) if in_SST else \ + self @ np.broadcast_to(vector,self.shape+(3,)) + + if self.family == 'cubic': + basis = {'improper':np.array([ [-1. , 0. , 1. ], + [ np.sqrt(2.) , -np.sqrt(2.) , 0. ], + [ 0. , np.sqrt(3.) , 0. ] ]), + 'proper':np.array([ [ 0. , -1. , 1. ], + [-np.sqrt(2.) , np.sqrt(2.) , 0. ], + [ np.sqrt(3.) , 0. , 0. ] ]), + } + elif self.family == 'hexagonal': + basis = {'improper':np.array([ [ 0. , 0. , 1. ], + [ 1. , -np.sqrt(3.) , 0. ], + [ 0. , 2. , 0. ] ]), + 'proper':np.array([ [ 0. , 0. , 1. ], + [-1. , np.sqrt(3.) , 0. ], + [ np.sqrt(3.) , -1. , 0. ] ]), + } + elif self.family == 'tetragonal': + basis = {'improper':np.array([ [ 0. , 0. , 1. ], + [ 1. , -1. , 0. ], + [ 0. , np.sqrt(2.) , 0. ] ]), + 'proper':np.array([ [ 0. , 0. , 1. ], + [-1. , 1. , 0. ], + [ np.sqrt(2.) , 0. , 0. ] ]), + } + elif self.family == 'orthorhombic': + basis = {'improper':np.array([ [ 0., 0., 1.], + [ 1., 0., 0.], + [ 0., 1., 0.] ]), + 'proper':np.array([ [ 0., 0., 1.], + [-1., 0., 0.], + [ 0., 1., 0.] ]), + } + else: # direct exit for unspecified symmetry + return np.zeros_like(vector_) + + if proper: + components_proper = np.around(np.einsum('...ji,...i', + np.broadcast_to(basis['proper'], vector_.shape+(3,)), + vector_), 12) + components_improper = np.around(np.einsum('...ji,...i', + np.broadcast_to(basis['improper'], vector_.shape+(3,)), + vector_), 12) + in_SST = np.all(components_proper >= 0.0,axis=-1) \ + | np.all(components_improper >= 0.0,axis=-1) + components = np.where((in_SST & np.all(components_proper >= 0.0,axis=-1))[...,np.newaxis], + components_proper,components_improper) + else: + components = np.around(np.einsum('...ji,...i', + np.broadcast_to(basis['improper'], vector_.shape+(3,)), + np.block([vector_[...,:2],np.abs(vector_[...,2:3])])), 12) + + in_SST = np.all(components >= 0.0,axis=-1) + + with np.errstate(invalid='ignore',divide='ignore'): + rgb = (components/np.linalg.norm(components,axis=-1,keepdims=True))**0.5 # smoothen color ramps + rgb = np.clip(rgb,0.,1.) # clip intensity + rgb /= np.max(rgb,axis=-1,keepdims=True) # normalize to (HS)V = 1 + rgb[np.broadcast_to(~in_SST[...,np.newaxis],rgb.shape)] = 0.0 + return rgb + + + def disorientation(self,other,return_operators=False): + """ + Calculate disorientation between myself 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. + return_operators : bool, optional + Return index pair of symmetrically equivalent orientations that result in disorientation axis falling into FZ. + Defaults to False. + + Returns + ------- + disorientation : Orientation + Disorientation between self and other. + operators : numpy.ndarray int of shape (...,2), conditional + Index of symmetrically equivalent orientation that rotated vector to the SST. + + Notes + ----- + Currently requires same crystal family for both orientations. + For extension to cases with differing symmetry see A. Heinz and P. Neumann 1991 and 10.1107/S0021889808016373. + + Examples + -------- + Disorientation between two specific orientations of hexagonal symmetry: + + >>> import damask + >>> a = damask.Orientation.from_Eulers(phi=[123,32,21],degrees=True,lattice='hexagonal') + >>> b = damask.Orientation.from_Eulers(phi=[104,11,87],degrees=True,lattice='hexagonal') + >>> a.disorientation(b) + Crystal family hexagonal + Quaternion: (real=0.976, imag=<+0.189, +0.018, +0.103>) + Matrix: + [[ 0.97831006 0.20710935 0.00389135] + [-0.19363288 0.90765544 0.37238141] + [ 0.07359167 -0.36505797 0.92807163]] + Bunge Eulers / deg: (11.40, 21.86, 0.60) + + """ + if self.family is None or other.family is None: + raise ValueError('missing crystal symmetry') + if self.family != other.family: + raise NotImplementedError('disorientation between different crystal families') + + blend = util.shapeblender(self.shape,other.shape) + s = self.equivalent + o = other.equivalent + + s_ = s.reshape((s.shape[0],1)+ self.shape).broadcast_to((s.shape[0],o.shape[0])+blend,mode='right') + o_ = o.reshape((1,o.shape[0])+other.shape).broadcast_to((s.shape[0],o.shape[0])+blend,mode='right') + r_ = s_.misorientation(o_) + _r = ~r_ + + forward = r_.in_FZ & r_.in_disorientation_FZ + reverse = _r.in_FZ & _r.in_disorientation_FZ + ok = forward | reverse + ok &= (np.cumsum(ok.reshape((-1,)+ok.shape[2:]),axis=0) == 1).reshape(ok.shape) + r = np.where(np.any(forward[...,np.newaxis],axis=(0,1),keepdims=True), + r_.quaternion, + _r.quaternion) + loc = np.where(ok) + sort = 0 if len(loc) == 2 else np.lexsort(loc[:1:-1]) + quat = r[ok][sort].reshape(blend+(4,)) + + return ( + (self.copy(rotation=quat), + (np.vstack(loc[:2]).T)[sort].reshape(blend+(2,))) + if return_operators else + self.copy(rotation=quat) + ) + + + def average(self,weights=None,return_cloud=False): + """ + Return orientation average over last dimension. + + Parameters + ---------- + weights : numpy.ndarray, optional + Relative weights of orientations. + return_cloud : bool, optional + Return the set of symmetrically equivalent orientations that was used in averaging. + Defaults to False. + + Returns + ------- + average : Orientation + Weighted average of original Orientation field. + cloud : Orientations, conditional + Set of symmetrically equivalent orientations that were used in averaging. + + References + ---------- + J.C. Glez and J. Driver, J. Appl. Cryst. 34:280-288, 2001 + "Orientation distribution analysis in deformed grains" + https://doi.org/10.1107/S0021889801003077 + + """ + if self.family is None: + raise ValueError('Missing crystal symmetry') - def IPF_color(self,axis): #ToDo axis or direction? - """TSL color of inverse pole figure for given axis.""" eq = self.equivalent - pole = eq.rotation @ np.broadcast_to(axis/np.linalg.norm(axis),eq.rotation.shape+(3,)) - in_SST, color = self.lattice.in_SST(pole,color=True) - - # remove duplicates (occur for highly symmetric orientations) - found = np.zeros_like(in_SST[0],dtype=bool) - c = color[0] - for s in range(in_SST.shape[0]): - c = np.where(np.expand_dims(np.logical_and(in_SST[s],~found),-1),color[s],c) - found = np.logical_or(in_SST[s],found) - - return c + m = eq.misorientation(self[...,0].reshape((1,)+self.shape[:-1]+(1,)) + .broadcast_to(eq.shape))\ + .as_axis_angle()[...,3] + r = Rotation(np.squeeze(np.take_along_axis(eq.quaternion, + np.argmin(m,axis=0)[np.newaxis,...,np.newaxis], + axis=0), + axis=0)) + return ( + (self.copy(rotation=Rotation(r).average(weights)), + self.copy(rotation=Rotation(r))) + if return_cloud else + self.copy(rotation=Rotation(r).average(weights)) + ) - # ToDo: Discuss vectorization/calling signature - @staticmethod - def from_average(orientations, - weights = []): - """Create orientation from average of list of orientations.""" - # further read: Orientation distribution analysis in deformed grains - # https://doi.org/10.1107/S0021889801003077 - if not all(isinstance(item, Orientation) for item in orientations): - raise TypeError("Only instances of Orientation can be averaged.") + def to_SST(self,vector,proper=False,return_operators=False): + """ + Rotate vector to ensure it falls into (improper or proper) standard stereographic triangle of crystal symmetry. - closest = [] - ref = orientations[0] - for o in orientations: - closest.append(o.equivalent[ - ref.disorientation(o, - SST = False, # select (o[ther]'s) sym orientation - symmetries = True)[2]].rotation) # with lowest misorientation + Parameters + ---------- + vector : numpy.ndarray of shape (...,3) + Lab frame vector to align with crystal frame direction. + Shape of other blends with shape of own rotation array. + For example, a rotation array of shape (3,2) and a (2,4) vector array result in (3,2,4) outputs. + proper : bool, optional + Consider only vectors with z >= 0, hence combine two neighboring SSTs. + Defaults to False. + return_operators : bool, optional + Return the symmetrically equivalent orientation that rotated vector to SST. + Defaults to False. - return Orientation(Rotation.from_average(closest,weights),ref.lattice) + Returns + ------- + vector_SST : numpy.ndarray of shape (...,3) + Rotated vector falling into SST. + operators : numpy.ndarray int of shape (...), conditional + Index of symmetrically equivalent orientation that rotated vector to SST. + + """ + if self.family is None: + raise ValueError('Missing crystal symmetry') + + eq = self.equivalent + blend = util.shapeblender(eq.shape,np.array(vector).shape[:-1]) + poles = eq.broadcast_to(blend,mode='right') @ np.broadcast_to(np.array(vector),blend+(3,)) + ok = self.in_SST(poles,proper=proper) + ok &= np.cumsum(ok,axis=0) == 1 + loc = np.where(ok) + sort = 0 if len(loc) == 1 else np.lexsort(loc[:0:-1]) + return ( + (poles[ok][sort].reshape(blend[1:]+(3,)), (np.vstack(loc[:1]).T)[sort].reshape(blend[1:])) + if return_operators else + poles[ok][sort].reshape(blend[1:]+(3,)) + ) - # ToDo: Discuss vectorization/calling signature - def average(self,other): - """Calculate the average rotation.""" - return Orientation.from_average([self,other]) + @classmethod + def Bravais_to_Miller(cls,*,uvtw=None,hkil=None): + """ + Transform 4 Miller–Bravais indices to 3 Miller indices of crystal direction [uvw] or plane normal (hkl). + + Parameters + ---------- + uvtw|hkil : numpy.ndarray of shape (...,4) + Miller–Bravais indices of crystallographic direction [uvtw] or plane normal (hkil). + + Returns + ------- + uvw|hkl : numpy.ndarray of shape (...,3) + Miller indices of [uvw] direction or (hkl) plane normal. + + """ + if (uvtw is not None) ^ (hkil is None): + raise KeyError('Specify either "uvtw" or "hkil"') + axis,basis = (np.array(uvtw),np.array([[1,0,-1,0], + [0,1,-1,0], + [0,0, 0,1]])) \ + if hkil is None else \ + (np.array(hkil),np.array([[1,0,0,0], + [0,1,0,0], + [0,0,0,1]])) + return np.einsum('il,...l',basis,axis) + + + @classmethod + def Miller_to_Bravais(cls,*,uvw=None,hkl=None): + """ + Transform 3 Miller indices to 4 Miller–Bravais indices of crystal direction [uvtw] or plane normal (hkil). + + Parameters + ---------- + uvw|hkl : numpy.ndarray of shape (...,3) + Miller indices of crystallographic direction [uvw] or plane normal (hkl). + + Returns + ------- + uvtw|hkil : numpy.ndarray of shape (...,4) + Miller–Bravais indices of [uvtw] direction or (hkil) plane normal. + + """ + if (uvw is not None) ^ (hkl is None): + raise KeyError('Specify either "uvw" or "hkl"') + axis,basis = (np.array(uvw),np.array([[ 2,-1, 0], + [-1, 2, 0], + [-1,-1, 0], + [ 0, 0, 3]])/3) \ + if hkl is None else \ + (np.array(hkl),np.array([[ 1, 0, 0], + [ 0, 1, 0], + [-1,-1, 0], + [ 0, 0, 1]])) + return np.einsum('il,...l',basis,axis) + + + def to_lattice(self,*,direction=None,plane=None): + """ + Calculate lattice vector corresponding to crystal frame direction or plane normal. + + Parameters + ---------- + direction|normal : numpy.ndarray of shape (...,3) + Vector along direction or plane normal. + + Returns + ------- + Miller : numpy.ndarray of shape (...,3) + lattice vector of direction or plane. + Use util.scale_to_coprime to convert to (integer) Miller indices. + + """ + if (direction is not None) ^ (plane is None): + raise KeyError('Specify either "direction" or "plane"') + axis,basis = (np.array(direction),self.basis_reciprocal.T) \ + if plane is None else \ + (np.array(plane),self.basis_real.T) + return np.einsum('il,...l',basis,axis) + + + def to_frame(self,*,uvw=None,hkl=None,with_symmetry=False): + """ + Calculate crystal frame vector along lattice direction [uvw] or plane normal (hkl). + + Parameters + ---------- + uvw|hkl : numpy.ndarray of shape (...,3) + Miller indices of crystallographic direction or plane normal. + with_symmetry : bool, optional + Calculate all N symmetrically equivalent vectors. + + Returns + ------- + vector : numpy.ndarray of shape (...,3) or (N,...,3) + Crystal frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. + + """ + if (uvw is not None) ^ (hkl is None): + raise KeyError('Specify either "uvw" or "hkl"') + axis,basis = (np.array(uvw),self.basis_real) \ + if hkl is None else \ + (np.array(hkl),self.basis_reciprocal) + return (self.symmetry_operations.broadcast_to(self.symmetry_operations.shape+axis.shape[:-1],mode='right') + @ np.broadcast_to(np.einsum('il,...l',basis,axis),self.symmetry_operations.shape+axis.shape) + if with_symmetry else + np.einsum('il,...l',basis,axis)) + + + def to_pole(self,*,uvw=None,hkl=None,with_symmetry=False): + """ + Calculate lab frame vector along lattice direction [uvw] or plane normal (hkl). + + Parameters + ---------- + uvw|hkl : numpy.ndarray of shape (...,3) + Miller indices of crystallographic direction or plane normal. + with_symmetry : bool, optional + Calculate all N symmetrically equivalent vectors. + + Returns + ------- + vector : numpy.ndarray of shape (...,3) or (N,...,3) + Lab frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. + + """ + v = self.to_frame(uvw=uvw,hkl=hkl,with_symmetry=with_symmetry) + return ~(self if self.shape+v.shape[:-1] == () else self.broadcast_to(self.shape+v.shape[:-1],mode='right')) \ + @ np.broadcast_to(v,self.shape+v.shape) + + + def Schmid(self,mode): + u""" + Calculate Schmid matrix P = d ⨂ n in the lab frame for given lattice shear kinematics. + + Parameters + ---------- + mode : str + Type of kinematics, i.e. 'slip' or 'twin'. + + Returns + ------- + P : numpy.ndarray of shape (...,N,3,3) + Schmid matrix for each of the N deformation systems. + + Examples + -------- + Schmid matrix (in lab frame) of slip systems of a face-centered + cubic crystal in "Goss" orientation. + + >>> import damask + >>> import numpy as np + >>> np.set_printoptions(3,suppress=True,floatmode='fixed') + >>> damask.Orientation.from_Eulers(phi=[0,45,0],degrees=True,lattice='cF').Schmid('slip')[0] + array([[ 0.000, 0.000, 0.000], + [ 0.577, -0.000, 0.816], + [ 0.000, 0.000, 0.000]]) + + """ + d = self.to_frame(uvw=self.kinematics[mode]['direction'],with_symmetry=False) + p = self.to_frame(hkl=self.kinematics[mode]['plane'] ,with_symmetry=False) + P = np.einsum('...i,...j',d/np.linalg.norm(d,axis=-1,keepdims=True), + p/np.linalg.norm(p,axis=-1,keepdims=True)) + + return ~self.broadcast_to( self.shape+P.shape[:-2],mode='right') \ + @ np.broadcast_to(P,self.shape+P.shape) diff --git a/python/damask/_result.py b/python/damask/_result.py index 5e8a9a9d0..1dfc33f95 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -7,6 +7,7 @@ import xml.etree.ElementTree as ET import xml.dom.minidom from pathlib import Path from functools import partial +from collections import defaultdict import h5py import numpy as np @@ -15,12 +16,13 @@ from numpy.lib import recfunctions as rfn import damask from . import VTK from . import Table -from . import Rotation from . import Orientation from . import grid_filters from . import mechanics +from . import tensor from . import util +h5py3 = h5py.__version__[0] == '3' class Result: """ @@ -35,20 +37,16 @@ class Result: Parameters ---------- - fname : str - name of the DADF5 file to be opened. + fname : str or pathlib.Path + Name of the DADF5 file to be opened. """ with h5py.File(fname,'r') as f: - try: - self.version_major = f.attrs['DADF5_version_major'] - self.version_minor = f.attrs['DADF5_version_minor'] - except KeyError: - self.version_major = f.attrs['DADF5-major'] - self.version_minor = f.attrs['DADF5-minor'] + self.version_major = f.attrs['DADF5_version_major'] + self.version_minor = f.attrs['DADF5_version_minor'] - if self.version_major != 0 or not 2 <= self.version_minor <= 7: + if self.version_major != 0 or not 7 <= self.version_minor <= 9: raise TypeError(f'Unsupported DADF5 version {self.version_major}.{self.version_minor}') self.structured = 'grid' in f['geometry'].attrs.keys() @@ -56,35 +54,33 @@ class Result: if self.structured: self.grid = f['geometry'].attrs['grid'] self.size = f['geometry'].attrs['size'] - self.origin = f['geometry'].attrs['origin'] if self.version_major == 0 and self.version_minor >= 5 else \ - np.zeros(3) + self.origin = f['geometry'].attrs['origin'] r=re.compile('inc[0-9]+') increments_unsorted = {int(i[3:]):i for i in f.keys() if r.match(i)} self.increments = [increments_unsorted[i] for i in sorted(increments_unsorted)] self.times = [round(f[i].attrs['time/s'],12) for i in self.increments] - self.Nmaterialpoints, self.Nconstituents = np.shape(f['mapping/cellResults/constituent']) - self.materialpoints = [m.decode() for m in np.unique(f['mapping/cellResults/materialpoint']['Name'])] - self.constituents = [c.decode() for c in np.unique(f['mapping/cellResults/constituent'] ['Name'])] + self.N_materialpoints, self.N_constituents = np.shape(f['mapping/phase']) - # faster, but does not work with (deprecated) DADF5_postResults - #self.materialpoints = [m for m in f['inc0/materialpoint']] - #self.constituents = [c for c in f['inc0/constituent']] + self.homogenizations = [m.decode() for m in np.unique(f['mapping/homogenization']['Name'])] + self.phases = [c.decode() for c in np.unique(f['mapping/phase']['Name'])] - self.con_physics = [] - for c in self.constituents: - self.con_physics += f['/'.join([self.increments[0],'constituent',c])].keys() - self.con_physics = list(set(self.con_physics)) # make unique + self.out_type_ph = [] + for c in self.phases: + self.out_type_ph += f['/'.join([self.increments[0],'phase',c])].keys() + self.out_type_ph = list(set(self.out_type_ph)) # make unique - self.mat_physics = [] - for m in self.materialpoints: - self.mat_physics += f['/'.join([self.increments[0],'materialpoint',m])].keys() - self.mat_physics = list(set(self.mat_physics)) # make unique + self.out_type_ho = [] + for m in self.homogenizations: + self.out_type_ho += f['/'.join([self.increments[0],'homogenization',m])].keys() + self.out_type_ho = list(set(self.out_type_ho)) # make unique - self.selection = {'increments': self.increments, - 'constituents': self.constituents,'materialpoints': self.materialpoints, - 'con_physics': self.con_physics, 'mat_physics': self.mat_physics + self.selection = {'increments': self.increments, + 'phases': self.phases, + 'homogenizations': self.homogenizations, + 'out_type_ph': self.out_type_ph, + 'out_type_ho': self.out_type_ho } self.fname = Path(fname).absolute() @@ -93,7 +89,7 @@ class Result: def __repr__(self): - """Show selected data.""" + """Show summary of file content.""" all_selected_increments = self.selection['increments'] self.pick('increments',all_selected_increments[0:1]) @@ -117,14 +113,18 @@ class Result: Parameters ---------- action : str - select from 'set', 'add', and 'del' + Select from 'set', 'add', and 'del'. what : str - attribute to change (must be from self.selection) + Attribute to change (must be from self.selection). datasets : list of str or bool - name of datasets as list, supports ? and * wildcards. + Name of datasets as list, supports ? and * wildcards. True is equivalent to [*], False is equivalent to [] """ + def natural_sort(key): + convert = lambda text: int(text) if text.isdigit() else text + return [ convert(c) for c in re.split('([0-9]+)', key) ] + # allow True/False and string arguments if datasets is True: datasets = ['*'] @@ -158,25 +158,60 @@ class Result: self.selection[what] = valid elif action == 'add': add = existing.union(valid) - add_sorted = sorted(add, key=lambda x: int("".join([i for i in x if i.isdigit()]))) + add_sorted = sorted(add, key=natural_sort) self.selection[what] = add_sorted elif action == 'del': diff = existing.difference(valid) - diff_sorted = sorted(diff, key=lambda x: int("".join([i for i in x if i.isdigit()]))) + diff_sorted = sorted(diff, key=natural_sort) self.selection[what] = diff_sorted + def _get_attribute(self,path,attr): + """ + Get the attribute of a dataset. + + Parameters + ---------- + Path : str + Path to the dataset. + attr : str + Name of the attribute to get. + + Returns + ------- + attr at path, str or None. + The requested attribute, None if not found. + + """ + with h5py.File(self.fname,'r') as f: + try: + return f[path].attrs[attr] if h5py3 else f[path].attrs[attr].decode() + except KeyError: + return None + + def allow_modification(self): - print(util.bcolors().WARNING+util.bcolors().BOLD+ - 'Warning: Modification of existing datasets allowed!'+ - util.bcolors().ENDC) + """Allow to overwrite existing data.""" + print(util.warn('Warning: Modification of existing datasets allowed!')) self._allow_modification = True def disallow_modification(self): + """Disllow to overwrite existing data (default case).""" self._allow_modification = False def incs_in_range(self,start,end): + """ + Select all increments within a given range. + + Parameters + ---------- + start : int or str + Start increment. + end : int or str + End increment. + + """ selected = [] for i,inc in enumerate([int(i[3:]) for i in self.increments]): s,e = map(lambda x: int(x[3:] if isinstance(x,str) and x.startswith('inc') else x), (start,end)) @@ -186,6 +221,17 @@ class Result: def times_in_range(self,start,end): + """ + Select all increments within a given time range. + + Parameters + ---------- + start : float + Time of start increment. + end : float + Time of end increment. + + """ selected = [] for i,time in enumerate(self.times): if start <= time <= end: @@ -200,7 +246,7 @@ class Result: Parameters ---------- what : str - attribute to change (must be from self.selection) + Attribute to change (must be from self.selection). """ datasets = self.selection[what] @@ -280,43 +326,41 @@ class Result: for path_old in self.get_dataset_location(name_old): path_new = os.path.join(os.path.dirname(path_old),name_new) f[path_new] = f[path_old] - f[path_new].attrs['Renamed'] = 'Original name: {}'.encode() + f[path_new].attrs['Renamed'] = f'Original name: {name_old}' if h5py3 else \ + f'Original name: {name_old}'.encode() del f[path_old] else: raise PermissionError('Rename operation not permitted') - # def datamerger(regular expression to filter groups into one copy) - - - def place(self,datasets,component=0,tagged=False,split=True): + def place(self,datasets,constituent=0,tagged=False,split=True): """ Distribute datasets onto geometry and return Table or (split) dictionary of Tables. Must not mix nodal end cell data. Only data within - - inc?????/constituent/*_*/* - - inc?????/materialpoint/*_*/* - - inc?????/geometry/* + - inc*/phase/*/* + - inc*/homogenization/*/* + - inc*/geometry/* are considered. Parameters ---------- datasets : iterable or str - component : int - homogenization component to consider for constituent data + constituent : int + Constituent to consider for phase data tagged : bool - tag Table.column name with '#component' + tag Table.column name with '#constituent' defaults to False split : bool split Table by increment and return dictionary of Tables defaults to True """ - sets = datasets if hasattr(datasets,'__iter__') and not isinstance(datasets,str) \ - else [datasets] - tag = f'#{component}' if tagged else '' + sets = datasets if hasattr(datasets,'__iter__') and not isinstance(datasets,str) else \ + [datasets] + tag = f'#{constituent}' if tagged else '' tbl = {} if split else None inGeom = {} inData = {} @@ -328,15 +372,15 @@ class Result: key = '/'.join([prop,name+tag]) if key not in inGeom: if prop == 'geometry': - inGeom[key] = inData[key] = np.arange(self.Nmaterialpoints) - elif prop == 'constituent': - inGeom[key] = np.where(f['mapping/cellResults/constituent'][:,component]['Name'] == str.encode(name))[0] - inData[key] = f['mapping/cellResults/constituent'][inGeom[key],component]['Position'] - else: - inGeom[key] = np.where(f['mapping/cellResults/materialpoint']['Name'] == str.encode(name))[0] - inData[key] = f['mapping/cellResults/materialpoint'][inGeom[key].tolist()]['Position'] + inGeom[key] = inData[key] = np.arange(self.N_materialpoints) + elif prop == 'phase': + inGeom[key] = np.where(f['mapping/phase'][:,constituent]['Name'] == str.encode(name))[0] + inData[key] = f['mapping/phase'][inGeom[key],constituent]['Position'] + elif prop == 'homogenization': + inGeom[key] = np.where(f['mapping/homogenization']['Name'] == str.encode(name))[0] + inData[key] = f['mapping/homogenization'][inGeom[key].tolist()]['Position'] shape = np.shape(f[path]) - data = np.full((self.Nmaterialpoints,) + (shape[1:] if len(shape)>1 else (1,)), + data = np.full((self.N_materialpoints,) + (shape[1:] if len(shape)>1 else (1,)), np.nan, dtype=np.dtype(f[path])) data[inGeom[key]] = (f[path] if len(shape)>1 else np.expand_dims(f[path],1))[inData[key]] @@ -345,12 +389,12 @@ class Result: try: tbl[inc].add(path,data) except KeyError: - tbl[inc] = Table(data.reshape(self.Nmaterialpoints,-1),{path:data.shape[1:]}) + tbl[inc] = Table(data.reshape(self.N_materialpoints,-1),{path:data.shape[1:]}) else: try: tbl.add(path,data) except AttributeError: - tbl = Table(data.reshape(self.Nmaterialpoints,-1),{path:data.shape[1:]}) + tbl = Table(data.reshape(self.N_materialpoints,-1),{path:data.shape[1:]}) return tbl @@ -360,8 +404,8 @@ class Result: Return groups that contain all requested datasets. Only groups within - - inc*/constituent/*/* - - inc*/materialpoint/*/* + - inc*/phase/*/* + - inc*/homogenization/*/* - inc*/geometry/* are considered as they contain user-relevant data. @@ -393,7 +437,7 @@ class Result: with h5py.File(self.fname,'r') as f: for i in self.iterate('increments'): - for o,p in zip(['constituents','materialpoints'],['con_physics','mat_physics']): + for o,p in zip(['phases','homogenizations'],['out_type_ph','out_type_ho']): for oo in self.iterate(o): for pp in self.iterate(p): group = '/'.join([i,o[:-1],oo,pp]) # o[:-1]: plural/singular issue @@ -412,7 +456,7 @@ class Result: with h5py.File(self.fname,'r') as f: for i in self.iterate('increments'): message += f'\n{i} ({self.times[self.increments.index(i)]}s)\n' - for o,p in zip(['constituents','materialpoints'],['con_physics','mat_physics']): + for o,p in zip(['phases','homogenizations'],['out_type_ph','out_type_ho']): message += f' {o[:-1]}\n' for oo in self.iterate(o): message += f' {oo}\n' @@ -422,8 +466,13 @@ class Result: for d in f[group].keys(): try: dataset = f['/'.join([group,d])] - unit = f" / {dataset.attrs['Unit'].decode()}" if 'Unit' in dataset.attrs else '' - description = dataset.attrs['Description'].decode() + if 'Unit' in dataset.attrs: + unit = f" / {dataset.attrs['Unit']}" if h5py3 else \ + f" / {dataset.attrs['Unit'].decode()}" + else: + unit = '' + description = dataset.attrs['Description'] if h5py3 else \ + dataset.attrs['Description'].decode() message += f' {d}{unit}: {description}\n' except KeyError: pass @@ -441,7 +490,7 @@ class Result: path.append(k) except KeyError: pass - for o,p in zip(['constituents','materialpoints'],['con_physics','mat_physics']): + for o,p in zip(['phases','homogenizations'],['out_type_ph','out_type_ho']): for oo in self.iterate(o): for pp in self.iterate(p): k = '/'.join([i,o[:-1],oo,pp,label]) @@ -453,19 +502,6 @@ class Result: return path - def get_constituent_ID(self,c=0): - """Pointwise constituent ID.""" - with h5py.File(self.fname,'r') as f: - names = f['/mapping/cellResults/constituent']['Name'][:,c].astype('str') - return np.array([int(n.split('_')[0]) for n in names.tolist()],dtype=np.int32) - - - def get_crystal_structure(self): # ToDo: extension to multi constituents/phase - """Info about the crystal structure.""" - with h5py.File(self.fname,'r') as f: - return f[self.get_dataset_location('orientation')[0]].attrs['Lattice'].astype('str') # np.bytes_ to string - - def enable_user_function(self,func): globals()[func.__name__]=func print(f'Function {func.__name__} enabled in add_calculation.') @@ -476,9 +512,21 @@ class Result: Dataset for all points/cells. If more than one path is given, the dataset is composed of the individual contributions. + + Parameters + ---------- + path : list of strings + The name of the datasets to consider. + c : int, optional + The constituent to consider. Defaults to 0. + plain: boolean, optional + Convert into plain numpy datatype. + Only relevant for compound datatype, e.g. the orientation. + Defaults to False. + """ with h5py.File(self.fname,'r') as f: - shape = (self.Nmaterialpoints,) + np.shape(f[path[0]])[1:] + shape = (self.N_materialpoints,) + np.shape(f[path[0]])[1:] if len(shape) == 1: shape = shape +(1,) dataset = np.full(shape,np.nan,dtype=np.dtype(f[path[0]])) for pa in path: @@ -488,17 +536,17 @@ class Result: dataset = np.array(f[pa]) continue - p = np.where(f['mapping/cellResults/constituent'][:,c]['Name'] == str.encode(label))[0] + p = np.where(f['mapping/phase'][:,c]['Name'] == str.encode(label))[0] if len(p)>0: - u = (f['mapping/cellResults/constituent']['Position'][p,c]) + u = (f['mapping/phase']['Position'][p,c]) a = np.array(f[pa]) if len(a.shape) == 1: a=a.reshape([a.shape[0],1]) dataset[p,:] = a[u,:] - p = np.where(f['mapping/cellResults/materialpoint']['Name'] == str.encode(label))[0] + p = np.where(f['mapping/homogenization']['Name'] == str.encode(label))[0] if len(p)>0: - u = (f['mapping/cellResults/materialpoint']['Position'][p.tolist()]) + u = (f['mapping/homogenization']['Position'][p.tolist()]) a = np.array(f[pa]) if len(a.shape) == 1: a=a.reshape([a.shape[0],1]) @@ -589,19 +637,19 @@ class Result: @staticmethod - def _add_Cauchy(P,F): + def _add_stress_Cauchy(P,F): return { - 'data': mechanics.Cauchy(P['data'],F['data']), + 'data': mechanics.stress_Cauchy(P['data'],F['data']), 'label': 'sigma', 'meta': { 'Unit': P['meta']['Unit'], 'Description': "Cauchy stress calculated " f"from {P['label']} ({P['meta']['Description']})" f" and {F['label']} ({F['meta']['Description']})", - 'Creator': 'add_Cauchy' + 'Creator': 'add_stress_Cauchy' } } - def add_Cauchy(self,P='P',F='F'): + def add_stress_Cauchy(self,P='P',F='F'): """ Add Cauchy stress calculated from first Piola-Kirchhoff stress and deformation gradient. @@ -613,7 +661,7 @@ class Result: Label of the dataset containing the deformation gradient. Defaults to ‘F’. """ - self._add_generic_pointwise(self._add_Cauchy,{'P':P,'F':F}) + self._add_generic_pointwise(self._add_stress_Cauchy,{'P':P,'F':F}) @staticmethod @@ -643,7 +691,7 @@ class Result: @staticmethod def _add_deviator(T): return { - 'data': mechanics.deviatoric_part(T['data']), + 'data': tensor.deviatoric(T['data']), 'label': f"s_{T['label']}", 'meta': { 'Unit': T['meta']['Unit'], @@ -674,7 +722,7 @@ class Result: label,p = 'Minimum',0 return { - 'data': mechanics.eigenvalues(T_sym['data'])[:,p], + 'data': tensor.eigenvalues(T_sym['data'])[:,p], 'label': f"lambda_{eigenvalue}({T_sym['label']})", 'meta' : { 'Unit': T_sym['meta']['Unit'], @@ -706,7 +754,7 @@ class Result: elif eigenvalue == 'min': label,p = 'minimum',0 return { - 'data': mechanics.eigenvectors(T_sym['data'])[:,p], + 'data': tensor.eigenvectors(T_sym['data'])[:,p], 'label': f"v_{eigenvalue}({T_sym['label']})", 'meta' : { 'Unit': '1', @@ -734,9 +782,11 @@ class Result: @staticmethod def _add_IPF_color(q,l): m = util.scale_to_coprime(np.array(l)) - - o = Orientation(Rotation(rfn.structured_to_unstructured(q['data'])), - lattice = q['meta']['Lattice']) + try: + lattice = {'fcc':'cF','bcc':'cI','hex':'hP'}[q['meta']['Lattice']] + except KeyError: + lattice = q['meta']['Lattice'] + o = Orientation(rotation = (rfn.structured_to_unstructured(q['data'])),lattice=lattice) return { 'data': np.uint8(o.IPF_color(l)*255), @@ -788,20 +838,27 @@ class Result: @staticmethod - def _add_Mises(T_sym): - t = 'strain' if T_sym['meta']['Unit'] == '1' else \ - 'stress' + def _add_equivalent_Mises(T_sym,kind): + k = kind + if k is None: + if T_sym['meta']['Unit'] == '1': + k = 'strain' + elif T_sym['meta']['Unit'] == 'Pa': + k = 'stress' + if k not in ['stress', 'strain']: + raise ValueError('invalid von Mises kind {kind}') return { - 'data': (mechanics.Mises_strain if t=='strain' else mechanics.Mises_stress)(T_sym['data']), + 'data': (mechanics.equivalent_strain_Mises if k=='strain' else \ + mechanics.equivalent_stress_Mises)(T_sym['data']), 'label': f"{T_sym['label']}_vM", 'meta': { 'Unit': T_sym['meta']['Unit'], - 'Description': f"Mises equivalent {t} of {T_sym['label']} ({T_sym['meta']['Description']})", + 'Description': f"Mises equivalent {k} of {T_sym['label']} ({T_sym['meta']['Description']})", 'Creator': 'add_Mises' } } - def add_Mises(self,T_sym): + def add_equivalent_Mises(self,T_sym,kind=None): """ Add the equivalent Mises stress or strain of a symmetric tensor. @@ -809,9 +866,12 @@ class Result: ---------- T_sym : str Label of symmetric tensorial stress or strain dataset. + kind : {'stress', 'strain', None}, optional + Kind of the von Mises equivalent. Defaults to None, in which case + it is selected based on the unit of the dataset ('1' -> strain, 'Pa' -> stress'). """ - self._add_generic_pointwise(self._add_Mises,{'T_sym':T_sym}) + self._add_generic_pointwise(self._add_equivalent_Mises,{'T_sym':T_sym},{'kind':kind}) @staticmethod @@ -853,19 +913,19 @@ class Result: @staticmethod - def _add_PK2(P,F): + def _add_stress_second_Piola_Kirchhoff(P,F): return { - 'data': mechanics.PK2(P['data'],F['data']), + 'data': mechanics.stress_second_Piola_Kirchhoff(P['data'],F['data']), 'label': 'S', 'meta': { 'Unit': P['meta']['Unit'], 'Description': "2. Piola-Kirchhoff stress calculated " f"from {P['label']} ({P['meta']['Description']})" f" and {F['label']} ({F['meta']['Description']})", - 'Creator': 'add_PK2' + 'Creator': 'add_stress_second_Piola_Kirchhoff' } } - def add_PK2(self,P='P',F='F'): + def add_stress_second_Piola_Kirchhoff(self,P='P',F='F'): """ Add second Piola-Kirchhoff stress calculated from first Piola-Kirchhoff stress and deformation gradient. @@ -877,59 +937,64 @@ class Result: Label of deformation gradient dataset. Defaults to ‘F’. """ - self._add_generic_pointwise(self._add_PK2,{'P':P,'F':F}) + self._add_generic_pointwise(self._add_stress_second_Piola_Kirchhoff,{'P':P,'F':F}) + + +# The add_pole functionality needs discussion. +# The new Crystal object can perform such a calculation but the outcome depends on the lattice parameters +# as well as on whether a direction or plane is concerned (see the DAMASK_examples/pole_figure notebook). +# Below code appears to be too simplistic. + + # @staticmethod + # def _add_pole(q,p,polar): + # pole = np.array(p) + # unit_pole = pole/np.linalg.norm(pole) + # m = util.scale_to_coprime(pole) + # rot = Rotation(q['data'].view(np.double).reshape(-1,4)) + # + # rotatedPole = rot @ np.broadcast_to(unit_pole,rot.shape+(3,)) # rotate pole according to crystal orientation + # xy = rotatedPole[:,0:2]/(1.+abs(unit_pole[2])) # stereographic projection + # coords = xy if not polar else \ + # np.block([np.sqrt(xy[:,0:1]*xy[:,0:1]+xy[:,1:2]*xy[:,1:2]),np.arctan2(xy[:,1:2],xy[:,0:1])]) + # return { + # 'data': coords, + # 'label': 'p^{}_[{} {} {})'.format(u'rφ' if polar else 'xy',*m), + # 'meta' : { + # 'Unit': '1', + # 'Description': '{} coordinates of stereographic projection of pole (direction/plane) in crystal frame'\ + # .format('Polar' if polar else 'Cartesian'), + # 'Creator': 'add_pole' + # } + # } + # def add_pole(self,q,p,polar=False): + # """ + # Add coordinates of stereographic projection of given pole in crystal frame. + # + # Parameters + # ---------- + # q : str + # Label of the dataset containing the crystallographic orientation as quaternions. + # p : numpy.array of shape (3) + # Crystallographic direction or plane. + # polar : bool, optional + # Give pole in polar coordinates. Defaults to False. + # + # """ + # self._add_generic_pointwise(self._add_pole,{'q':q},{'p':p,'polar':polar}) @staticmethod - def _add_pole(q,p,polar): - pole = np.array(p) - unit_pole = pole/np.linalg.norm(pole) - m = util.scale_to_coprime(pole) - rot = Rotation(q['data'].view(np.double).reshape(-1,4)) - - rotatedPole = rot @ np.broadcast_to(unit_pole,rot.shape+(3,)) # rotate pole according to crystal orientation - xy = rotatedPole[:,0:2]/(1.+abs(unit_pole[2])) # stereographic projection - coords = xy if not polar else \ - np.block([np.sqrt(xy[:,0:1]*xy[:,0:1]+xy[:,1:2]*xy[:,1:2]),np.arctan2(xy[:,1:2],xy[:,0:1])]) + def _add_rotation(F): return { - 'data': coords, - 'label': 'p^{}_[{} {} {})'.format(u'rφ' if polar else 'xy',*m), - 'meta' : { - 'Unit': '1', - 'Description': '{} coordinates of stereographic projection of pole (direction/plane) in crystal frame'\ - .format('Polar' if polar else 'Cartesian'), - 'Creator': 'add_pole' - } - } - def add_pole(self,q,p,polar=False): - """ - Add coordinates of stereographic projection of given pole in crystal frame. - - Parameters - ---------- - q : str - Label of the dataset containing the crystallographic orientation as quaternions. - p : numpy.array of shape (3) - Crystallographic direction or plane. - polar : bool, optional - Give pole in polar coordinates. Defaults to False. - - """ - self._add_generic_pointwise(self._add_pole,{'q':q},{'p':p,'polar':polar}) - - - @staticmethod - def _add_rotational_part(F): - return { - 'data': mechanics.rotational_part(F['data']), + 'data': mechanics.rotation(F['data']).as_matrix(), 'label': f"R({F['label']})", 'meta': { 'Unit': F['meta']['Unit'], 'Description': f"Rotational part of {F['label']} ({F['meta']['Description']})", - 'Creator': 'add_rotational_part' + 'Creator': 'add_rotation' } } - def add_rotational_part(self,F): + def add_rotation(self,F): """ Add rotational part of a deformation gradient. @@ -939,13 +1004,13 @@ class Result: Label of deformation gradient dataset. """ - self._add_generic_pointwise(self._add_rotational_part,{'F':F}) + self._add_generic_pointwise(self._add_rotation,{'F':F}) @staticmethod def _add_spherical(T): return { - 'data': mechanics.spherical_part(T['data']), + 'data': tensor.spherical(T['data'],False), 'label': f"p_{T['label']}", 'meta': { 'Unit': T['meta']['Unit'], @@ -967,21 +1032,21 @@ class Result: @staticmethod - def _add_strain_tensor(F,t,m): + def _add_strain(F,t,m): return { - 'data': mechanics.strain_tensor(F['data'],t,m), + 'data': mechanics.strain(F['data'],t,m), 'label': f"epsilon_{t}^{m}({F['label']})", 'meta': { 'Unit': F['meta']['Unit'], 'Description': f"Strain tensor of {F['label']} ({F['meta']['Description']})", - 'Creator': 'add_strain_tensor' + 'Creator': 'add_strain' } } - def add_strain_tensor(self,F='F',t='V',m=0.0): + def add_strain(self,F='F',t='V',m=0.0): """ Add strain tensor of a deformation gradient. - For details refer to damask.mechanics.strain_tensor + For details refer to damask.mechanics.strain Parameters ---------- @@ -994,13 +1059,13 @@ class Result: Order of the strain calculation. Defaults to ‘0.0’. """ - self._add_generic_pointwise(self._add_strain_tensor,{'F':F},{'t':t,'m':m}) + self._add_generic_pointwise(self._add_strain,{'F':F},{'t':t,'m':m}) @staticmethod def _add_stretch_tensor(F,t): return { - 'data': (mechanics.left_stretch if t.upper() == 'V' else mechanics.right_stretch)(F['data']), + 'data': (mechanics.stretch_left if t.upper() == 'V' else mechanics.stretch_right)(F['data']), 'label': f"{t}({F['label']})", 'meta': { 'Unit': F['meta']['Unit'], @@ -1035,7 +1100,7 @@ class Result: loc = f[group+'/'+label] datasets_in[arg]={'data' :loc[()], 'label':label, - 'meta': {k:v.decode() for k,v in loc.attrs.items()}} + 'meta': {k:(v if h5py3 else v.decode()) for k,v in loc.attrs.items()}} lock.release() r = func(**datasets_in,**args) return [group,r] @@ -1080,17 +1145,21 @@ class Result: if self._allow_modification and result[0]+'/'+result[1]['label'] in f: dataset = f[result[0]+'/'+result[1]['label']] dataset[...] = result[1]['data'] - dataset.attrs['Overwritten'] = 'Yes'.encode() + dataset.attrs['Overwritten'] = 'Yes' if h5py3 else \ + 'Yes'.encode() else: dataset = f[result[0]].create_dataset(result[1]['label'],data=result[1]['data']) now = datetime.datetime.now().astimezone() - dataset.attrs['Created'] = now.strftime('%Y-%m-%d %H:%M:%S%z').encode() + dataset.attrs['Created'] = now.strftime('%Y-%m-%d %H:%M:%S%z') if h5py3 else \ + now.strftime('%Y-%m-%d %H:%M:%S%z').encode() for l,v in result[1]['meta'].items(): - dataset.attrs[l]=v.encode() - creator = f"damask.Result.{dataset.attrs['Creator'].decode()} v{damask.version}" - dataset.attrs['Creator'] = creator.encode() + dataset.attrs[l]=v if h5py3 else v.encode() + creator = dataset.attrs['Creator'] if h5py3 else \ + dataset.attrs['Creator'].decode() + dataset.attrs['Creator'] = f"damask.Result.{creator} v{damask.version}" if h5py3 else \ + f"damask.Result.{creator} v{damask.version}".encode() except (OSError,RuntimeError) as err: print(f'Could not add dataset: {err}.') @@ -1107,7 +1176,7 @@ class Result: This works only for scalar, 3-vector and 3x3-tensor data. Selection is not taken into account. """ - if len(self.constituents) != 1 or not self.structured: + if self.N_constituents != 1 or not self.structured: raise NotImplementedError('XDMF only available for grid results with 1 constituent.') xdmf=ET.Element('Xdmf') @@ -1158,9 +1227,11 @@ class Result: delta.text="{} {} {}".format(*(self.size/self.grid)) + type_map = defaultdict(lambda:'Matrix', ( ((),'Scalar'), ((3,),'Vector'), ((3,3),'Tensor')) ) + with h5py.File(self.fname,'r') as f: attributes.append(ET.SubElement(grid, 'Attribute')) - attributes[-1].attrib={'Name': 'u', + attributes[-1].attrib={'Name': 'u / m', 'Center': 'Node', 'AttributeType': 'Vector'} data_items.append(ET.SubElement(attributes[-1], 'DataItem')) @@ -1169,7 +1240,7 @@ class Result: 'Dimensions': '{} {} {} 3'.format(*(self.grid+1))} data_items[-1].text=f'{os.path.split(self.fname)[1]}:/{inc}/geometry/u_n' - for o,p in zip(['constituents','materialpoints'],['con_physics','mat_physics']): + for o,p in zip(['phases','homogenizations'],['out_type_ph','out_type_ho']): for oo in getattr(self,o): for pp in getattr(self,p): g = '/'.join([inc,o[:-1],oo,pp]) @@ -1177,19 +1248,21 @@ class Result: name = '/'.join([g,l]) shape = f[name].shape[1:] dtype = f[name].dtype - prec = f[name].dtype.itemsize - if (shape not in [(1,), (3,), (3,3)]) or dtype != np.float64: continue + if dtype != np.float64: continue + prec = f[name].dtype.itemsize + unit = f[name].attrs['Unit'] if h5py3 else f[name].attrs['Unit'].decode() attributes.append(ET.SubElement(grid, 'Attribute')) - attributes[-1].attrib={'Name': name.split('/',2)[2], - 'Center': 'Cell', - 'AttributeType': 'Tensor'} + attributes[-1].attrib={'Name': name.split('/',2)[2]+f' / {unit}', + 'Center': 'Cell', + 'AttributeType': type_map[shape]} data_items.append(ET.SubElement(attributes[-1], 'DataItem')) data_items[-1].attrib={'Format': 'HDF', 'NumberType': 'Float', 'Precision': f'{prec}', - 'Dimensions': '{} {} {} {}'.format(*self.grid,np.prod(shape))} + 'Dimensions': '{} {} {} {}'.format(*self.grid,1 if shape == () else + np.prod(shape))} data_items[-1].text=f'{os.path.split(self.fname)[1]}:{name}' with open(self.fname.with_suffix('.xdmf').name,'w') as f: @@ -1212,58 +1285,53 @@ class Result: if mode.lower()=='cell': if self.structured: - v = VTK.from_rectilinearGrid(self.grid,self.size,self.origin) + v = VTK.from_rectilinear_grid(self.grid,self.size,self.origin) else: with h5py.File(self.fname,'r') as f: - v = VTK.from_unstructuredGrid(f['/geometry/x_n'][()], - f['/geometry/T_c'][()]-1, - f['/geometry/T_c'].attrs['VTK_TYPE'].decode()) + v = VTK.from_unstructured_grid(f['/geometry/x_n'][()], + f['/geometry/T_c'][()]-1, + f['/geometry/T_c'].attrs['VTK_TYPE'] if h5py3 else \ + f['/geometry/T_c'].attrs['VTK_TYPE'].decode()) elif mode.lower()=='point': - v = VTK.from_polyData(self.cell_coordinates()) + v = VTK.from_poly_data(self.cell_coordinates) N_digits = int(np.floor(np.log10(max(1,int(self.increments[-1][3:])))))+1 for inc in util.show_progress(self.iterate('increments'),len(self.selection['increments'])): - materialpoints_backup = self.selection['materialpoints'].copy() - self.pick('materialpoints',False) + picked_backup_ho = self.selection['homogenizations'].copy() + self.pick('homogenizations',False) for label in (labels if isinstance(labels,list) else [labels]): - for p in self.iterate('con_physics'): - if p != 'generic': - for c in self.iterate('constituents'): - x = self.get_dataset_location(label) - if len(x) == 0: + for o in self.iterate('out_type_ph'): + for c in range(self.N_constituents): + prefix = '' if self.N_constituents == 1 else f'constituent{c}/' + if o != 'mechanics': + for _ in self.iterate('phases'): + path = self.get_dataset_location(label) + if len(path) == 0: + continue + array = self.read_dataset(path,c) + v.add(array,prefix+path[0].split('/',1)[1]+f' / {self._get_attribute(path[0],"Unit")}') + else: + paths = self.get_dataset_location(label) + if len(paths) == 0: continue - array = self.read_dataset(x,0) - v.add(array,'1_'+x[0].split('/',1)[1]) #ToDo: hard coded 1! - else: - x = self.get_dataset_location(label) - if len(x) == 0: - continue - array = self.read_dataset(x,0) - ph_name = re.compile(r'(?<=(constituent\/))(.*?)(?=(generic))') # identify phase name - dset_name = '1_' + re.sub(ph_name,r'',x[0].split('/',1)[1]) # removing phase name - v.add(array,dset_name) - self.pick('materialpoints',materialpoints_backup) + array = self.read_dataset(paths,c) + ph_name = re.compile(r'(?<=(phase\/))(.*?)(?=(mechanics))') # identify phase name + dset_name = prefix+re.sub(ph_name,r'',paths[0].split('/',1)[1]) # remove phase name + v.add(array,dset_name+f' / {self._get_attribute(paths[0],"Unit")}') + self.pick('homogenizations',picked_backup_ho) - constituents_backup = self.selection['constituents'].copy() - self.pick('constituents',False) + picked_backup_ph = self.selection['phases'].copy() + self.pick('phases',False) for label in (labels if isinstance(labels,list) else [labels]): - for p in self.iterate('mat_physics'): - if p != 'generic': - for m in self.iterate('materialpoints'): - x = self.get_dataset_location(label) - if len(x) == 0: - continue - array = self.read_dataset(x,0) - v.add(array,'1_'+x[0].split('/',1)[1]) #ToDo: why 1_? - else: - x = self.get_dataset_location(label) - if len(x) == 0: - continue - array = self.read_dataset(x,0) - v.add(array,'1_'+x[0].split('/',1)[1]) - self.pick('constituents',constituents_backup) + for _ in self.iterate('out_type_ho'): + paths = self.get_dataset_location(label) + if len(paths) == 0: + continue + array = self.read_dataset(paths) + v.add(array,paths[0].split('/',1)[1]+f' / {self._get_attribute(paths[0],"Unit")}') + self.pick('phases',picked_backup_ph) u = self.read_dataset(self.get_dataset_location('u_n' if mode.lower() == 'cell' else 'u_p')) v.add(u,'u') diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index b0c9dab5b..5fb8109c3 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -1,6 +1,6 @@ import numpy as np -from . import mechanics +from . import tensor from . import util from . import grid_filters @@ -13,18 +13,18 @@ _R1 = (3.*np.pi/4.)**(1./3.) class Rotation: u""" - Orientation stored with functionality for conversion to different representations. + Rotation with functionality for conversion between different representations. The following conventions apply: - - coordinate frames are right-handed. - - a rotation angle ω is taken to be positive for a counterclockwise rotation + - Coordinate frames are right-handed. + - A rotation angle ω is taken to be positive for a counterclockwise rotation when viewing from the end point of the rotation axis towards the origin. - - rotations will be interpreted in the passive sense. + - Rotations will be interpreted in the passive sense. - Euler angle triplets are implemented using the Bunge convention, - with the angular ranges as [0,2π], [0,π], [0,2π]. - - the rotation angle ω is limited to the interval [0,π]. - - the real part of a quaternion is positive, Re(q) > 0 + with angular ranges of [0,2π], [0,π], [0,2π]. + - The rotation angle ω is limited to the interval [0,π]. + - The real part of a quaternion is positive, Re(q) > 0 - P = -1 (as default). Examples @@ -33,7 +33,7 @@ class Rotation: coordinates "b" expressed in system "B": - b = Q @ a - - b = np.dot(Q.asMatrix(),a) + - b = np.dot(Q.as_matrix(),a) References ---------- @@ -44,20 +44,70 @@ class Rotation: __slots__ = ['quaternion'] - def __init__(self,quaternion = np.array([1.0,0.0,0.0,0.0])): + def __init__(self,rotation = np.array([1.0,0.0,0.0,0.0])): """ - Initializes to identity unless specified. + Initialize rotation object. Parameters ---------- - quaternion : numpy.ndarray, optional + rotation : list, numpy.ndarray, Rotation, optional Unit quaternion in positive real hemisphere. Use .from_quaternion to perform a sanity check. + Defaults to no rotation. """ - if quaternion.shape[-1] != 4: - raise ValueError('Not a quaternion') - self.quaternion = quaternion.copy() + if isinstance(rotation,Rotation): + self.quaternion = rotation.quaternion.copy() + elif np.array(rotation).shape[-1] == 4: + self.quaternion = np.array(rotation) + else: + raise TypeError('"rotation" is neither a Rotation nor a quaternion') + + + def __repr__(self): + """Represent rotation as unit quaternion, rotation matrix, and Bunge-Euler angles.""" + if self == Rotation(): + return 'Rotation()' + else: + return f'Quaternions {self.shape}:\n'+str(self.quaternion) \ + if self.quaternion.shape != (4,) else \ + '\n'.join([ + 'Quaternion: (real={:.3f}, imag=<{:+.3f}, {:+.3f}, {:+.3f}>)'.format(*(self.quaternion)), + 'Matrix:\n{}'.format(np.round(self.as_matrix(),8)), + 'Bunge Eulers / deg: ({:3.2f}, {:3.2f}, {:3.2f})'.format(*self.as_Euler_angles(degrees=True)), + ]) + + + # ToDo: Check difference __copy__ vs __deepcopy__ + def __copy__(self,**kwargs): + """Copy.""" + return self.__class__(rotation=kwargs['rotation'] if 'rotation' in kwargs else self.quaternion) + + copy = __copy__ + + + def __getitem__(self,item): + """Return slice according to item.""" + return self.copy() \ + if self.shape == () else \ + self.copy(rotation=self.quaternion[item+(slice(None),)] if isinstance(item,tuple) else self.quaternion[item]) + + + def __eq__(self,other): + """ + Equal to other. + + Equality is determined taking limited floating point precision into + account. See numpy.allclose for details. + + Parameters + ---------- + other : Rotation + Rotation to check for equality. + + """ + return np.prod(self.shape,dtype=int) == np.prod(other.shape,dtype=int) \ + and np.allclose(self.quaternion,other.quaternion) @property @@ -65,39 +115,36 @@ class Rotation: return self.quaternion.shape[:-1] - # ToDo: Check difference __copy__ vs __deepcopy__ - def __copy__(self): - """Copy.""" - return self.__class__(self.quaternion) - - copy = __copy__ - - - def __repr__(self): - """Orientation displayed as unit quaternion, rotation matrix, and Bunge-Euler angles.""" - if self.quaternion.shape != (4,): - return 'Quaternions:\n'+str(self.quaternion) # ToDo: could be nicer ... - return '\n'.join([ - 'Quaternion: (real={:.3f}, imag=<{:+.3f}, {:+.3f}, {:+.3f}>)'.format(*(self.quaternion)), - 'Matrix:\n{}'.format(np.round(self.as_matrix(),8)), - 'Bunge Eulers / deg: ({:3.2f}, {:3.2f}, {:3.2f})'.format(*self.as_Eulers(degrees=True)), - ]) - - - def __getitem__(self,item): - """Iterate over leading/leftmost dimension of Rotation array.""" - if self.shape == (): return self.copy() - if isinstance(item,tuple) and len(item) >= len(self): - raise IndexError('Too many indices') - return self.__class__(self.quaternion[item]) - - def __len__(self): """Length of leading/leftmost dimension of Rotation array.""" return 0 if self.shape == () else self.shape[0] - def __matmul__(self, other): + def __invert__(self): + """Inverse rotation (backward rotation).""" + dup = self.copy() + dup.quaternion[...,1:] *= -1 + return dup + + + def __pow__(self,pwr): + """ + Raise quaternion to power. + + Equivalent to performing the rotation 'pwr' times. + + Parameters + ---------- + pwr : float + Power to raise quaternion to. + + """ + phi = np.arccos(self.quaternion[...,0:1]) + p = self.quaternion[...,1:]/np.linalg.norm(self.quaternion[...,1:],axis=-1,keepdims=True) + return self.copy(rotation=Rotation(np.block([np.cos(pwr*phi),np.sin(pwr*phi)*p]))._standardize()) + + + def __matmul__(self,other): """ Rotation of vector, second or fourth order tensor, or rotation object. @@ -112,14 +159,14 @@ class Rotation: Rotated vector, second or fourth order tensor, or rotation object. """ - if isinstance(other, Rotation): + if isinstance(other,Rotation): q_m = self.quaternion[...,0:1] p_m = self.quaternion[...,1:] q_o = other.quaternion[...,0:1] p_o = other.quaternion[...,1:] q = (q_m*q_o - np.einsum('...i,...i',p_m,p_o).reshape(self.shape+(1,))) p = q_m*p_o + q_o*p_m + _P * np.cross(p_m,p_o) - return self.__class__(np.block([q,p]))._standardize() + return Rotation(np.block([q,p]))._standardize() elif isinstance(other,np.ndarray): if self.shape + (3,) == other.shape: @@ -146,27 +193,89 @@ class Rotation: def _standardize(self): - """Standardize (ensure positive real hemisphere).""" + """Standardize quaternion (ensure positive real hemisphere).""" self.quaternion[self.quaternion[...,0] < 0.0] *= -1 return self - def inverse(self): - """In-place inverse rotation (backward rotation).""" - self.quaternion[...,1:] *= -1 - return self - def __invert__(self): - """Inverse rotation (backward rotation).""" - return self.copy().inverse() + def append(self,other): + """Extend rotation array along first dimension with other array.""" + return self.copy(rotation=np.vstack((self.quaternion,other.quaternion))) - def inversed(self): - """Inverse rotation (backward rotation).""" - return ~ self + + def flatten(self,order = 'C'): + """Flatten quaternion array.""" + return self.copy(rotation=self.quaternion.reshape((-1,4),order=order)) + + + def reshape(self,shape,order = 'C'): + """Reshape quaternion array.""" + if isinstance(shape,(int,np.integer)): shape = (shape,) + return self.copy(rotation=self.quaternion.reshape(tuple(shape)+(4,),order=order)) + + + def broadcast_to(self,shape,mode = 'right'): + """ + Broadcast quaternion array to shape. + + Parameters + ---------- + shape : tuple + Shape of broadcasted array. + mode : str, optional + Where to preferentially locate missing dimensions. + Either 'left' or 'right' (default). + + """ + if isinstance(shape,(int,np.integer)): shape = (shape,) + return self.copy(rotation=np.broadcast_to(self.quaternion.reshape(util.shapeshifter(self.shape,shape,mode)+(4,)), + shape+(4,))) + + + def average(self,weights = None): + """ + Average rotations along last dimension. + + Parameters + ---------- + weights : list of floats, optional + Relative weight of each rotation. + + Returns + ------- + average : Rotation + Weighted average of original Rotation field. + + References + ---------- + Quaternion averaging + F. Landis Markley, Yang Cheng, John L. Crassidis, Yaakov Oshman + Journal of Guidance, Control, and Dynamics 30(4):1193-1197, 2007 + 10.2514/1.28949 + + """ + def _M(quat): + """Intermediate representation supporting quaternion averaging.""" + return np.einsum('...i,...j',quat,quat) + + if not weights: + weights = np.ones(self.shape,dtype=float) + + eig, vec = np.linalg.eig(np.sum(_M(self.quaternion) * weights[...,np.newaxis,np.newaxis],axis=-3) \ + /np.sum( weights[...,np.newaxis,np.newaxis],axis=-3)) + + return Rotation.from_quaternion(np.real( + np.squeeze( + np.take_along_axis(vec, + eig.argmax(axis=-1)[...,np.newaxis,np.newaxis], + axis=-1), + axis=-1)), + accept_homomorph = True) def misorientation(self,other): """ - Get Misorientation. + Calculate misorientation from self to other Rotation. Parameters ---------- @@ -177,33 +286,6 @@ class Rotation: return other@~self - def broadcast_to(self,shape): - if isinstance(shape,(int,np.integer)): shape = (shape,) - if self.shape == (): - q = np.broadcast_to(self.quaternion,shape+(4,)) - else: - q = np.block([np.broadcast_to(self.quaternion[...,0:1],shape).reshape(shape+(1,)), - np.broadcast_to(self.quaternion[...,1:2],shape).reshape(shape+(1,)), - np.broadcast_to(self.quaternion[...,2:3],shape).reshape(shape+(1,)), - np.broadcast_to(self.quaternion[...,3:4],shape).reshape(shape+(1,))]) - return self.__class__(q) - - - def average(self,other): #ToDo: discuss calling for vectors - """ - Calculate the average rotation. - - Parameters - ---------- - other : Rotation - Rotation from which the average is rotated. - - """ - if self.quaternion.shape != (4,) or other.quaternion.shape != (4,): - raise NotImplementedError('Support for multiple rotations missing') - return Rotation.from_average([self,other]) - - ################################################################################################ # convert to different orientation representations (numpy arrays) @@ -219,8 +301,8 @@ class Rotation: """ return self.quaternion.copy() - def as_Eulers(self, - degrees = False): + def as_Euler_angles(self, + degrees = False): """ Represent as Bunge-Euler angles. @@ -277,8 +359,8 @@ class Rotation: """ return Rotation._qu2om(self.quaternion) - def as_Rodrigues(self, - vector = False): + def as_Rodrigues_vector(self, + compact = False): """ Represent as Rodrigues-Frank vector with separated axis and angle argument. @@ -296,7 +378,7 @@ class Rotation: """ ro = Rotation._qu2ro(self.quaternion) - if vector: + if compact: with np.errstate(invalid='ignore'): return ro[...,:3]*ro[...,3:4] else: @@ -309,7 +391,7 @@ class Rotation: Returns ------- h : numpy.ndarray of shape (...,3) - Homochoric vector: (h_1, h_2, h_3), ǀhǀ < 1/2*π^(2/3). + Homochoric vector: (h_1, h_2, h_3), ǀhǀ < (3/4*π)^(1/3). """ return Rotation._qu2ho(self.quaternion) @@ -326,20 +408,6 @@ class Rotation: """ return Rotation._qu2cu(self.quaternion) - @property - def M(self): # ToDo not sure about the name: as_M or M? we do not have a from_M - """ - Intermediate representation supporting quaternion averaging. - - References - ---------- - F. Landis Markley et al., Journal of Guidance, Control, and Dynamics 30(4):1193-1197, 2007 - https://doi.org/10.2514/1.28949 - - """ - return np.einsum('...i,...j',self.quaternion,self.quaternion) - - ################################################################################################ # Static constructors. The input data needs to follow the conventions, options allow to # relax the conventions. @@ -347,7 +415,7 @@ class Rotation: def from_quaternion(q, accept_homomorph = False, P = -1, - acceptHomomorph = None): # old name (for compatibility) + **kwargs): """ Initialize from quaternion. @@ -363,15 +431,13 @@ class Rotation: Convention used. Defaults to -1. """ - if acceptHomomorph is not None: - accept_homomorph = acceptHomomorph # for compatibility qu = np.array(q,dtype=float) if qu.shape[:-2:-1] != (4,): raise ValueError('Invalid shape.') if abs(P) != 1: raise ValueError('P ∉ {-1,1}') - if P == 1: qu[...,1:4] *= -1 + qu[...,1:4] *= -P if accept_homomorph: qu[qu[...,0] < 0.0] *= -1 else: @@ -383,8 +449,9 @@ class Rotation: return Rotation(qu) @staticmethod - def from_Eulers(phi, - degrees = False): + def from_Euler_angles(phi, + degrees = False, + **kwargs): """ Initialize from Bunge-Euler angles. @@ -411,7 +478,8 @@ class Rotation: def from_axis_angle(axis_angle, degrees = False, normalize = False, - P = -1): + P = -1, + **kwargs): """ Initialize from Axis angle pair. @@ -434,7 +502,7 @@ class Rotation: if abs(P) != 1: raise ValueError('P ∉ {-1,1}') - if P == 1: ax[...,0:3] *= -1 + ax[...,0:3] *= -P if degrees: ax[..., 3] = np.radians(ax[...,3]) if normalize: ax[...,0:3] /= np.linalg.norm(ax[...,0:3],axis=-1,keepdims=True) if np.any(ax[...,3] < 0.0) or np.any(ax[...,3] > np.pi): @@ -448,14 +516,15 @@ class Rotation: @staticmethod def from_basis(basis, orthonormal = True, - reciprocal = False): + reciprocal = False, + **kwargs): """ Initialize from lattice basis vectors. Parameters ---------- basis : numpy.ndarray of shape (...,3,3) - Three lattice basis vectors in three dimensions. + Three three-dimensional lattice basis vectors. orthonormal : boolean, optional Basis is strictly orthonormal, i.e. is free of stretch components. Defaults to True. reciprocal : boolean, optional @@ -463,15 +532,15 @@ class Rotation: """ om = np.array(basis,dtype=float) - if om.shape[:-3:-1] != (3,3): + if om.shape[-2:] != (3,3): raise ValueError('Invalid shape.') if reciprocal: - om = np.linalg.inv(mechanics.transpose(om)/np.pi) # transform reciprocal basis set + om = np.linalg.inv(tensor.transpose(om)/np.pi) # transform reciprocal basis set orthonormal = False # contains stretch if not orthonormal: (U,S,Vh) = np.linalg.svd(om) # singular value decomposition - om = np.einsum('...ij,...jl->...il',U,Vh) + om = np.einsum('...ij,...jl',U,Vh) if not np.all(np.isclose(np.linalg.det(om),1.0)): raise ValueError('Orientation matrix has determinant ≠ 1.') if not np.all(np.isclose(np.einsum('...i,...i',om[...,0],om[...,1]), 0.0)) \ @@ -482,7 +551,7 @@ class Rotation: return Rotation(Rotation._om2qu(om)) @staticmethod - def from_matrix(R): + def from_matrix(R,**kwargs): """ Initialize from rotation matrix. @@ -495,17 +564,46 @@ class Rotation: return Rotation.from_basis(R) @staticmethod - def from_Rodrigues(rho, - normalize = False, - P = -1): + def from_parallel(a,b, + **kwargs): """ - Initialize from Rodrigues-Frank vector. + Initialize from pairs of two orthogonal lattice basis vectors. + + Parameters + ---------- + a : numpy.ndarray of shape (...,2,3) + Two three-dimensional lattice vectors of first orthogonal basis. + b : numpy.ndarray of shape (...,2,3) + Corresponding three-dimensional lattice vectors of second basis. + + """ + a_ = np.array(a) + b_ = np.array(b) + if a_.shape[-2:] != (2,3) or b_.shape[-2:] != (2,3) or a_.shape != b_.shape: + raise ValueError('Invalid shape.') + am = np.stack([ a_[...,0,:], + a_[...,1,:], + np.cross(a_[...,0,:],a_[...,1,:]) ],axis=-2) + bm = np.stack([ b_[...,0,:], + b_[...,1,:], + np.cross(b_[...,0,:],b_[...,1,:]) ],axis=-2) + + return Rotation.from_basis(np.swapaxes(am/np.linalg.norm(am,axis=-1,keepdims=True),-1,-2))\ + .misorientation(Rotation.from_basis(np.swapaxes(bm/np.linalg.norm(bm,axis=-1,keepdims=True),-1,-2))) + + + @staticmethod + def from_Rodrigues_vector(rho, + normalize = False, + P = -1, + **kwargs): + """ + Initialize from Rodrigues-Frank vector (angle separated from axis). Parameters ---------- rho : numpy.ndarray of shape (...,4) - Rodrigues-Frank vector (angle separated from axis). - (n_1, n_2, n_3, tan(ω/2)), ǀnǀ = 1 and ω ∈ [0,π]. + Rodrigues-Frank vector. (n_1, n_2, n_3, tan(ω/2)), ǀnǀ = 1 and ω ∈ [0,π]. normalize : boolean, optional Allow ǀnǀ ≠ 1. Defaults to False. P : int ∈ {-1,1}, optional @@ -518,7 +616,7 @@ class Rotation: if abs(P) != 1: raise ValueError('P ∉ {-1,1}') - if P == 1: ro[...,0:3] *= -1 + ro[...,0:3] *= -P if normalize: ro[...,0:3] /= np.linalg.norm(ro[...,0:3],axis=-1,keepdims=True) if np.any(ro[...,3] < 0.0): raise ValueError('Rodrigues vector rotation angle not positive.') @@ -529,7 +627,8 @@ class Rotation: @staticmethod def from_homochoric(h, - P = -1): + P = -1, + **kwargs): """ Initialize from homochoric vector. @@ -547,7 +646,7 @@ class Rotation: if abs(P) != 1: raise ValueError('P ∉ {-1,1}') - if P == 1: ho *= -1 + ho *= -P if np.any(np.linalg.norm(ho,axis=-1) >_R1+1e-9): raise ValueError('Homochoric coordinate outside of the sphere.') @@ -556,7 +655,8 @@ class Rotation: @staticmethod def from_cubochoric(c, - P = -1): + P = -1, + **kwargs): """ Initialize from cubochoric vector. @@ -577,46 +677,15 @@ class Rotation: if np.abs(np.max(cu)) > np.pi**(2./3.) * 0.5+1e-9: raise ValueError('Cubochoric coordinate outside of the cube.') - ho = Rotation._cu2ho(cu) - if P == 1: ho *= -1 + ho = -P * Rotation._cu2ho(cu) return Rotation(Rotation._ho2qu(ho)) @staticmethod - def from_average(rotations,weights = None): - """ - Average rotation. - - References - ---------- - F. Landis Markley et al., Journal of Guidance, Control, and Dynamics 30(4):1193-1197, 2007 - https://doi.org/10.2514/1.28949 - - Parameters - ---------- - rotations : list of Rotations - Rotations to average from - weights : list of floats, optional - Weights for each rotation used for averaging - - """ - if not all(isinstance(item, Rotation) for item in rotations): - raise TypeError('Only instances of Rotation can be averaged.') - - N = len(rotations) - if not weights: - weights = np.ones(N,dtype='i') - - for i,(r,n) in enumerate(zip(rotations,weights)): - M = r.M * n if i == 0 \ - else M + r.M * n # noqa add (multiples) of this rotation to average noqa - eig, vec = np.linalg.eig(M/N) - - return Rotation.from_quaternion(np.real(vec.T[eig.argmax()]),accept_homomorph = True) - - @staticmethod - def from_random(shape=None,seed=None): + def from_random(shape = None, + rng_seed = None, + **kwargs): """ Draw random rotation. @@ -627,18 +696,13 @@ class Rotation: shape : tuple of ints, optional Shape of the sample. Defaults to None which gives a single rotation - seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional + rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional A seed to initialize the BitGenerator. Defaults to None. If None, then fresh, unpredictable entropy will be pulled from the OS. """ - rng = np.random.default_rng(seed) - if shape is None: - r = rng.random(3) - elif hasattr(shape, '__iter__'): - r = rng.random(tuple(shape)+(3,)) - else: - r = rng.random((shape,3)) + rng = np.random.default_rng(rng_seed) + r = rng.random(3 if shape is None else tuple(shape)+(3,) if hasattr(shape, '__iter__') else (shape,3)) A = np.sqrt(r[...,2]) B = np.sqrt(1.0-r[...,2]) @@ -647,14 +711,17 @@ class Rotation: np.cos(2.0*np.pi*r[...,1])*B, np.sin(2.0*np.pi*r[...,0])*A],axis=-1) - return Rotation(q.reshape(r.shape[:-1]+(4,)) if shape is not None else q)._standardize() - - # for compatibility - __mul__ = __matmul__ + return Rotation(q if shape is None else q.reshape(r.shape[:-1]+(4,)))._standardize() @staticmethod - def from_ODF(weights,Eulers,N=500,degrees=True,fractions=True,seed=None): + def from_ODF(weights, + phi, + N = 500, + degrees = True, + fractions = True, + rng_seed = None, + **kwargs): """ Sample discrete values from a binned ODF. @@ -662,7 +729,7 @@ class Rotation: ---------- weights : numpy.ndarray of shape (n) Texture intensity values (probability density or volume fraction) at Euler grid points. - Eulers : numpy.ndarray of shape (n,3) + phi : numpy.ndarray of shape (n,3) Grid coordinates in Euler space at which weights are defined. N : integer, optional Number of discrete orientations to be sampled from the given ODF. @@ -672,7 +739,7 @@ class Rotation: fractions : boolean, optional ODF values correspond to volume fractions, not probability density. Defaults to True. - seed: {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional + rng_seed: {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional A seed to initialize the BitGenerator. Defaults to None, i.e. unpredictable entropy will be pulled from the OS. @@ -695,19 +762,24 @@ class Rotation: """ def _dg(eu,deg): """Return infinitesimal Euler space volume of bin(s).""" - Eulers_sorted = eu[np.lexsort((eu[:,0],eu[:,1],eu[:,2]))] - steps,size,_ = grid_filters.cell_coord0_gridSizeOrigin(Eulers_sorted) + phi_sorted = eu[np.lexsort((eu[:,0],eu[:,1],eu[:,2]))] + steps,size,_ = grid_filters.cell_coord0_gridSizeOrigin(phi_sorted) delta = np.radians(size/steps) if deg else size/steps return delta[0]*2.0*np.sin(delta[1]/2.0)*delta[2] / 8.0 / np.pi**2 * np.sin(np.radians(eu[:,1]) if deg else eu[:,1]) - dg = 1.0 if fractions else _dg(Eulers,degrees) + dg = 1.0 if fractions else _dg(phi,degrees) dV_V = dg * np.maximum(0.0,weights.squeeze()) - return Rotation.from_Eulers(Eulers[util.hybrid_IA(dV_V,N,seed)],degrees) + return Rotation.from_Euler_angles(phi[util.hybrid_IA(dV_V,N,rng_seed)],degrees) @staticmethod - def from_spherical_component(center,sigma,N=500,degrees=True,seed=None): + def from_spherical_component(center, + sigma, + N = 500, + degrees = True, + rng_seed = None, + **kwargs): """ Calculate set of rotations with Gaussian distribution around center. @@ -721,12 +793,12 @@ class Rotation: Number of samples, defaults to 500. degrees : boolean, optional sigma is given in degrees. - seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional + rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional A seed to initialize the BitGenerator. Defaults to None, i.e. unpredictable entropy will be pulled from the OS. """ - rng = np.random.default_rng(seed) + rng = np.random.default_rng(rng_seed) sigma = np.radians(sigma) if degrees else sigma u,Theta = (rng.random((N,2)) * 2.0 * np.array([1,np.pi]) - np.array([1.0, 0])).T omega = abs(rng.normal(scale=sigma,size=N)) @@ -738,7 +810,13 @@ class Rotation: @staticmethod - def from_fiber_component(alpha,beta,sigma=0.0,N=500,degrees=True,seed=None): + def from_fiber_component(alpha, + beta, + sigma = 0.0, + N = 500, + degrees = True, + rng_seed = None, + **kwargs): """ Calculate set of rotations with Gaussian distribution around direction. @@ -755,12 +833,12 @@ class Rotation: Number of samples, defaults to 500. degrees : boolean, optional sigma, alpha, and beta are given in degrees. - seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional + rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional A seed to initialize the BitGenerator. Defaults to None, i.e. unpredictable entropy will be pulled from the OS. """ - rng = np.random.default_rng(seed) + rng = np.random.default_rng(rng_seed) sigma_,alpha_,beta_ = map(np.radians,(sigma,alpha,beta)) if degrees else (sigma,alpha,beta) d_cr = np.array([np.sin(alpha_[0])*np.cos(alpha_[1]), np.sin(alpha_[0])*np.sin(alpha_[1]), np.cos(alpha_[0])]) diff --git a/python/damask/_table.py b/python/damask/_table.py index dd8df97b0..c05fa71a7 100644 --- a/python/damask/_table.py +++ b/python/damask/_table.py @@ -31,7 +31,7 @@ class Table: def __repr__(self): """Brief overview.""" - return util.srepr(self.comments)+'\n'+self.data.__repr__() + return '\n'.join(['# '+c for c in self.comments])+'\n'+self.data.__repr__() def __len__(self): """Number of rows.""" @@ -159,7 +159,7 @@ class Table: comments = [util.execution_stamp('Table','from_ang')] for line in content: if line.startswith('#'): - comments.append(line.strip()) + comments.append(line.split('#',1)[1].strip()) else: break @@ -175,7 +175,7 @@ class Table: @property def labels(self): - return list(self.shapes.keys()) + return list(self.shapes) def get(self,label): @@ -222,6 +222,7 @@ class Table: dup.data[label] = data.reshape(dup.data[label].shape) return dup + def add(self,label,data,info=None): """ Add column data. diff --git a/python/damask/_test.py b/python/damask/_test.py index 5cadc9dfe..000b76e0e 100644 --- a/python/damask/_test.py +++ b/python/damask/_test.py @@ -316,12 +316,6 @@ class Test: return self.compare_Array(refName,curName) - def compare_ArrayCurCur(self,cur0,cur1): - - cur0Name = self.fileInCurrent(cur0) - cur1Name = self.fileInCurrent(cur1) - return self.compare_Array(cur0Name,cur1Name) - def compare_Table(self,headings0,file0, headings1,file1, normHeadings='',normType=None, diff --git a/python/damask/_vtk.py b/python/damask/_vtk.py index c1fe52f38..2f4d63791 100644 --- a/python/damask/_vtk.py +++ b/python/damask/_vtk.py @@ -1,3 +1,4 @@ +import os import multiprocessing as mp from pathlib import Path @@ -36,7 +37,7 @@ class VTK: @staticmethod - def from_rectilinearGrid(grid,size,origin=np.zeros(3)): + def from_rectilinear_grid(grid,size,origin=np.zeros(3)): """ Create VTK of type vtk.vtkRectilinearGrid. @@ -64,7 +65,7 @@ class VTK: @staticmethod - def from_unstructuredGrid(nodes,connectivity,cell_type): + def from_unstructured_grid(nodes,connectivity,cell_type): """ Create VTK of type vtk.vtkUnstructuredGrid. @@ -96,7 +97,7 @@ class VTK: @staticmethod - def from_polyData(points): + def from_poly_data(points): """ Create VTK of type vtk.polyData. @@ -108,11 +109,18 @@ class VTK: Spatial position of the points. """ + N = points.shape[0] vtk_points = vtk.vtkPoints() vtk_points.SetData(np_to_vtk(points)) + vtk_cells = vtk.vtkCellArray() + vtk_cells.SetNumberOfCells(N) + vtk_cells.SetCells(N,np_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.SetPoints(vtk_points) + vtk_data.SetVerts(vtk_cells) return VTK(vtk_data) @@ -131,6 +139,8 @@ class VTK: vtkUnstructuredGrid, and vtkPolyData. """ + if not os.path.isfile(fname): # vtk has a strange error handling + raise FileNotFoundError(f'no such file: {fname}') ext = Path(fname).suffix if ext == '.vtk' or dataset_type is not None: reader = vtk.vtkGenericDataObjectReader() @@ -164,6 +174,7 @@ class VTK: return VTK(vtk_data) + @staticmethod def _write(writer): """Wrapper for parallel writing.""" @@ -189,11 +200,10 @@ class VTK: elif isinstance(self.vtk_data,vtk.vtkPolyData): writer = vtk.vtkXMLPolyDataWriter() - default_ext = writer.GetDefaultFileExtension() + default_ext = '.'+writer.GetDefaultFileExtension() ext = Path(fname).suffix - if ext and ext != '.'+default_ext: - raise ValueError(f'Given extension {ext} does not match default .{default_ext}') - writer.SetFileName(str(Path(fname).with_suffix('.'+default_ext))) + writer.SetFileName(str(fname)+(default_ext if default_ext != ext else '')) + if compress: writer.SetCompressorTypeToZLib() else: @@ -238,10 +248,10 @@ class VTK: else data).reshape(N_data,-1),deep=True) # avoid large files d.SetName(label) - if N_data == N_cells: - self.vtk_data.GetCellData().AddArray(d) - elif N_data == N_points: + if N_data == N_points: self.vtk_data.GetPointData().AddArray(d) + elif N_data == N_cells: + self.vtk_data.GetCellData().AddArray(d) else: raise ValueError(f'Cell / point count ({N_cells} / {N_points}) differs from data ({N_data}).') elif isinstance(data,pd.DataFrame): diff --git a/python/damask/lattice.py b/python/damask/lattice.py new file mode 100644 index 000000000..768dc5ace --- /dev/null +++ b/python/damask/lattice.py @@ -0,0 +1,420 @@ +import numpy as _np + +kinematics = { + 'cF': { + 'slip' : _np.array([ + [+0,+1,-1 , +1,+1,+1], + [-1,+0,+1 , +1,+1,+1], + [+1,-1,+0 , +1,+1,+1], + [+0,-1,-1 , -1,-1,+1], + [+1,+0,+1 , -1,-1,+1], + [-1,+1,+0 , -1,-1,+1], + [+0,-1,+1 , +1,-1,-1], + [-1,+0,-1 , +1,-1,-1], + [+1,+1,+0 , +1,-1,-1], + [+0,+1,+1 , -1,+1,-1], + [+1,+0,-1 , -1,+1,-1], + [-1,-1,+0 , -1,+1,-1], + [+1,+1,+0 , +1,-1,+0], + [+1,-1,+0 , +1,+1,+0], + [+1,+0,+1 , +1,+0,-1], + [+1,+0,-1 , +1,+0,+1], + [+0,+1,+1 , +0,+1,-1], + [+0,+1,-1 , +0,+1,+1], + ],'d'), + 'twin' : _np.array([ + [-2, 1, 1, 1, 1, 1], + [ 1,-2, 1, 1, 1, 1], + [ 1, 1,-2, 1, 1, 1], + [ 2,-1, 1, -1,-1, 1], + [-1, 2, 1, -1,-1, 1], + [-1,-1,-2, -1,-1, 1], + [-2,-1,-1, 1,-1,-1], + [ 1, 2,-1, 1,-1,-1], + [ 1,-1, 2, 1,-1,-1], + [ 2, 1,-1, -1, 1,-1], + [-1,-2,-1, -1, 1,-1], + [-1, 1, 2, -1, 1,-1], + ],dtype=float), + }, + 'cI': { + 'slip' : _np.array([ + [+1,-1,+1 , +0,+1,+1], + [-1,-1,+1 , +0,+1,+1], + [+1,+1,+1 , +0,-1,+1], + [-1,+1,+1 , +0,-1,+1], + [-1,+1,+1 , +1,+0,+1], + [-1,-1,+1 , +1,+0,+1], + [+1,+1,+1 , -1,+0,+1], + [+1,-1,+1 , -1,+0,+1], + [-1,+1,+1 , +1,+1,+0], + [-1,+1,-1 , +1,+1,+0], + [+1,+1,+1 , -1,+1,+0], + [+1,+1,-1 , -1,+1,+0], + [-1,+1,+1 , +2,+1,+1], + [+1,+1,+1 , -2,+1,+1], + [+1,+1,-1 , +2,-1,+1], + [+1,-1,+1 , +2,+1,-1], + [+1,-1,+1 , +1,+2,+1], + [+1,+1,-1 , -1,+2,+1], + [+1,+1,+1 , +1,-2,+1], + [-1,+1,+1 , +1,+2,-1], + [+1,+1,-1 , +1,+1,+2], + [+1,-1,+1 , -1,+1,+2], + [-1,+1,+1 , +1,-1,+2], + [+1,+1,+1 , +1,+1,-2], + ],'d'), + 'twin' : _np.array([ + [-1, 1, 1, 2, 1, 1], + [ 1, 1, 1, -2, 1, 1], + [ 1, 1,-1, 2,-1, 1], + [ 1,-1, 1, 2, 1,-1], + [ 1,-1, 1, 1, 2, 1], + [ 1, 1,-1, -1, 2, 1], + [ 1, 1, 1, 1,-2, 1], + [-1, 1, 1, 1, 2,-1], + [ 1, 1,-1, 1, 1, 2], + [ 1,-1, 1, -1, 1, 2], + [-1, 1, 1, 1,-1, 2], + [ 1, 1, 1, 1, 1,-2], + ],dtype=float), + }, + 'hP': { + 'slip' : _np.array([ + [+2,-1,-1,+0 , +0,+0,+0,+1], + [-1,+2,-1,+0 , +0,+0,+0,+1], + [-1,-1,+2,+0 , +0,+0,+0,+1], + [+2,-1,-1,+0 , +0,+1,-1,+0], + [-1,+2,-1,+0 , -1,+0,+1,+0], + [-1,-1,+2,+0 , +1,-1,+0,+0], + [-1,+1,+0,+0 , +1,+1,-2,+0], + [+0,-1,+1,+0 , -2,+1,+1,+0], + [+1,+0,-1,+0 , +1,-2,+1,+0], + [-1,+2,-1,+0 , +1,+0,-1,+1], + [-2,+1,+1,+0 , +0,+1,-1,+1], + [-1,-1,+2,+0 , -1,+1,+0,+1], + [+1,-2,+1,+0 , -1,+0,+1,+1], + [+2,-1,-1,+0 , +0,-1,+1,+1], + [+1,+1,-2,+0 , +1,-1,+0,+1], + [-2,+1,+1,+3 , +1,+0,-1,+1], + [-1,-1,+2,+3 , +1,+0,-1,+1], + [-1,-1,+2,+3 , +0,+1,-1,+1], + [+1,-2,+1,+3 , +0,+1,-1,+1], + [+1,-2,+1,+3 , -1,+1,+0,+1], + [+2,-1,-1,+3 , -1,+1,+0,+1], + [+2,-1,-1,+3 , -1,+0,+1,+1], + [+1,+1,-2,+3 , -1,+0,+1,+1], + [+1,+1,-2,+3 , +0,-1,+1,+1], + [-1,+2,-1,+3 , +0,-1,+1,+1], + [-1,+2,-1,+3 , +1,-1,+0,+1], + [-2,+1,+1,+3 , +1,-1,+0,+1], + [-1,-1,+2,+3 , +1,+1,-2,+2], + [+1,-2,+1,+3 , -1,+2,-1,+2], + [+2,-1,-1,+3 , -2,+1,+1,+2], + [+1,+1,-2,+3 , -1,-1,+2,+2], + [-1,+2,-1,+3 , +1,-2,+1,+2], + [-2,+1,+1,+3 , +2,-1,-1,+2], + ],'d'), + 'twin' : _np.array([ + [-1, 0, 1, 1, 1, 0, -1, 2], # shear = (3-(c/a)^2)/(sqrt(3) c/a) <-10.1>{10.2} + [ 0, -1, 1, 1, 0, 1, -1, 2], + [ 1, -1, 0, 1, -1, 1, 0, 2], + [ 1, 0, -1, 1, -1, 0, 1, 2], + [ 0, 1, -1, 1, 0, -1, 1, 2], + [-1, 1, 0, 1, 1, -1, 0, 2], + [-1, -1, 2, 6, 1, 1, -2, 1], # shear = 1/(c/a) <11.6>{-1-1.1} + [ 1, -2, 1, 6, -1, 2, -1, 1], + [ 2, -1, -1, 6, -2, 1, 1, 1], + [ 1, 1, -2, 6, -1, -1, 2, 1], + [-1, 2, -1, 6, 1, -2, 1, 1], + [-2, 1, 1, 6, 2, -1, -1, 1], + [ 1, 0, -1, -2, 1, 0, -1, 1], # shear = (4(c/a)^2-9)/(4 sqrt(3) c/a) <10.-2>{10.1} + [ 0, 1, -1, -2, 0, 1, -1, 1], + [-1, 1, 0, -2, -1, 1, 0, 1], + [-1, 0, 1, -2, -1, 0, 1, 1], + [ 0, -1, 1, -2, 0, -1, 1, 1], + [ 1, -1, 0, -2, 1, -1, 0, 1], + [ 1, 1, -2, -3, 1, 1, -2, 2], # shear = 2((c/a)^2-2)/(3 c/a) <11.-3>{11.2} + [-1, 2, -1, -3, -1, 2, -1, 2], + [-2, 1, 1, -3, -2, 1, 1, 2], + [-1, -1, 2, -3, -1, -1, 2, 2], + [ 1, -2, 1, -3, 1, -2, 1, 2], + [ 2, -1, -1, -3, 2, -1, -1, 2], + ],dtype=float), + }, +} + +# Kurdjomov--Sachs orientation relationship for fcc <-> bcc transformation +# from S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013 +# also see K. Kitahara et al., Acta Materialia 54:1279-1288, 2006 + +relations = { + 'KS': { + 'cF' : _np.array([ + [[ -1, 0, 1],[ 1, 1, 1]], + [[ -1, 0, 1],[ 1, 1, 1]], + [[ 0, 1, -1],[ 1, 1, 1]], + [[ 0, 1, -1],[ 1, 1, 1]], + [[ 1, -1, 0],[ 1, 1, 1]], + [[ 1, -1, 0],[ 1, 1, 1]], + [[ 1, 0, -1],[ 1, -1, 1]], + [[ 1, 0, -1],[ 1, -1, 1]], + [[ -1, -1, 0],[ 1, -1, 1]], + [[ -1, -1, 0],[ 1, -1, 1]], + [[ 0, 1, 1],[ 1, -1, 1]], + [[ 0, 1, 1],[ 1, -1, 1]], + [[ 0, -1, 1],[ -1, 1, 1]], + [[ 0, -1, 1],[ -1, 1, 1]], + [[ -1, 0, -1],[ -1, 1, 1]], + [[ -1, 0, -1],[ -1, 1, 1]], + [[ 1, 1, 0],[ -1, 1, 1]], + [[ 1, 1, 0],[ -1, 1, 1]], + [[ -1, 1, 0],[ 1, 1, -1]], + [[ -1, 1, 0],[ 1, 1, -1]], + [[ 0, -1, -1],[ 1, 1, -1]], + [[ 0, -1, -1],[ 1, 1, -1]], + [[ 1, 0, 1],[ 1, 1, -1]], + [[ 1, 0, 1],[ 1, 1, -1]], + ],dtype=float), + 'cI' : _np.array([ + [[ -1, -1, 1],[ 0, 1, 1]], + [[ -1, 1, -1],[ 0, 1, 1]], + [[ -1, -1, 1],[ 0, 1, 1]], + [[ -1, 1, -1],[ 0, 1, 1]], + [[ -1, -1, 1],[ 0, 1, 1]], + [[ -1, 1, -1],[ 0, 1, 1]], + [[ -1, -1, 1],[ 0, 1, 1]], + [[ -1, 1, -1],[ 0, 1, 1]], + [[ -1, -1, 1],[ 0, 1, 1]], + [[ -1, 1, -1],[ 0, 1, 1]], + [[ -1, -1, 1],[ 0, 1, 1]], + [[ -1, 1, -1],[ 0, 1, 1]], + [[ -1, -1, 1],[ 0, 1, 1]], + [[ -1, 1, -1],[ 0, 1, 1]], + [[ -1, -1, 1],[ 0, 1, 1]], + [[ -1, 1, -1],[ 0, 1, 1]], + [[ -1, -1, 1],[ 0, 1, 1]], + [[ -1, 1, -1],[ 0, 1, 1]], + [[ -1, -1, 1],[ 0, 1, 1]], + [[ -1, 1, -1],[ 0, 1, 1]], + [[ -1, -1, 1],[ 0, 1, 1]], + [[ -1, 1, -1],[ 0, 1, 1]], + [[ -1, -1, 1],[ 0, 1, 1]], + [[ -1, 1, -1],[ 0, 1, 1]], + ],dtype=float), + }, + 'GT': { + 'cF' : _np.array([ + [[ -5,-12, 17],[ 1, 1, 1]], + [[ 17, -5,-12],[ 1, 1, 1]], + [[-12, 17, -5],[ 1, 1, 1]], + [[ 5, 12, 17],[ -1, -1, 1]], + [[-17, 5,-12],[ -1, -1, 1]], + [[ 12,-17, -5],[ -1, -1, 1]], + [[ -5, 12,-17],[ -1, 1, 1]], + [[ 17, 5, 12],[ -1, 1, 1]], + [[-12,-17, 5],[ -1, 1, 1]], + [[ 5,-12,-17],[ 1, -1, 1]], + [[-17, -5, 12],[ 1, -1, 1]], + [[ 12, 17, 5],[ 1, -1, 1]], + [[ -5, 17,-12],[ 1, 1, 1]], + [[-12, -5, 17],[ 1, 1, 1]], + [[ 17,-12, -5],[ 1, 1, 1]], + [[ 5,-17,-12],[ -1, -1, 1]], + [[ 12, 5, 17],[ -1, -1, 1]], + [[-17, 12, -5],[ -1, -1, 1]], + [[ -5,-17, 12],[ -1, 1, 1]], + [[-12, 5,-17],[ -1, 1, 1]], + [[ 17, 12, 5],[ -1, 1, 1]], + [[ 5, 17, 12],[ 1, -1, 1]], + [[ 12, -5,-17],[ 1, -1, 1]], + [[-17,-12, 5],[ 1, -1, 1]], + ],dtype=float), + 'cI' : _np.array([ + [[-17, -7, 17],[ 1, 0, 1]], + [[ 17,-17, -7],[ 1, 1, 0]], + [[ -7, 17,-17],[ 0, 1, 1]], + [[ 17, 7, 17],[ -1, 0, 1]], + [[-17, 17, -7],[ -1, -1, 0]], + [[ 7,-17,-17],[ 0, -1, 1]], + [[-17, 7,-17],[ -1, 0, 1]], + [[ 17, 17, 7],[ -1, 1, 0]], + [[ -7,-17, 17],[ 0, 1, 1]], + [[ 17, -7,-17],[ 1, 0, 1]], + [[-17,-17, 7],[ 1, -1, 0]], + [[ 7, 17, 17],[ 0, -1, 1]], + [[-17, 17, -7],[ 1, 1, 0]], + [[ -7,-17, 17],[ 0, 1, 1]], + [[ 17, -7,-17],[ 1, 0, 1]], + [[ 17,-17, -7],[ -1, -1, 0]], + [[ 7, 17, 17],[ 0, -1, 1]], + [[-17, 7,-17],[ -1, 0, 1]], + [[-17,-17, 7],[ -1, 1, 0]], + [[ -7, 17,-17],[ 0, 1, 1]], + [[ 17, 7, 17],[ -1, 0, 1]], + [[ 17, 17, 7],[ 1, -1, 0]], + [[ 7,-17,-17],[ 0, -1, 1]], + [[-17, -7, 17],[ 1, 0, 1]], + ],dtype=float), + }, + 'GT_prime': { + 'cF' : _np.array([ + [[ 0, 1, -1],[ 7, 17, 17]], + [[ -1, 0, 1],[ 17, 7, 17]], + [[ 1, -1, 0],[ 17, 17, 7]], + [[ 0, -1, -1],[ -7,-17, 17]], + [[ 1, 0, 1],[-17, -7, 17]], + [[ 1, -1, 0],[-17,-17, 7]], + [[ 0, 1, -1],[ 7,-17,-17]], + [[ 1, 0, 1],[ 17, -7,-17]], + [[ -1, -1, 0],[ 17,-17, -7]], + [[ 0, -1, -1],[ -7, 17,-17]], + [[ -1, 0, 1],[-17, 7,-17]], + [[ -1, -1, 0],[-17, 17, -7]], + [[ 0, -1, 1],[ 7, 17, 17]], + [[ 1, 0, -1],[ 17, 7, 17]], + [[ -1, 1, 0],[ 17, 17, 7]], + [[ 0, 1, 1],[ -7,-17, 17]], + [[ -1, 0, -1],[-17, -7, 17]], + [[ -1, 1, 0],[-17,-17, 7]], + [[ 0, -1, 1],[ 7,-17,-17]], + [[ -1, 0, -1],[ 17, -7,-17]], + [[ 1, 1, 0],[ 17,-17, -7]], + [[ 0, 1, 1],[ -7, 17,-17]], + [[ 1, 0, -1],[-17, 7,-17]], + [[ 1, 1, 0],[-17, 17, -7]], + ],dtype=float), + 'cI' : _np.array([ + [[ 1, 1, -1],[ 12, 5, 17]], + [[ -1, 1, 1],[ 17, 12, 5]], + [[ 1, -1, 1],[ 5, 17, 12]], + [[ -1, -1, -1],[-12, -5, 17]], + [[ 1, -1, 1],[-17,-12, 5]], + [[ 1, -1, -1],[ -5,-17, 12]], + [[ -1, 1, -1],[ 12, -5,-17]], + [[ 1, 1, 1],[ 17,-12, -5]], + [[ -1, -1, 1],[ 5,-17,-12]], + [[ 1, -1, -1],[-12, 5,-17]], + [[ -1, -1, 1],[-17, 12, -5]], + [[ -1, -1, -1],[ -5, 17,-12]], + [[ 1, -1, 1],[ 12, 17, 5]], + [[ 1, 1, -1],[ 5, 12, 17]], + [[ -1, 1, 1],[ 17, 5, 12]], + [[ -1, 1, 1],[-12,-17, 5]], + [[ -1, -1, -1],[ -5,-12, 17]], + [[ -1, 1, -1],[-17, -5, 12]], + [[ -1, -1, 1],[ 12,-17, -5]], + [[ -1, 1, -1],[ 5,-12,-17]], + [[ 1, 1, 1],[ 17, -5,-12]], + [[ 1, 1, 1],[-12, 17, -5]], + [[ 1, -1, -1],[ -5, 12,-17]], + [[ 1, 1, -1],[-17, 5,-12]], + ],dtype=float), + }, + 'NW': { + 'cF' : _np.array([ + [[ 2, -1, -1],[ 1, 1, 1]], + [[ -1, 2, -1],[ 1, 1, 1]], + [[ -1, -1, 2],[ 1, 1, 1]], + [[ -2, -1, -1],[ -1, 1, 1]], + [[ 1, 2, -1],[ -1, 1, 1]], + [[ 1, -1, 2],[ -1, 1, 1]], + [[ 2, 1, -1],[ 1, -1, 1]], + [[ -1, -2, -1],[ 1, -1, 1]], + [[ -1, 1, 2],[ 1, -1, 1]], + [[ 2, -1, 1],[ -1, -1, 1]], + [[ -1, 2, 1],[ -1, -1, 1]], + [[ -1, -1, -2],[ -1, -1, 1]], + ],dtype=float), + 'cI' : _np.array([ + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + ],dtype=float), + }, + 'Pitsch': { + 'cF' : _np.array([ + [[ 1, 0, 1],[ 0, 1, 0]], + [[ 1, 1, 0],[ 0, 0, 1]], + [[ 0, 1, 1],[ 1, 0, 0]], + [[ 0, 1, -1],[ 1, 0, 0]], + [[ -1, 0, 1],[ 0, 1, 0]], + [[ 1, -1, 0],[ 0, 0, 1]], + [[ 1, 0, -1],[ 0, 1, 0]], + [[ -1, 1, 0],[ 0, 0, 1]], + [[ 0, -1, 1],[ 1, 0, 0]], + [[ 0, 1, 1],[ 1, 0, 0]], + [[ 1, 0, 1],[ 0, 1, 0]], + [[ 1, 1, 0],[ 0, 0, 1]], + ],dtype=float), + 'cI' : _np.array([ + [[ 1, -1, 1],[ -1, 0, 1]], + [[ 1, 1, -1],[ 1, -1, 0]], + [[ -1, 1, 1],[ 0, 1, -1]], + [[ -1, 1, -1],[ 0, -1, -1]], + [[ -1, -1, 1],[ -1, 0, -1]], + [[ 1, -1, -1],[ -1, -1, 0]], + [[ 1, -1, -1],[ -1, 0, -1]], + [[ -1, 1, -1],[ -1, -1, 0]], + [[ -1, -1, 1],[ 0, -1, -1]], + [[ -1, 1, 1],[ 0, -1, 1]], + [[ 1, -1, 1],[ 1, 0, -1]], + [[ 1, 1, -1],[ -1, 1, 0]], + ],dtype=float), + }, + 'Bain': { + 'cF' : _np.array([ + [[ 0, 1, 0],[ 1, 0, 0]], + [[ 0, 0, 1],[ 0, 1, 0]], + [[ 1, 0, 0],[ 0, 0, 1]], + ],dtype=float), + 'cI' : _np.array([ + [[ 0, 1, 1],[ 1, 0, 0]], + [[ 1, 0, 1],[ 0, 1, 0]], + [[ 1, 1, 0],[ 0, 0, 1]], + ],dtype=float), + }, + 'Burgers' : { + 'cI' : _np.array([ + [[ -1, 1, 1],[ 1, 1, 0]], + [[ -1, 1, -1],[ 1, 1, 0]], + [[ 1, 1, 1],[ 1, -1, 0]], + [[ 1, 1, -1],[ 1, -1, 0]], + + [[ 1, 1, -1],[ 1, 0, 1]], + [[ -1, 1, 1],[ 1, 0, 1]], + [[ 1, 1, 1],[ -1, 0, 1]], + [[ 1, -1, 1],[ -1, 0, 1]], + + [[ -1, 1, -1],[ 0, 1, 1]], + [[ 1, 1, -1],[ 0, 1, 1]], + [[ -1, 1, 1],[ 0, -1, 1]], + [[ 1, 1, 1],[ 0, -1, 1]], + ],dtype=float), + 'hP' : _np.array([ + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + ],dtype=float), + }, +} diff --git a/python/damask/mechanics.py b/python/damask/mechanics.py index c81399d94..66f5c9916 100644 --- a/python/damask/mechanics.py +++ b/python/damask/mechanics.py @@ -1,307 +1,282 @@ +"""Finite-strain continuum mechanics.""" + +from . import tensor as _tensor +from . import _rotation + import numpy as _np -def Cauchy(P,F): - """ - Return Cauchy stress calculated from first Piola-Kirchhoff stress and deformation gradient. - Resulting tensor is symmetrized as the Cauchy stress needs to be symmetric. +def deformation_Cauchy_Green_left(F): + """ + Calculate left Cauchy-Green deformation tensor (Finger deformation tensor). Parameters ---------- - F : numpy.ndarray of shape (:,3,3) or (3,3) + F : numpy.ndarray of shape (...,3,3) Deformation gradient. - P : numpy.ndarray of shape (:,3,3) or (3,3) - First Piola-Kirchhoff stress. + + Returns + ------- + B : numpy.ndarray of shape (...,3,3) + Left Cauchy-Green deformation tensor. """ - if _np.shape(F) == _np.shape(P) == (3,3): - sigma = 1.0/_np.linalg.det(F) * _np.dot(P,F.T) - else: - sigma = _np.einsum('i,ijk,ilk->ijl',1.0/_np.linalg.det(F),P,F) - return symmetric(sigma) + return _np.matmul(F,_tensor.transpose(F)) -def deviatoric_part(T): +def deformation_Cauchy_Green_right(F): """ - Return deviatoric part of a tensor. + Calculate right Cauchy-Green deformation tensor. Parameters ---------- - T : numpy.ndarray of shape (:,3,3) or (3,3) - Tensor of which the deviatoric part is computed. + F : numpy.ndarray of shape (...,3,3) + Deformation gradient. + + Returns + ------- + C : numpy.ndarray of shape (...,3,3) + Right Cauchy-Green deformation tensor. """ - return T - _np.eye(3)*spherical_part(T) if _np.shape(T) == (3,3) else \ - T - _np.einsum('ijk,i->ijk',_np.broadcast_to(_np.eye(3),[T.shape[0],3,3]),spherical_part(T)) + return _np.matmul(_tensor.transpose(F),F) -def eigenvalues(T_sym): +def equivalent_strain_Mises(epsilon): """ - Return the eigenvalues, i.e. principal components, of a symmetric tensor. - - The eigenvalues are sorted in ascending order, each repeated according to - its multiplicity. + Calculate the Mises equivalent of a strain tensor. Parameters ---------- - T_sym : numpy.ndarray of shape (:,3,3) or (3,3) - Symmetric tensor of which the eigenvalues are computed. + epsilon : numpy.ndarray of shape (...,3,3) + Symmetric strain tensor of which the von Mises equivalent is computed. + + Returns + ------- + epsilon_vM : numpy.ndarray of shape (...) + Von Mises equivalent strain of epsilon. """ - return _np.linalg.eigvalsh(symmetric(T_sym)) + return _equivalent_Mises(epsilon,2.0/3.0) -def eigenvectors(T_sym,RHS=False): +def equivalent_stress_Mises(sigma): """ - Return eigenvectors of a symmetric tensor. - - The eigenvalues are sorted in ascending order of their associated eigenvalues. + Calculate the Mises equivalent of a stress tensor. Parameters ---------- - T_sym : numpy.ndarray of shape (:,3,3) or (3,3) - Symmetric tensor of which the eigenvectors are computed. - RHS: bool, optional - Enforce right-handed coordinate system. Default is False. + sigma : numpy.ndarray of shape (...,3,3) + Symmetric stress tensor of which the von Mises equivalent is computed. + + Returns + ------- + sigma_vM : numpy.ndarray of shape (...) + Von Mises equivalent stress of sigma. """ - (u,v) = _np.linalg.eigh(symmetric(T_sym)) - - if RHS: - if _np.shape(T_sym) == (3,3): - if _np.linalg.det(v) < 0.0: v[:,2] *= -1.0 - else: - v[_np.linalg.det(v) < 0.0,:,2] *= -1.0 - return v - - -def left_stretch(T): - """ - Return the left stretch of a tensor. - - Parameters - ---------- - T : numpy.ndarray of shape (:,3,3) or (3,3) - Tensor of which the left stretch is computed. - - """ - return _polar_decomposition(T,'V')[0] + return _equivalent_Mises(sigma,3.0/2.0) def maximum_shear(T_sym): """ - Return the maximum shear component of a symmetric tensor. + Calculate the maximum shear component of a symmetric tensor. Parameters ---------- - T_sym : numpy.ndarray of shape (:,3,3) or (3,3) + T_sym : numpy.ndarray of shape (...,3,3) Symmetric tensor of which the maximum shear is computed. + Returns + ------- + gamma_max : numpy.ndarray of shape (...) + Maximum shear of T_sym. + """ - w = eigenvalues(T_sym) - return (w[0] - w[2])*0.5 if _np.shape(T_sym) == (3,3) else \ - (w[:,0] - w[:,2])*0.5 + w = _tensor.eigenvalues(T_sym) + return (w[...,0] - w[...,2])*0.5 -def Mises_strain(epsilon): +def rotation(T): """ - Return the Mises equivalent of a strain tensor. + Calculate the rotational part of a tensor. Parameters ---------- - epsilon : numpy.ndarray of shape (:,3,3) or (3,3) - Symmetric strain tensor of which the von Mises equivalent is computed. - - """ - return _Mises(epsilon,2.0/3.0) - - -def Mises_stress(sigma): - """ - Return the Mises equivalent of a stress tensor. - - Parameters - ---------- - sigma : numpy.ndarray of shape (:,3,3) or (3,3) - Symmetric stress tensor of which the von Mises equivalent is computed. - - """ - return _Mises(sigma,3.0/2.0) - - -def PK2(P,F): - """ - Calculate second Piola-Kirchhoff stress from first Piola-Kirchhoff stress and deformation gradient. - - Parameters - ---------- - P : numpy.ndarray of shape (...,3,3) or (3,3) - First Piola-Kirchhoff stress. - F : numpy.ndarray of shape (...,3,3) or (3,3) - Deformation gradient. - - """ - if _np.shape(F) == _np.shape(P) == (3,3): - S = _np.dot(_np.linalg.inv(F),P) - else: - S = _np.einsum('...jk,...kl->...jl',_np.linalg.inv(F),P) - return symmetric(S) - - -def right_stretch(T): - """ - Return the right stretch of a tensor. - - Parameters - ---------- - T : numpy.ndarray of shape (:,3,3) or (3,3) - Tensor of which the right stretch is computed. - - """ - return _polar_decomposition(T,'U')[0] - - -def rotational_part(T): - """ - Return the rotational part of a tensor. - - Parameters - ---------- - T : numpy.ndarray of shape (:,3,3) or (3,3) + T : numpy.ndarray of shape (...,3,3) Tensor of which the rotational part is computed. - """ - return _polar_decomposition(T,'R')[0] - - -def spherical_part(T,tensor=False): - """ - Return spherical (hydrostatic) part of a tensor. - - Parameters - ---------- - T : numpy.ndarray of shape (:,3,3) or (3,3) - Tensor of which the hydrostatic part is computed. - tensor : bool, optional - Map spherical part onto identity tensor. Default is false + Returns + ------- + R : damask.Rotation of shape (...) + Rotational part of the vector. """ - if T.shape == (3,3): - sph = _np.trace(T)/3.0 - return sph if not tensor else _np.eye(3)*sph - else: - sph = _np.trace(T,axis1=1,axis2=2)/3.0 - if not tensor: - return sph - else: - return _np.einsum('ijk,i->ijk',_np.broadcast_to(_np.eye(3),(T.shape[0],3,3)),sph) + return _rotation.Rotation.from_matrix(_polar_decomposition(T,'R')[0]) -def strain_tensor(F,t,m): +def strain(F,t,m): """ - Return strain tensor calculated from deformation gradient. + Calculate strain tensor (Seth–Hill family). For details refer to https://en.wikipedia.org/wiki/Finite_strain_theory and https://de.wikipedia.org/wiki/Verzerrungstensor Parameters ---------- - F : numpy.ndarray of shape (:,3,3) or (3,3) + F : numpy.ndarray of shape (...,3,3) Deformation gradient. t : {‘V’, ‘U’} - Type of the polar decomposition, ‘V’ for left stretch tensor and ‘U’ for right stretch tensor. + Type of the polar decomposition, ‘V’ for left stretch tensor + and ‘U’ for right stretch tensor. m : float Order of the strain. + Returns + ------- + epsilon : numpy.ndarray of shape (...,3,3) + Strain of F. + """ - F_ = F.reshape(1,3,3) if F.shape == (3,3) else F if t == 'V': - B = _np.matmul(F_,transpose(F_)) - w,n = _np.linalg.eigh(B) + w,n = _np.linalg.eigh(deformation_Cauchy_Green_left(F)) elif t == 'U': - C = _np.matmul(transpose(F_),F_) - w,n = _np.linalg.eigh(C) + w,n = _np.linalg.eigh(deformation_Cauchy_Green_right(F)) if m > 0.0: - eps = 1.0/(2.0*abs(m)) * (+ _np.matmul(n,_np.einsum('ij,ikj->ijk',w**m,n)) - - _np.broadcast_to(_np.eye(3),[F_.shape[0],3,3])) + 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.matmul(n,_np.einsum('ij,ikj->ijk',w**m,n)) - + _np.broadcast_to(_np.eye(3),[F_.shape[0],3,3])) + eps = 1.0/(2.0*abs(m)) * (- _np.einsum('...j,...kj,...lj',w**m,n,n) + _np.eye(3)) else: - eps = _np.matmul(n,_np.einsum('ij,ikj->ijk',0.5*_np.log(w),n)) + eps = _np.einsum('...j,...kj,...lj',0.5*_np.log(w),n,n) - return eps.reshape(3,3) if _np.shape(F) == (3,3) else \ - eps + return eps -def symmetric(T): + +def stress_Cauchy(P,F): """ - Return the symmetrized tensor. + Calculate the Cauchy stress (true stress). + + Resulting tensor is symmetrized as the Cauchy stress needs to be symmetric. Parameters ---------- - T : numpy.ndarray of shape (...,3,3) or (3,3) - Tensor of which the symmetrized values are computed. + P : numpy.ndarray of shape (...,3,3) + First Piola-Kirchhoff stress. + F : numpy.ndarray of shape (...,3,3) + Deformation gradient. + + Returns + ------- + sigma : numpy.ndarray of shape (...,3,3) + Cauchy stress. """ - return (T+transpose(T))*0.5 + return _tensor.symmetric(_np.einsum('...,...ij,...kj',1.0/_np.linalg.det(F),P,F)) -def transpose(T): +def stress_second_Piola_Kirchhoff(P,F): """ - Return the transpose of a tensor. + Calculate the second Piola-Kirchhoff stress. + + Resulting tensor is symmetrized as the second Piola-Kirchhoff stress + needs to be symmetric. Parameters ---------- - T : numpy.ndarray of shape (...,3,3) or (3,3) - Tensor of which the transpose is computed. + P : numpy.ndarray of shape (...,3,3) + First Piola-Kirchhoff stress. + F : numpy.ndarray of shape (...,3,3) + Deformation gradient. + + Returns + ------- + S : numpy.ndarray of shape (...,3,3) + Second Piola-Kirchhoff stress. """ - return T.T if _np.shape(T) == (3,3) else \ - _np.swapaxes(T,axis2=-2,axis1=-1) + return _tensor.symmetric(_np.einsum('...ij,...jk',_np.linalg.inv(F),P)) + + +def stretch_left(T): + """ + Calculate left stretch of a tensor. + + Parameters + ---------- + T : numpy.ndarray of shape (...,3,3) + Tensor of which the left stretch is computed. + + Returns + ------- + V : numpy.ndarray of shape (...,3,3) + Left stretch tensor from Polar decomposition of T. + + """ + return _polar_decomposition(T,'V')[0] + + +def stretch_right(T): + """ + Calculate right stretch of a tensor. + + Parameters + ---------- + T : numpy.ndarray of shape (...,3,3) + Tensor of which the right stretch is computed. + + Returns + ------- + U : numpy.ndarray of shape (...,3,3) + Left stretch tensor from Polar decomposition of T. + + """ + return _polar_decomposition(T,'U')[0] def _polar_decomposition(T,requested): """ - Singular value decomposition. + Perform singular value decomposition. Parameters ---------- - T : numpy.ndarray of shape (:,3,3) or (3,3) + T : numpy.ndarray of shape (...,3,3) Tensor of which the singular values are computed. requested : iterable of str Requested outputs: ‘R’ for the rotation tensor, ‘V’ for left stretch tensor and ‘U’ for right stretch tensor. """ - u, s, vh = _np.linalg.svd(T) - R = _np.dot(u,vh) if _np.shape(T) == (3,3) else \ - _np.einsum('ijk,ikl->ijl',u,vh) + u, _, vh = _np.linalg.svd(T) + R = _np.einsum('...ij,...jk',u,vh) output = [] if 'R' in requested: output.append(R) if 'V' in requested: - output.append(_np.dot(T,R.T) if _np.shape(T) == (3,3) else _np.einsum('ijk,ilk->ijl',T,R)) + output.append(_np.einsum('...ij,...kj',T,R)) if 'U' in requested: - output.append(_np.dot(R.T,T) if _np.shape(T) == (3,3) else _np.einsum('ikj,ikl->ijl',R,T)) + output.append(_np.einsum('...ji,...jk',R,T)) + + if len(output) == 0: + raise ValueError('output needs to be out of V, R, U') return tuple(output) -def _Mises(T_sym,s): +def _equivalent_Mises(T_sym,s): """ - Base equation for Mises equivalent of a stres or strain tensor. + Base equation for Mises equivalent of a stress or strain tensor. Parameters ---------- - T_sym : numpy.ndarray of shape (:,3,3) or (3,3) + T_sym : numpy.ndarray of shape (...,3,3) Symmetric tensor of which the von Mises equivalent is computed. s : float Scaling factor (2/3 for strain, 3/2 for stress). """ - d = deviatoric_part(T_sym) - return _np.sqrt(s*(_np.sum(d**2.0))) if _np.shape(T_sym) == (3,3) else \ - _np.sqrt(s*_np.einsum('ijk->i',d**2.0)) + d = _tensor.deviatoric(T_sym) + return _np.sqrt(s*_np.sum(d**2.0,axis=(-1,-2))) diff --git a/python/damask/seeds.py b/python/damask/seeds.py index 9aab953d0..1c4150c2d 100644 --- a/python/damask/seeds.py +++ b/python/damask/seeds.py @@ -1,3 +1,5 @@ +"""Functionality for generation of seed points for Voronoi or Laguerre tessellation.""" + from scipy import spatial as _spatial import numpy as _np @@ -5,7 +7,7 @@ from . import util from . import grid_filters -def from_random(size,N_seeds,grid=None,seed=None): +def from_random(size,N_seeds,grid=None,rng_seed=None): """ Random seeding in space. @@ -18,12 +20,12 @@ def from_random(size,N_seeds,grid=None,seed=None): grid : numpy.ndarray of shape (3), optional. If given, ensures that all seeds initiate one grain if using a standard Voronoi tessellation. - seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional + rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional A seed to initialize the BitGenerator. Defaults to None. If None, then fresh, unpredictable entropy will be pulled from the OS. """ - rng = _np.random.default_rng(seed) + rng = _np.random.default_rng(rng_seed) if grid is None: coords = rng.random((N_seeds,3)) * size else: @@ -34,7 +36,7 @@ def from_random(size,N_seeds,grid=None,seed=None): return coords -def from_Poisson_disc(size,N_seeds,N_candidates,distance,periodic=True,seed=None): +def from_Poisson_disc(size,N_seeds,N_candidates,distance,periodic=True,rng_seed=None): """ Seeding in space according to a Poisson disc distribution. @@ -50,12 +52,12 @@ def from_Poisson_disc(size,N_seeds,N_candidates,distance,periodic=True,seed=None Minimum acceptable distance to other seeds. periodic : boolean, optional Calculate minimum distance for periodically repeated grid. - seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional + rng_seed : {None, int, array_like[ints], SeedSequence, BitGenerator, Generator}, optional A seed to initialize the BitGenerator. Defaults to None. If None, then fresh, unpredictable entropy will be pulled from the OS. """ - rng = _np.random.default_rng(seed) + rng = _np.random.default_rng(rng_seed) coords = _np.empty((N_seeds,3)) coords[0] = rng.random(3) * size diff --git a/python/damask/solver/_marc.py b/python/damask/solver/_marc.py index 12e36f7ed..5ddcf8898 100644 --- a/python/damask/solver/_marc.py +++ b/python/damask/solver/_marc.py @@ -1,6 +1,7 @@ import subprocess import shlex -import string +import re +import io from pathlib import Path from .. import environment @@ -8,7 +9,7 @@ from .. import environment class Marc: """Wrapper to run DAMASK with MSCMarc.""" - def __init__(self,version=environment.options['MARC_VERSION']): + def __init__(self,version=environment.options['MSC_VERSION']): """ Create a Marc solver object. @@ -27,7 +28,10 @@ class Marc: path_MSC = environment.options['MSC_ROOT'] path_lib = Path(f'{path_MSC}/mentat{self.version}/shlib/linux64') - return path_lib if path_lib.is_dir() else None + if not path_lib.is_dir(): + raise FileNotFoundError(f'library path "{path_lib}" not found') + + return path_lib @property @@ -36,10 +40,12 @@ class Marc: path_MSC = environment.options['MSC_ROOT'] path_tools = Path(f'{path_MSC}/marc{self.version}/tools') - return path_tools if path_tools.is_dir() else None + if not path_tools.is_dir(): + raise FileNotFoundError(f'tools path "{path_tools}" not found') + + return path_tools -#-------------------------- def submit_job(self, model, job = 'job1', @@ -48,38 +54,37 @@ class Marc: optimization = '', ): - usersub = environment.root_dir/'src/DAMASK_marc' usersub = usersub.parent/(usersub.name + ('.f90' if compile else '.marc')) if not usersub.is_file(): - raise FileNotFoundError("DAMASK4Marc ({}) '{}' not found".format(('source' if compile else 'binary'),usersub)) + raise FileNotFoundError(f'subroutine ({"source" if compile else "binary"}) "{usersub}" not found') # Define options [see Marc Installation and Operation Guide, pp 23] script = f'run_damask_{optimization}mp' - cmd = str(self.tools_path/Path(script)) + \ - ' -jid ' + model + '_' + job + \ - ' -nprocd 1 -autorst 0 -ci n -cr n -dcoup 0 -b no -v no' - - if compile: cmd += ' -u ' + str(usersub) + ' -save y' - else: cmd += ' -prog ' + str(usersub.with_suffix('')) - - print('job submission {} compilation: {}'.format(('with' if compile else 'without'),usersub)) - if logfile: log = open(logfile, 'w') + cmd = str(self.tools_path/script) + \ + ' -jid ' + model+'_'+job + \ + ' -nprocd 1 -autorst 0 -ci n -cr n -dcoup 0 -b no -v no' + cmd += ' -u ' + str(usersub) + ' -save y' if compile else \ + ' -prog ' + str(usersub.with_suffix('')) print(cmd) - process = subprocess.Popen(shlex.split(cmd),stdout = log,stderr = subprocess.STDOUT) - log.close() - process.wait() -#-------------------------- - def exit_number_from_outFile(self,outFile=None): - exitnumber = -1 - with open(outFile,'r') as fid_out: - for line in fid_out: - if (string.find(line,'tress iteration') != -1): - print(line) - elif (string.find(line,'Exit number') != -1): - substr = line[string.find(line,'Exit number'):len(line)] - exitnumber = int(substr[12:16]) + if logfile is not None: + try: + f = open(logfile,'w+') + except TypeError: + f = logfile + else: + f = io.StringIO() - return exitnumber + proc = subprocess.Popen(shlex.split(cmd),stdout=f,stderr=subprocess.STDOUT) + proc.wait() + f.seek(0) + + try: + v = int(re.search('Exit number ([0-9]+)',''.join(f.readlines())).group(1)) + except (AttributeError,ValueError): + raise RuntimeError('Marc simulation failed (unknown return value)') + + if v != 3004: + raise RuntimeError(f'Marc simulation failed ({v})') diff --git a/python/damask/tensor.py b/python/damask/tensor.py new file mode 100644 index 000000000..00fc7e72a --- /dev/null +++ b/python/damask/tensor.py @@ -0,0 +1,131 @@ +""" +Tensor operations. + +Notes +----- +This is not a tensor class, but a collection of routines +to operate on numpy.ndarrays of shape (...,3,3). + +""" + +import numpy as _np + + +def deviatoric(T): + """ + Calculate deviatoric part of a tensor. + + Parameters + ---------- + T : numpy.ndarray of shape (...,3,3) + Tensor of which the deviatoric part is computed. + + Returns + ------- + T' : numpy.ndarray of shape (...,3,3) + Deviatoric part of T. + + """ + return T - spherical(T,tensor=True) + + +def eigenvalues(T_sym): + """ + Eigenvalues, i.e. principal components, of a symmetric tensor. + + Parameters + ---------- + T_sym : numpy.ndarray of shape (...,3,3) + Symmetric tensor of which the eigenvalues are computed. + + Returns + ------- + lambda : numpy.ndarray of shape (...,3) + Eigenvalues of T_sym sorted in ascending order, each repeated + according to its multiplicity. + + """ + return _np.linalg.eigvalsh(symmetric(T_sym)) + + +def eigenvectors(T_sym,RHS=False): + """ + Eigenvectors of a symmetric tensor. + + Parameters + ---------- + T_sym : numpy.ndarray of shape (...,3,3) + Symmetric tensor of which the eigenvectors are computed. + RHS: bool, optional + Enforce right-handed coordinate system. Defaults to False. + + Returns + ------- + x : numpy.ndarray of shape (...,3,3) + Eigenvectors of T_sym sorted in ascending order of their + associated eigenvalues. + + """ + (u,v) = _np.linalg.eigh(symmetric(T_sym)) + + if RHS: + v[_np.linalg.det(v) < 0.0,:,2] *= -1.0 + return v + + +def spherical(T,tensor=True): + """ + Calculate spherical part of a tensor. + + Parameters + ---------- + T : numpy.ndarray of shape (...,3,3) + Tensor of which the spherical part is computed. + tensor : bool, optional + Map spherical part onto identity tensor. Defaults to True. + + Returns + ------- + p : numpy.ndarray of shape (...,3,3) + unless tensor == False: shape (...,) + Spherical part of tensor T. p is an isotropic tensor. + + """ + sph = _np.trace(T,axis2=-2,axis1=-1)/3.0 + return _np.einsum('...jk,...',_np.eye(3),sph) if tensor else sph + + +def symmetric(T): + """ + Symmetrize tensor. + + Parameters + ---------- + T : numpy.ndarray of shape (...,3,3) + Tensor of which the symmetrized values are computed. + + Returns + ------- + T_sym : numpy.ndarray of shape (...,3,3) + Symmetrized tensor T. + + """ + return (T+transpose(T))*0.5 + + +def transpose(T): + """ + Transpose tensor. + + Parameters + ---------- + T : numpy.ndarray of shape (...,3,3) + Tensor of which the transpose is computed. + + Returns + ------- + T.T : numpy.ndarray of shape (...,3,3) + Transpose of tensor T. + + """ + return _np.swapaxes(T,axis2=-2,axis1=-1) diff --git a/python/damask/util.py b/python/damask/util.py index 9a1b0b67b..fb122bd11 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -3,6 +3,7 @@ import datetime import os import subprocess import shlex +import re import fractions from functools import reduce from optparse import Option @@ -16,14 +17,17 @@ __all__=[ 'srepr', 'croak', 'report', - 'emph','deemph','delete','strikeout', + 'emph','deemph','warn','strikeout', 'execute', 'show_progress', 'scale_to_coprime', + 'project_stereographic', 'hybrid_IA', 'return_message', 'extendableOption', - 'execution_stamp' + 'execution_stamp', + 'shapeshifter', 'shapeblender', + 'extend_docstring', 'extended_docstring' ] #################################################################################################### @@ -38,7 +42,7 @@ def srepr(arg,glue = '\n'): arg : iterable Items to join. glue : str, optional - Defaults to \n. + Glue used for joining operation. Defaults to \n. """ if (not hasattr(arg, "strip") and @@ -52,6 +56,8 @@ def croak(what, newline = True): """ Write formated to stderr. + DEPRECATED + Parameters ---------- what : str or iterable @@ -68,7 +74,7 @@ def croak(what, newline = True): def report(who = None, what = None): """ - Reports script and file name. + Report script and file name. DEPRECATED @@ -80,16 +86,13 @@ def emph(what): """Formats string with emphasis.""" return bcolors.BOLD+srepr(what)+bcolors.ENDC - def deemph(what): """Formats string with deemphasis.""" return bcolors.DIM+srepr(what)+bcolors.ENDC - -def delete(what): - """Formats string as deleted.""" - return bcolors.DIM+srepr(what)+bcolors.ENDC - +def warn(what): + """Formats string for warning.""" + return bcolors.WARNING+emph(what)+bcolors.ENDC def strikeout(what): """Formats string as strikeout.""" @@ -160,7 +163,15 @@ def show_progress(iterable,N_iter=None,prefix='',bar_length=50): def scale_to_coprime(v): - """Scale vector to co-prime (relatively prime) integers.""" + """ + Scale vector to co-prime (relatively prime) integers. + + Parameters + ---------- + v : numpy.ndarray of shape (:) + Vector to scale. + + """ MAX_DENOMINATOR = 1000000 def get_square_denominator(x): @@ -169,6 +180,7 @@ def scale_to_coprime(v): def lcm(a, b): """Least common multiple.""" + # Python 3.9 provides math.lcm, see https://stackoverflow.com/questions/51716916. return a * b // np.gcd(a, b) m = (np.array(v) * reduce(lcm, map(lambda x: int(get_square_denominator(x)),v)) ** 0.5).astype(np.int) @@ -181,6 +193,28 @@ def scale_to_coprime(v): return m +def project_stereographic(vector,normalize=False): + """ + Apply stereographic projection to vector. + + Parameters + ---------- + vector : numpy.ndarray of shape (...,3) + Vector coordinates to be projected. + normalize : bool + Ensure unit length for vector. Defaults to False. + + Returns + ------- + coordinates : numpy.ndarray of shape (...,2) + Projected coordinates. + + """ + v_ = vector/np.linalg.norm(vector,axis=-1,keepdims=True) if normalize else vector + return np.block([v_[...,:2]/(1+np.abs(v_[...,2:3])), + np.zeros_like(v_[...,2:3])]) + + def execution_stamp(class_name,function_name=None): """Timestamp the execution of a (function within a) class.""" now = datetime.datetime.now().astimezone().strftime('%Y-%m-%d %H:%M:%S%z') @@ -188,7 +222,21 @@ def execution_stamp(class_name,function_name=None): return f'damask.{class_name}{_function_name} v{version} ({now})' -def hybrid_IA(dist,N,seed=None): +def hybrid_IA(dist,N,rng_seed=None): + """ + Hybrid integer approximation. + + Parameters + ---------- + dist : numpy.ndarray + Distribution to be approximated + N : int + Number of samples to draw. + 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. + + """ N_opt_samples,N_inv_samples = (max(np.count_nonzero(dist),N),0) # random subsampling if too little samples requested scale_,scale,inc_factor = (0.0,float(N_opt_samples),1.0) @@ -199,7 +247,112 @@ def hybrid_IA(dist,N,seed=None): if N_inv_samples < N_opt_samples else \ (scale_,0.5*(scale_ + scale), 1.0) - return np.repeat(np.arange(len(dist)),repeats)[np.random.default_rng(seed).permutation(N_inv_samples)[:N]] + return np.repeat(np.arange(len(dist)),repeats)[np.random.default_rng(rng_seed).permutation(N_inv_samples)[:N]] + + +def shapeshifter(fro,to,mode='left',keep_ones=False): + """ + Return a tuple that reshapes 'fro' to become broadcastable to 'to'. + + Parameters + ---------- + fro : tuple + Original shape of array. + to : tuple + Target shape of array after broadcasting. + len(to) cannot be less than len(fro). + mode : str, optional + Indicates whether new axes are preferably added to + either 'left' or 'right' of the original shape. + Defaults to 'left'. + keep_ones : bool, optional + Treat '1' in fro as literal value instead of dimensional placeholder. + Defaults to False. + + """ + beg = dict(left ='(^.*\\b)', + right='(^.*?\\b)') + sep = dict(left ='(.*\\b)', + right='(.*?\\b)') + end = dict(left ='(.*?$)', + right='(.*$)') + fro = (1,) if not len(fro) else fro + to = (1,) if not len(to) else to + try: + grp = re.match(beg[mode] + +f',{sep[mode]}'.join(map(lambda x: f'{x}' + if x>1 or (keep_ones and len(fro)>1) else + '\\d+',fro)) + +f',{end[mode]}', + ','.join(map(str,to))+',').groups() + except AttributeError: + raise ValueError(f'Shapes can not be shifted {fro} --> {to}') + fill = () + for g,d in zip(grp,fro+(None,)): + fill += (1,)*g.count(',')+(d,) + return fill[:-1] + + +def shapeblender(a,b): + """ + Return a shape that overlaps the rightmost entries of 'a' with the leftmost of 'b'. + + Parameters + ---------- + a : tuple + Shape of first array. + b : tuple + Shape of second array. + + Examples + -------- + >>> shapeblender((4,4,3),(3,2,1)) + (4,4,3,2,1) + >>> shapeblender((1,2),(1,2,3)) + (1,2,3) + >>> shapeblender((1,),(2,2,1)) + (1,2,2,1) + >>> shapeblender((3,2),(3,2)) + (3,2) + + """ + i = min(len(a),len(b)) + while i > 0 and a[-i:] != b[:i]: i -= 1 + return a + b[i:] + + +def extend_docstring(extra_docstring): + """ + Decorator: Append to function's docstring. + + Parameters + ---------- + extra_docstring : str + Docstring to append. + + """ + def _decorator(func): + func.__doc__ += extra_docstring + return func + return _decorator + + +def extended_docstring(f,extra_docstring): + """ + Decorator: Combine another function's docstring with a given docstring. + + Parameters + ---------- + f : function + Function of which the docstring is taken. + extra_docstring : str + Docstring to append. + + """ + def _decorator(func): + func.__doc__ = f.__doc__ + extra_docstring + return func + return _decorator #################################################################################################### @@ -295,17 +448,6 @@ class bcolors: UNDERLINE = '\033[4m' CROSSOUT = '\033[9m' - def disable(self): - self.HEADER = '' - self.OKBLUE = '' - self.OKGREEN = '' - self.WARNING = '' - self.FAIL = '' - self.ENDC = '' - self.BOLD = '' - self.UNDERLINE = '' - self.CROSSOUT = '' - class return_message: """Object with formatted return message.""" diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 9c58eedd9..c4b6d1d6e 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -1,8 +1,13 @@ from pathlib import Path import datetime +import os import numpy as np import pytest +import matplotlib as mpl +if os.name == 'posix' and 'DISPLAY' not in os.environ: + mpl.use('Agg') +import matplotlib.pyplot as plt import damask @@ -25,8 +30,9 @@ def patch_datetime_now(monkeypatch): monkeypatch.setattr(datetime, 'datetime', mydatetime) + @pytest.fixture -def execution_stamp(monkeypatch): +def patch_execution_stamp(monkeypatch): """Set damask.util.execution_stamp for reproducible tests results.""" def execution_stamp(class_name,function_name=None): _function_name = '' if function_name is None else f'.{function_name}' @@ -35,21 +41,31 @@ def execution_stamp(monkeypatch): monkeypatch.setattr(damask.util, 'execution_stamp', execution_stamp) +@pytest.fixture +def patch_plt_show(monkeypatch): + def _None(block=None): + pass + monkeypatch.setattr(plt, 'show', _None, raising=True) + + def pytest_addoption(parser): parser.addoption("--update", action="store_true", default=False) + @pytest.fixture def update(request): """Store current results as new reference results.""" return request.config.getoption("--update") + @pytest.fixture -def reference_dir_base(): +def ref_path_base(): """Directory containing reference results.""" return Path(__file__).parent/'reference' + @pytest.fixture def set_of_quaternions(): """A set of n random rotations.""" @@ -66,7 +82,7 @@ def set_of_quaternions(): return qu - n = 1100 + n = 600 scatter=1.e-2 specials = np.array([ [1.0, 0.0, 0.0, 0.0], diff --git a/python/tests/reference/ConfigMaterial/material.yaml b/python/tests/reference/ConfigMaterial/material.yaml index 97c6504bb..933e295b3 100644 --- a/python/tests/reference/ConfigMaterial/material.yaml +++ b/python/tests/reference/ConfigMaterial/material.yaml @@ -33,12 +33,12 @@ material: phase: Aluminum: - elasticity: {C_11: 106.75e9, C_12: 60.41e9, C_44: 28.34e9, type: hooke} - generic: - output: [F, P, Fe, Fp, Lp] - lattice: fcc + lattice: cF + mech: + output: [F, P, F_e, F_p, L_p] + elasticity: {C_11: 106.75e9, C_12: 60.41e9, C_44: 28.34e9, type: hooke} Steel: - elasticity: {C_11: 233.3e9, C_12: 135.5e9, C_44: 118.0e9, type: hooke} - generic: - output: [F, P, Fe, Fp, Lp] - lattice: bcc + lattice: cI + mech: + output: [F, P, F_e, F_p, L_p] + elasticity: {C_11: 233.3e9, C_12: 135.5e9, C_44: 118.0e9, type: hooke} diff --git a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20.vtr b/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20.vtr deleted file mode 100644 index dc1924d58..000000000 --- a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20.vtr +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - AQAAAACAAABdAAAAYAAAAA==eF4NwlsKgCAQAMCO4mcRK6tmrwt0g35jKXuQtqHR+WuYhQKlUw6Og1wjh2nkyBcf0+NSct7Tc/AlXiNRIpC/dwJra9hW03Tk5qUVuUaNoBRoKxT2lf6XqBCL7APiGxu3 - - - - - - - - AQAAAACAAACAcAAAFwQAAA==eF7t2sFuYzkQQ9Gk+///uTddm0KIy6JUdmCbG2KAV0e2n6QBJvP19XO+//cf0X9FU77NnroVcnu7ISf1aZ663ocKzStPdYWcqdv31S3P3bc0R63cV/Er5KRuhbxTv0Lus32Kex6moXNX7X7OHnJ7uyEn9WnebRWaU03vm+anbt+v5Lieex623O37s4ec3+5XyEvdCrnKn8b1p6FzkYbc03XIS31yUp/m3VahObd76HlqtV9pbuqp80De1KXznHrTe4gc16eQp3w35PVOQ+70c/eQn4b2bRpyT9chL/XJSX2ad1uF5tzuoefd7vuVnndbnQeam3rqPPffLfXS80xO6lbIO/UrW25l21fv+zS0X9OQe7oOealPTurTvNsqNOd2Dz0/7Qo9N+1+Luh5avc8T7+X67oh79SvbLmVbb+y5dJ+T0P7KQ25p+uQl/rkpD7Nu61Cc2730PPTrtBzad+6n7unznP6vW7dQ+Slbs+2X9lyK1s+7fc0tI/SkHu6DnmpT07q07zbKjTndg89P+0KPfdbu+f0e53eE8rbuoe23Mq2T+8zjbtfpqH9k4bc03XIS31yUp/m3VahObd76PlpV+i5tKd/F5y2+ntg/+dpp3+f2nJVtu/P7fTf51bU738aer9pyD1dh7zUJyf1ad5tFZpzu4een3aFnkt7635W/1/F6fdy/38NlS1Xpe7lR/u30n+fW1Hv4TS0f9KQe7oOealPTurTvNsqNOd2Dz0/7Wfdn2krV90f5FXTPefeT8p1fTdbbg/5p6H3mob2TxraR6lPbu9pyEt9clKf5t1WoTm3e+h5t7fuT/LTdeiemK6j/rsweXT+6O925KY+ea5fSX03U38adz9Ok+53Cu1/tY4bcnu7ISf1ad5tFZpzu78Hep6azgPNk0u+u96te6ivc+v+7E1u6vfc8lVOXfIr276731P3tk/ni9xTn9ZRIY/WUaF5t1VoTjW9b5pXXnoepu7UrybvUT45r+pXbvs9W27llv/oz0/nmLxT312nh7zUJ8e9l04/9+l52HK7T17qV5P3Kj45Hz/rym33UT6dX5qnz033EDmpm65DjnsvpZ+bXPe9TL2pX01O6n58r8lL3XfxaZ5626d1aI6a7iGan7qn/55xPWp3HXJSn+apn+2n65D3rj45Hz9rcrd9mqeme47mXffWOq7ndvq7U9P3oHnqZ/vpOuR9/FmTl7of32u6f2hedYXuO3Km7uk6rue2+7nJmb4Xmqd+dT9dh7yPP2vyUnfb77nl97j3BLmu35u81J3e0xVy6Hfq3XPLpe+TeuRO9/27+uk65L2rT86jfJVTX8W9J2777j2tQh6to0Lz7u+lQs70Xq3vc8tz3/uWOz1Xv9VP1yHvVfwKeVNf5dSt/gdcjxTB - - - - - AQAAAACAAABoAAAANAAAAA==eF5jYICAUDDYag+hj9pDRC9A+VftV4HBLaj4Ayj/EVT+KVT8BVT8FVT8LVT8gz0An9sjLg== - - - AQAAAACAAACAAAAASAAAAA==eF5jYIAAQTDYaA+hD9rPmgkCJ6H8i/ahYHAVKn7T/t5dMIDKP7Q3BoPHUHVP7cvB4DlU/Uv7PbtB4DVU31t7iK0f7AHaRzOg - - - AQAAAACAAACoAAAAVQAAAA==eF5jYICAWTNBYKU9hN5pb2IMAoeh/JP2EFUXoOKX7dPTQOAaVP6m/dkzIHAHqu4BVPwhVP1jqPwTqL5nUHUvoOpeQtW9hqp7A1X3Dqrugz0ASSZF3Q== - - - - - diff --git a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_x_perFalse.vtu b/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_x_perFalse.vtu deleted file mode 100644 index 62809aa74..000000000 --- a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_x_perFalse.vtu +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - BAAAAACAAACAGQAA9g4AADYOAADfDQAA8QIAAA==eF51nUGO5VgRRf8WWAvMexWshbXADmDGgEEJJinkQc2cKnlQSCCnW0+4jCx5CajTir7yiXs9+eTN0CH7yPb7HYrX7/Xy1+8/r7/95POp5fc1h/ql5X/5vP4VOGuo3wJ/BM4eOEfgnIFzhfz1+s3n9ddff1/efD61/L7mUL+0vLx5zhrqt8AfgbMHzhE4Z+BcIS9vb8Eb86nl9zWH+qXlT2/krKF+C/wROHvgHIFzBs4V8tfrT3/85fravPl8avl9zaF+aXl585w11G+BPwJnD5wjcM7AuUJe99t7uN+YTy2/rznULy1/3m/krKF+C/wROHvgHIFzBs4V8v7+Lm8+n1p+X3OoX1pe3jxnDfVb4I/A2QPnCJwzcK6Q13P6vXnz+dTy+5pD/dLy8uY5a6jfAn8Ezh44R+CcgXOF/PX6z78/r+bN51PL72sO9UvLy5vnrKF+C/wROHvgHIFzBs4V8nq/fTRvPp9afl9zqF9aXt48Zw31W+CPwNkD5wicM3CukL9ev/28fm7efD61/L7mUL+0vLx5zhrqt8AfgbMHzhE4Z+BcIdf///PnWheYTy2/rznULy0vb56zhvot8Efg7IFzBM4ZOFfIX68/fF7/bd58PrX8vuZQv7S8vHnOGuq3wB+BswfOEThn4Fwhr/X0R/Pm86nl9zWH+qXl5c1z1lC/Bf4InD1wjsA5A+cK+ev1j7//cv2vefP51PL7mkP90vLy5jlrqN8CfwTOHjhH4JyBc4W8voec4NT3EOZTy+9rDvVLy8ub56yhfgv8ETh74ByBcwbOFXL9vv5XefP51PL7mkP90vLyxvy+1lC/Bf4InD1wjsA5A+cKua77Of7zT09vzKeW3z/NoX5p+dMbOWuo3wJ/BM4eOEfgnIFzhVz9N3rz+dTymzKH+qXl5c1z1lC/Bf4InD1wjsA5A+cKufpv3hvzqeVPb6xfWv70Rs4a6rfAH4GzB84ROGfgXCFX/43efD61vLz5+qXl5c1z1lC/Bf4InD1wjsA5A+cKufpv9ObzqeXlzdcvLX/eb+SsoX4L/BE4e+AcgXMGzhVyfj+VN59PLS9vvn5peXnznDXUb4E/AmcPnCNwzsC5Qq7+G735fGp5efP1S8vLm+esoX4L/BE4e+AcgXMGzhVy9d/ozedTy8ubr19aXt48Zw31W+CPwNkD5wicM3CukKv/Rm8+n1pe3nz90vLy5jlrqN8CfwTOHjhH4JyBc4Vc/Td68/nU8vLm65eWlzfPWUP9FvgjcPbAOQLnDJwr5OwrcF1gPrW8vPn6peXlzXPWUL8F/gicPXCOwDkD5wq5+m/05vOp5eXN1y8tL2+es4b6LfBH4OyBcwTOGThXyNV/ozefTy0vb75+aXl585w11G+BPwJnD5wjcM7AuUKu/hu9+XxqeXnz9UvLy5vnrKF+C/wROHvgHIFzBs4VcvXf6M3nU8vLm69fWl7ePGcN9Vvgj8DZA+cInDNwrpDXdYGj/hu9Mb9/nkP90vLy5jlrqN8CfwTOHjhH4JyBc4Vc151/gTfm7L99gTfWs//2Bd7IYf/tC7yRz/5bcdh/+wJv5LD/Vhz23778+vP9va76S/Lmc/bf5M3Xs/8mb57D/pu8eT77b/LmOey/yZvnsP9Gb2/gsP9Gb2/gsP9Gb2/4+9l/ozfWs/9Gb+Sw/0Zv5LD/Rm/M2X+TN5+z/yZvvp79N3nzHPbf5M3z2X+TN89h/03ePIf9N95v7+Cw/8b77R0c9t94vyl/3m/ksP/G+4189t94v5HD/hvvN3LYf5O3530hbz5n/03efD37b/LmOey/yZvns/8mb57D/pu8eQ77b3xOv4PD/huf0+/gsP/G51R5efMc9t/4nJLP/hufU3LYf+NzSg77b/L27C/Jm8/Zf5M3X8/+m7x5Dvtv8ub57L/Jm+ew/yZvnsP+G99vH+Cw/8b32wc47L/x/faBv5/9N77fWM/+G99v5LD/xvcbOey/8f3GnP03efM5+2/y5uvZf5M3z2H/Td48n/03efMc9t/kzXPYf+O6MMBh/43rwgCH/TeuCwN/P/tvXBdYz/4b1wVy2H/jukAO+29cF5iz/yZvPmf/Td58Pftv8uY57L/Jm+ez/yZvnsP+m7x5DvtvXE9/gMP+G9fTH+Cw/8b1VHl58xz237ieks/+G9dTcth/43pKDvtv8vbsL8mbz9l/kzdfz/6bvHkO+2/y5vnsv8mb57D/Jm+ew/6bvD37S/Lmc/bf5M3Xs//G7yHksP/G7yHks/8mb57D/pu8eQ77b/JWv6c3n7P/Jm++nv03efMc9t/kzfPZf5M3z2H/Td48h/03enu9fve53v4T3phPLb9/mkP90vKnN3LWUL8F/gicPXCOwDkD5wq5+m/05vOp5TdlDvVLy8ub56yhfgv8ETh74ByBcwbOFXL137w35lPLn95Yv7T86Y2cNdRvgT8CZw+cI3DOwLlCrv4bvfl8anl58/VLy8ub56yhfgv8ETh74ByBcwbOFXL13/z9xnxqeXnz9UvLn/cbOWuo3wJ/BM4eOEfgnIFzhVz9N3rz+dTy8ubrl5aXN89ZQ/0W+CNw9sA5AucMnCvk6r/Rm8+nlpc3X7+0vLx5zhrqt8AfgbMHzhE4Z+BcIVf/jd58PrW8vPn6peXlzXPWUL8F/gicPXCOwDkD5wq5+m/05vOp5eXN1y8tL2+es4b6LfBH4OyBcwTOGThXyNV/ozefTy0vb75+aXl585w11G+BPwJnD5wjcM7AuUKu/hu9+XxqeXnz9UvLy5vnrKF+C/wROHvgHIFzBs4VcvXf6M3nU8vLm69fWl7ePGcN9Vvgj8DZA+cInDNwrpCr/0ZvPp9aXt58/dLy8uY5a6jfAn8Ezh44R+CcgXOFXP03evP51PLy5uuXlpc3z1lD/Rb4I3D2wDkC5wycK+Tqv9Gbz6eWlzdfv7S8vHnOGuq3wB+BswfOEThn4Fwhr+tq3nw+tfz+eQ71S8vLm+esoX4L/BE4e+AcgXMGzhVyXfXvr09vzDn/9hXeWM/5t6/wRg7n377CG/mcfysO59++whs5nH8rDuffvv768/29rua75M3nnH+TN1/P+Td58xzOv8mb53P+Td48h/Nv8uY5nH+jtzdwOP9Gb2/gcP6N3t7w93P+jd5Yz/k3eiOH82/0Rg7n3+iNOeff5M3nnH+TN1/P+Td58xzOv8mb53P+Td48h/Nv8uY5nH/j/fYODuffeL+9g8P5N95vyp/3Gzmcf+P9Rj7n33i/kcP5N95v5HD+Td54X5Q3n3P+Td58Peff5M1zOP8mb57P+Td58xzOv8mb53D+jc/pd3A4/8bn9Ds4nH/jc6q8vHkO59/4nJLP+Tc+p+Rw/o3PKTmcf5O36i/Rm885/yZvvp7zb/LmOZx/kzfP5/ybvHkO59/kzXM4/8b32wc4nH/j++0DHM6/8f32gb+f8298v7Ge8298v5HD+Te+38jh/Bvfb8w5/yZvPuf8m7z5es6/yZvncP5N3jyf82/y5jmcf5M3z+H8G9eFAQ7n37guDHA4/8Z1YeDv5/wb1wXWc/6N6wI5nH/jukAO59+4LjDn/Ju8+Zzzb/Lm6zn/Jm+ew/k3efN8zr/Jm+dw/k3ePIfzb1xPf4DD+Teupz/A4fwb11Pl5c1zOP/G9ZR8zr9xPSWH829cT8nh/Ju8Pee75M3nnH+TN1/P+Td58xzOv8mb53P+Td48h/Nv8uY5nH+Tt+ov0ZvPOf8mb76e82/8HkIO59/4PYR8zr/Jm+dw/k3ePIfzb/JWv6c3n3P+Td58Peff5M1zOP8mb57P+Td58xzOv8mb53D+jd7qmuGN+dTy5yfr+3+P++lN+f3J8xdmeFP+9Kb8/uT5CzO8KX96U35/8r+Xp3/O+3tdnS8gbz6fWl6fvn5peXnzHJ6/IG+ePwKH5y/Im+fw/AV58znPX6A35lPL69PXLy1/eiOH5y/QG/kjcHj+Ar2Rw/MX6I1573eXN59PLa9PX9/7JuXNc3j+grx5fv/+fX/y/AV585y+zt2f6T3O8xd4vzGfWl6fvn5p+fN+I4fnL/B+I38EDs9f4P1GDs9f4P3GnP/dU3nzOc9fmPF71vP8BXnzHJ6/IG+ez/MX5M1zeP6CvHnOFXL1354c9d+e+dTy+vT1S8vLm+fw/AU+p+SPwOH5C3xOyTkD5wo5z1+QN5/z/AV58/U8f0HePIfnL8ib5/P8BXnzHJ6/IG+ew/MX+H77AEf9t2c+tbw+ff3S8vLmOTx/ge838kfg8PwFvt/I4fkLfL8x5/kL8uZznr8gb76e5y/Im+fw/AV583yevyBvnsPzF+TNc3j+AteFAQ7PX+C6MMCZQz3PX+C6QA7PX+C6QD7PX+C6QA7PX+C6QM4Vcp6/IG8+5/kL8ubref6CvHkOz1+QN8/n+Qvy5jk8f0HePIfnL3A9/dG8+XxqeX36+qXl5c1zeP4C11PyR+Dw/AWup+ScgXOFnOcvyJvPef6CvPl6nr8gb57D8xfkzfN5/oK8eQ7PX5A3z+H5C/JW/aUnh+cvyBvz+vT1PH+B30PI4fkL/B5CPs9fkDfP4fkL8uY5PH9B3ur3T476b898avnzk/VLy8ub5/D8BXljXt48h+cvyJvn8PwF/vMw13XPxX2DN+bcf/qt8Z713H/6Dd7I4f7Tb/BGPvefFof7T7/BGzncf1qcK+TcfypvPuf+U3nz9dx/Km+ew/2n8ub53H8qb57D/afy5jncf0pvb+Bw/ym9vYHD/af09oa/n/tP6Y313H9Kb+Rw/ym9kcP9p/TGnPtP5c3n3H8qb76e+0/lzXO4/1TePJ/7T+XNc7j/VN48h/tPeb+9g8P9p7zf3sHh/lPeb+/4+7n/lPcb67n/lPcbOdx/yvuNHO4/5f3GnPtP5c3n3H8qb76e+0/lzXO4/1TePJ/7T+XNc7j/VN485wo595/yOWXO/ad8TlnP/ad8Tsnh/lM+p+Rz/ymfU3K4/5TPKTncfypv1V+iN59z/6m8+XruP5U3z+H+U3nzfO4/lTfP4f5TefMc7j/l++0DHO4/5fvtAxzuP+X77QN/P/ef8v3Geu4/5fuNHO4/5fuNHO4/5fuN+ev1f33QNPp4XnWdQa4dSRFF3xZYi3vuVbAFWAJrgSUwY8AIJh7kAAmkslAiGdEof7ZTpBOllEtA/kV0qG7EqUnpnb46bV29/+t1tOPlhw/fr58+/vB+//vHX75ff/z4IeUl8Nf7dUG+Bv779+uf4GmQ7+Af4JngWeDZ4DnAXy/792tvOS+BW295vgZuveWeBvkO/gGeCZ4Fng2eA/z1+s379Z/QW85L4NZbnq+BW2+5p0G+g3+AZ4JngWeD5wB/vX732+/Xt9Bbzkvg1luer4Fbb7mnQb6Df4BngmeBZ4PnAH+9/vyn79d/Q285L4Fbb3m+Bm695Z4G+Q7+AZ4JngWeDZ4D/PX694/frx16y3kJ3HrL8zVw6y33NMh38A/wTPAs8GzwHOB2ndBbzkvg9+sL8jVw6y33NMh38A/wTPAs8GzwHOB+/fpX369/SG/KS+D3qwvyNfBnb+ppkO/gH+CZ4Fng2eA5wF+vX7xff/ioveW8BH5bLsjXwK233NMg38E/wDPBs8CzwXOAW2+foDflJfBnb5qvgT97U0+DfAf/AM8EzwLPBs8Bbp9D/hJ6y3kJ3HrL8zVw6y33NMh38A/wTPAs8GzwHOD2fvsM7zflJXDrLc/XwJ/vN/U0yHfwD/BM8CzwbPAc4PbfWTX0lvMSuPWW52vg1lvuaZDv4B/gmeBZ4NngOcDt5/RL6C3nJXDrLc/XwK233NMg38E/wDPBs8CzwXOA2+feH0NvOS+BW295vgZuveWeBvkO/gGeCZ4Fng2eA9x+v72F3nJeArfe8nwN3HrLPQ3yHfwDPBM8CzwbPAf46/X+MfjDT6G3nJfArbc8XwO33nJPg3wH/wDPBM8CzwbPAe7zN+0t5yVw6y3P18Ctt9zTIN/BP8AzwbPAs8FzgPv8TXvLeQncesvzNXDrLfc0yHfwD/BM8CzwbPAc4D5/095yXgK33vJ8Ddx6yz0N8h38AzwTPAs8GzwHuM/ftLecl8CttzxfA7feck+DfAf/AM8EzwLPBs8B7vM37S3nJXDrLc/XwK233NMg38E/wDPBs8CzwXOA23VCbzkvgd+vL8jXwK233NMg38E/wDPBs8CzwXOA+2Wfi5+9KS+B368uyNfAn72pp0G+g3+AZ4JngWeD5wD3+Zv2lvMS+G25IF8Dt95yT4N8B/8AzwTPAs8GzwHu87e8N+Ul8Gdvmq+BP3tTT4N8B/8AzwTPAs8GzwHu8zftLeclcOstz9fArbfc0yDfwT/AM8GzwLPBc4D7/E17y3kJ3HrL8zXw5/tNPQ3yHfwDPBM8CzwbPAe4z9+0t5yXwK23PF8Dt95yT4N8B/8AzwTPAs8GzwHuzwntLeclcOstz9fArbfc0yDfwT/AM8GzwLPBc4D7/E17y3kJ3HrL8zVw6y33NMh38A/wTPAs8GzwHOA+f9Pecl4Ct97yfA3cess9DfId/AM8EzwLPBs8B7jP37S3nJfArbc8XwO33nJPg3wH/wDPBM8CzwbPAe7zN+0t5yVw6y3P18Ctt9zTIN/BP8AzwbPAs8FzgPv8TXvLeQncesvzNXDrLfc0yHfwD/BM8CzwbPAc4D5/095yXgK33vJ8Ddx6yz0N8h38AzwTPAs8GzwHuM/ftLecl8CttzxfA7feck+DfAf/AM8EzwLPBs8B7vM37S3nJXDrLc/XwK233NMg38E/wDPBs8CzwXOA23XE4/M37U35/fqCfA3cess9DfId/AM8EzwLPBs8B7hff/vr9+tf0pvyEvj96oJ8DfzZm3oa5Dv4B3gmeBZ4NngOcJ+/aW85L4HflgvyNXDrLfc0yHfwD/BM8CzwbPAc4D5/y3tTXgJ/9qb5GvizN/U0yHfwD/BM8CzwbPAc4D5/095yXgK33vJ8Ddx6yz0N8h38AzwTPAs8GzwHuM/f8veb8hK49Zbna+DP95t6GuQ7+Ad4JngWeDZ4DnCfv2lvOS+BW295vgZuveWeBvkO/gGeCZ4Fng2eA9znb9pbzkvg1luer4Fbb7mnQb6Df4BngmeBZ4PnAPf5m/aW8xK49Zbna+DWW+5pkO/gH+CZ4Fng2eA5wH3+pr3lvARuveX5Grj1lnsa5Dv4B3gmeBZ4NngOcJ+/aW85L4Fbb3m+Bm695Z4G+Q7+AZ4JngWeDZ4D3Odv2lvOS+DWW56vgVtvuadBvoN/gGeCZ4Fng+cA9/mb9pbzErj1ludr4NZb7mmQ7+Af4JngWeDZ4DnAff6mveW8BG695fkauPWWexrkO/gHeCZ4Fng2eA5wn79pbzkvgVtveb4Gbr3lngb5Dv4BngmeBZ4NngPc52/aW85L4NZbnq+BW2+5p0G+g3+AZ4JngWeD5wC364Tecl4Cv19fkK+BW2+5p0G+g3+AZ4JngWeD5wDXq0lvykvg9/2CfA382Zvz513zHfwDPBM8CzwbPAe4z9+MW285L4Hf9wvyNXDrLfc0yHfwD/BM8CzwbPAc4D5/e3p8/vbkJfD7fkG+Bv7sTT0N8h38AzwTPAs8GzwHuM/fnh6fvz15Cfy+X5CvgVtvuadBvoN/gGeCZ4Fng+cA9/nb0+Pztycvgd/3C/I18Of7TT0N8h38AzwTPAs8GzwHuM/fnh6fvz15Cfy+X5CvgVtvuUefJ95b7h/gmeBZ4NngOcDj3+e+cz5/e/IS+H2/IB//XqD1lnsa5Dv4498vue8TPAs88f/j3nf6/5Q+f3t6fP725CXw+35BvgZuveWeBvkO/gGeCZ4Fng2eA9znb0+Pz9+evAR+3y/I18Ctt9zTIN/BP8AzwbPAs8FzgPv87enx+duTl8Dv+wX5Grj1lnsa5Dv4B3gmeBZ4NngOcJ+/PT0+f3vyEvh9vyBfA7feck+DfAf/AM8EzwLPBs8B7vO3p8fnb09eAr/vF+Rr4NZb7mmQ7+Af4JngWeDZ4DnAff729Pj87clL4Pf9gnwN3HrLPQ3yHfwDPBM8CzwbPAe4z9+eHp+/PXkJ/L5fkK+BW2+5p0G+g3+AZ4JngWeD5wD3+dvT4/O3Jy+B3/cL8jVw6y33NMh38A/wTPAs8GzwHOB2HfH4/O3JS+D3/YJ8Ddx6yz0N8j1w6y33TPAs8GzwHOB+3T/Hb9Kbct0/tc81un/6Jr05f/amHv29+ya9qV/3T82j+6dv0pt6dP/UPLp/+vbz6/tzne1Xem851/1T7y3P6/6p95Z7dP/Ue8v9un/qveUe3T/13nKP7p9qb5/Eo/un2tsn8ej+qfb2Sf78un+qvWle90+1N/Xo/qn2ph7dP9XelOv+qfeWc90/9d7yvO6fem+5R/dPvbfcr/un3lvu0f1T7y336P6pvt8+i0f3T/X99lk8un+q7zfnz/ebenT/VN9v6tf9U32/qUf3T/X9ph7dP/Xenu8L7y3nun/qveV53T/13nKPfl723nK/7p96b7lH90+9t9yj+6f6c/pFPLp/qj+nX8Sj+6f6c+rcess9un+qP6fq1/1T/TlVj+6f6s+penT/1Huz+ZL2lnPdP/Xe8rzun3pvuUf3T7233K/7p95b7tH9U+8t9+j+qf5+exOP7p/q77c38ej+qf5+e5M/v+6f6u83zev+qf5+U4/un+rvN/Xo/qn+flOu+6feW851/9R7y/O6f+q95R7dP/Xecr/un3pvuUf3T7233KP7p/pcGOLR/VN9Lgzx6P6pPheG/Pl1/1SfC5rX/VN9LqhH90/1uaAe3T/V54Jy3T/13nKu+6feW57X/VPvLffo/qn3lvt1/9R7yz26f+q95R7dP9Xn6Tfx6P6pPk+/iUf3T/V56tx6yz26f6rPU/Xr/qk+T9Wj+6f6PFWP7p96b8/9Su8t57p/6r3led0/9d5yj+6fem+5X/dPvbfco/un3lvu0f1T783mS9pbznX/1HvL87p/qp9D1KP7p/o5RP26f+q95R7dP/Xeco/un3pv9s+1t5zr/qn3lud1/9R7yz06n/becr/un3pvuUf3T7233KP7p9rb6/XD/5+3z96Ul8DvVxfka+DP3tTTIN/BP8AzwbPAs8FzgPv8TXvLeQn8tlyQr4Fbb7mnQb6Df4BngmeBZ4PnAPf5W96b8hL4szfN18CfvamnQb6Df4BngmeBZ4PnAPf5m/aW8xK49Zbna+DWW+5pkO/gH+CZ4Fng2eA5wH3+lr/flJfArbc8XwN/vt/U0yDfwT/AM8GzwLPBc4D7/E17y3kJ3HrL8zVw6y33NMh38A/wTPAs8GzwHOA+f9Pecl4Ct97yfA3cess9DfId/AM8EzwLPBs8B7jP37S3nJfArbc8XwO33nJPg3wH/wDPBM8CzwbPAe7zN+0t5yVw6y3P18Ctt9zTIN/BP8AzwbPAs8FzgPv8TXvLuZ5/6r3leT3/1HvLPXr+qfeW+/X8U+8t9+j5p95b7tHzT/W5MMSj55/qc2GIR88/1efCkD+/nn+qzwXN6/mn+lxQj55/qs8F9ej5p/pcUK7nn3pvOdfzT723PK/nn3pvuUfPP/Xecr+ef+q95R49/9R7yz16/qk+T7+Jx+dv2pty6y3P18Ctt9yj55/q81T9Azx6/qk+T9WzwXOA6/mn3lvO9fxT7y3P6/mn3lvu0fNPvbfcr+efem+5R88/9d5yj55/6r3ZfEl7y7mef+q95Xk9/1Q/h6hHzz/VzyHq1/NPvbfco+efem+5R88/9d7sn2tvOS+B368vyNfArbfc0yCv5596b7lHzz/13nKPnn/qveXcr3sv9av0ply//+2r9KZ5/f63r9Kbehrk9fvfvkpv6tHvf/sqvalHv//NPPr9b19/fn1/rrPvN/Pecq7f/+a95Xn9/jfvLffo9795b7lfv//Ne8s9+v1v3lvu0e9/094+ffwfkHMVtHhedZ0xjnU5EUZ7C6wF8n8VbAGWwFpgCWQERJBM4ACJkS6Bg0ECeQwGjwePvAT091Wp5K/q3OSpD6Wj4ei9fj0VTH3716/Pv7/88vP545effT7ffPn24iXwj8/ngfka+O8/n7+Dp8F8B/8AzwTPAs8GzwH+8fG73359/iKeP37JeQncuuXzNXDrlnsazHfwD/BM8CzwbPAc4B8fb8+/hW45L4Fbt3y+Bn6/39TTYL6Df4BngmeBZ4PnAP/4sP9/2i3nJXDrls/XwK1b7mkw38E/wDPBs8CzwXOA2+f0u9At5yVw65bP18CtW+5pMN/BP8AzwbPAs8FzgH98/PMfn0/olvMSuHXL52vg1i33NJjv4B/gmeBZ4NngOcDt99v3oVvOS+DWLZ+vgVu33NNgvoN/gGeCZ4Fng+cA//j4+efzr9At5yVw65bP18CtW+5pMN/BP8AzwbPAs8FzgNv3wgjdcl4Ct275fA3cuuWeBvMd/AM8EzwLPBs8B/jHx28+n/+GbjkvgVu3fL4Gbt1yT4P5Dv4BngmeBZ4NngPcvk9/CN1yXgK3bvl8Ddy65Z4G8x38AzwTPAs8GzwH+MfHn//09fkxdMt5Cdy65fM1cOuWexrMd/AP8EzwLPBs8Bzg9nfIDt1yXgK3bvl8Ddy65Z4G8x38AzwTPAs8GzwHuD0ndMt5Cfz9+YH5Grh1yz0N5jv4B3gmeBZ4NngOcH9+/auvz3+km/IS+PvTA/M18LubehrMd/AP8EzwLPBs8Bzg9nfvH75ot5yXwF/LA/M1cOuWexrMd/AP8EzwLPBs8Bzg1u0b6Ka8BH530/ka+N1NPQ3mO/gHeCZ4Fng2eA5w379pt5yXwK1bPl8Dt265p8F8B/8AzwTPAs8GzwHu+7f8/aa8BG7d8vka+P1+U0+D+Q7+AZ4JngWeDZ4D3Pdv2i3nJXDrls/XwK1b7mkw38E/wDPBs8CzwXOA+/5Nu+W8BG7d8vkauHXLPQ3mO/gHeCZ4Fng2eA5w379pt5yXwK1bPl8Dt265p8F8B/8AzwTPAs8GzwHu+zftlvMSuHXL52vg1i33NJjv4B/gmeBZ4NngOcB9/6bdcl4Ct275fA3cuuWeBvMd/AM8EzwLPBs8B7jv37Rbzkvg1i2fr4Fbt9zTYL6Df4BngmeBZ4PnAPf9m3bLeQncuuXzNXDrlnsazHfwD/BM8CzwbPAc4L5/0245L4Fbt3y+Bm7dck+D+Q7+AZ4JngWeDZ4D3Pdv2i3nJXDrls/XwK1b7mkw38E/wDPBs8CzwXOA+/5Nu+W8BG7d8vkauHXLPQ3mO/gHeCZ4Fng2eA5we07olvMS+PvzA/M1cOuWexrMd/AP8EzwLPBs8Bzg+kzpprwE/r4+MF8Dv7s5f18bzHfwD/BM8CzwbPAc4L5/M27dcl4Cf18fmK+BW7fc02C+g3+AZ4JngWeD5wD3/dvt8f3bzUvg7+sD8zXwu5t6Gsx38A/wTPAs8GzwHOC+f7s9vn+7eQn8fX1gvgZu3XJPg/kO/gGeCZ4Fng2eA9z3b7fH9283L4G/rw/M18Dv95t6Gsx38A/wTPAs8GzwHOC+f7s9vn+7eQn8fX1gvgZu3XJPg/kO/gEe/V7ybrlng+cA9/3b7fH9281L4O/rA/M1cOuWexrMd/AP8EzwLPBs8Bzgvn+7Pb5/u3kJ/H19YL4Gbt1yT4P5Dv4BngmeBZ4NngPc92+3x/dvNy+Bv68PzNfArVvuaTDfwT/AM8GzwLPBc4D7/u32+P7t5iXw9/WB+Rq4dcs9DeY7+Ad4JngWeDZ4DnDfv90e37/dvAT+vj4wXwO3brmnwXwH/wDPBM8CzwbPAe77t9vj+7ebl8Df1wfma+DWLfc0mO/gH+CZ4Fng2eA5wH3/dnt8/3bzEvj7+sB8Ddy65Z4G8x38AzwTPAs8GzwHuO/fbo/v325eAn9fH5ivgVu33NNgvoN/gGeCZ4Fng+cA9/3b7fH9281L4O/rA/M1cOuWexrMd/AP8EzwLPBs8Bzg9hzx+P7t5iXw9/WB+Rq4dcs9DeZ74NYt90zwLPBs8Bzg/tjn+O6mvAT+/vTAfA387qaeBvMd/AM88ff33U09GzwHuO/ftFvOS+Cv5YH5Grh1yz0N5jv4B3gmeBZ4NngOcN+/5d2Ul8DvbjpfA7+7qafBfAf/AM8EzwLPBs8B7vs37ZbzErh1y+dr4NYt9zSY7+Af4JngWeDZ4DnAff+m3XJeArdu+XwN/H6/qafBfAf/AM8EzwLPBs8B7vs37ZbzErh1y+dr4NYt9zSY7+Af4Il/d1u33LPBc4D7/k275bwEbt3y+Rq4dcs9DeY7+Ad4JngWeDZ4DnDfv2m3nJfArVs+XwO3brmnwXwH/wDPBM8CzwbPAe77N+2W8xK4dcvna+DWLfc0mO/gH+CZ4Fng2eA5wH3/pt1yXgK3bvl8Ddy65Z4G8x38AzwTPAs8GzwHuO/ftFvOS+DWLZ+vgVu33NNgvoN/gCfuS6xb7tngOcB9/6bdcl4Ct275fA3cuuWeBvMd/AM8EzwLPBs8B7j/e5d2y3kJ3Lrl8zVw65Z7Gsx38A/wTPAs8GzwHOC+f9NuOS+BW7d8vgZu3XJPg/kO/gGeCZ4Fng2eA9z3b9ot5yVw65bP18CtW+5pMN/BP8AzwbPAs8FzgNtzxOP7N+2m/P35gfkauHXLPQ3mO/gHeOKe27rlng2eA9yfX3x+3/4o3ZSXwN+fHpivgd/d1NNgvoN/gGeCZ4Fng+cA9/2bdst5Cfy1PDBfA7duuafBfAf/AM8EzwLPBs8B7vu3vJvyEvjdTedr4Hc39TSY7+Af4JngWeDZ4DnAff+m3XJeArdu+XwN3LrlngbzHfwDPBM8CzwbPAe479/y95vyErh1y+dr4Pf7TT0N5jv4B3gmeBZ4NngOcN+/abecl8CtWz5fA7duuafBfAf/AM8EzwLPBs8B7vs37ZbzErh1y+dr4NYt9zSY7+Af4JngWeDZ4DnAff+m3XJeArdu+XwN3LrlngbzHfwDPBM8CzwbPAe479+0W85L4NYtn6+BW7fc02C+g3+AZ4JngWeD5wD3/Zt2y3kJ3Lrl8zVw65Z7Gsx38A/wTPAs8GzwHOC+f9NuOS+BW7d8vgZu3XJPg/kO/gGeCZ4Fng2eA9z3b9ot5yVw65bP18CtW+5pMN/BP8AzwbPAs8FzgPv+TbvlvARu3fL5Grh1yz0N5jv4B3gmeBZ4NngOcN+/abecl8CtWz5fA7duuafBfAf/AM8EzwLPBs8B7vs37ZbzErh1y+dr4NYt9zSY7+Af4JngWeDZ4DnA7TmhW85L4O/PD8zXwK1b7mkw38E/wDPBs8CzwXOA+/P+d+H+J92Ul8Dfnx6Yr4Hf3dTTYL6Df4BngmeBZ4PnAPf9m3bLeQn8tTwwXwO3brmnwXwH/wDPBM8CzwbPAe77t7yb8hL43U3na+B3N/Xo/QXtpv4BHr2/oN3Uo/cXtJtyvb/g3XJeArdu+XwN3LrlHr2/4N1y/wCP3l/wbrlH7y94t5zr/QV9vykvgVu3fL4Gfr/f1KP3F/T9pv4BHr2/oO839ej9BX2/Kdf7C94t53p/wbvl83p/wbvlHr2/4N1yv95f8G65R+8veLfco/cX9HP6nXh8/6bdlFu3fL4Gbt1yj95f0M+p+gd49P6Cfk7Vo/cX9HOqXO8veLec6/0F75bP6/0F75Z79P6Cd8v9en/Bu+Uevb/g3XKP3l/Q32/fi8f3b9pNuXXL52vg1i336P0F/f2m/gEevb+gv9/Uo/cX9Pebcr2/4N1yrvcXvFs+r/cXvFvu0fsL3i336/0F75Z79P6Cd8s9en9BvxeGePT+gn4vDPHo/QX9Xhjyz6/3F/R7Qef1/oJ+L6hngkfvL+j3gnr0/oJ3s/2Sdsu53l/wbvm83l/wbrlH7y94t9yv9xe8W+7R+wveLffo/QX9Pv1BPL5/027KrVs+XwO3brlH7y/o96n6B3j0/oJ+n6pH7y/o96lyvb/g3XKu9xe8Wz6v9xe8W+7R+wveLffr/QXvlnv0/oJ3yz16f8G72X5Ju+Vc7y94t3xe7y/o3yHq0fsL+neI+vX+gnfLPXp/wbvlHr2/4N3sf9duOS+Bvz8/MF8Dt265R+8veLfcP8AzwaP3F7xb7jnA/Xn/u3A/STflJfD3pwfma+B3N/Xo/YWfpJv6B3gmePT+wk/STT0HuN5f8G45L4G/lgfma+DWLffo/QXvlvsHePT+gnfLPXp/wbvlXO8vaDflJfC7m87XwO9u6tH7C9pN/QM8en9Bu6lH7y9oN+V6f8G75bwEbt3y+Rq4dcs9en/Bu+X+AR69v+Ddco/eX/BuOdf7C/p+U14Ct275fA38fr+pR+8v6PtN/QM8en9B32/q0fsL+n5TrvcXvFvO9f6Cd8vn9f6Cd8s9en/Bu+V+vb/g3XKP3l/wbrlH7y/o5/Q78fj+Tbspt275fA3cuuUevb+gn1P1D/Do/QX9nKpH7y/o51S53l/wbjnX+wveLZ/X+wveLffo/QXvlvv1/oJ3yz16f8G75R69v6C/374Xj+/ftJty65bP18CtW+7R+wv6+039Azx6f0F/v6lH7y/o7zflen/Bu+Vc7y94t3xe7y94t9yj9xe8W+7X+wveLffo/QXvlnv0/oJ+Lwzx6P0F/V4Y4tH7C/q9MOSfX+8v6PeCzuv9Bf1eUM8Ej95f0O8F9ej9Be9m+yXj/wcPgkTdeF51mLGtFDEURd0CtVDAVvFbgBKoBUogI9gIEgIHZCZwABLIGFkYIyOXgHatx2rmvvOSpz26e6S9mp0Z+enpNtfLq/v8urx8cZu/lzt+isLDfRLks/C39/kKngL5Cv4Gng6eAZ4JngU8hDevb/P75LlefB6FW29+Pgu33nxPgXwFfwNPB88AzwTPAh7Ch/e3+SO9+TwKt978fBZuvfmeAvkK/gaeDp4BngmeBTyE799uM6U3n0fh1pufz8KtN99TIF/B38DTwTPAM8GzgNss6c3nUfj+nCCfhVtvvqdAvoK/gaeDZ4BngmcBP8869XbmUfjeCfJZ+LG3B9+7QL6Cv4Gng2eAZ4JnAQ/h2X3e/c9Zbz6PwvdOkM/CrTffUyBfwd/A08EzwDPBs4Bbbx+htzOPwvdOkM/Cj72dPQXyFfwNPB08AzwTPAu4vYd8kt58HoXvnSCfhVtvvqdAvoK/gaeDZ4BngmcBt+vts/Tm8yh87wT5LPx4vZ09BfIV/A08HTwDPBM8C3gI9vuOnqs8D623M987QT4Lt958T4F8BX8DTwfPAM8Ez/n5dv6ffpHefB6F750gn4Vbb76nQL6Cv4Gng2eAZ4JnAbf33m/Sm8+j8L0T5LNw6833FMhX8DfwdPAM8EzwLOB2f/shvfk8Ct87QT4Lt958T4F8BX8DTwfPAM8EzwIewvP7/JTefB6F750gn4Vbb76nQL6Cv4Gng2eAZ4JnAbfnQjt5rhefR+F7J8hn4dab7ymQr+Bv4OngGeCZ4FnAQ7DzpWPucf525FH43gnyWbj15nsK5Cv4G3g6eAZ4JngW8Mf529HzOH878ih87wT5LNx68z0F8hX8DTwdPAM8EzwL+OP87eh5nL8deRS+d4J8Fm69+Z4C+Qr+Bp4OngGeCZ4F/HH+dvQ8zt+OPArfO0E+C7fefE+BfAV/A08HzwDPBM8CbnO+D17l+9bbme+dIK/v39ab7ymQr8KtN9/TwTPAo33R/gcuOnQL - - - 0 - - - 1.7320508076 - - - - - - - AQAAAACAAADAVQAASRMAAA==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 - - - AQAAAACAAABwFQAAvgMAAA==eF411xGbAogCQNF5+3q77X5BEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEATBwEAQBEEQBEEQBEEQLMx5V85vuIGv34IMMcwIo4wxzgSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzwGHHHHMCaeccc4Fl1xxzQ233PGbP9zzwCNPPPPCK2+888EnX3zzw6///BJgkCGGGWGUMcaZYJIppplhljnmWWCRJZZZYZU11tlgky222WGXPfY54JAjjjnhlDPOueCSK6654ZY7fvOHex545IlnXnjljXc++OSLb3749ccvAQYZYpgRRhljnAkmmWKaGWaZY54FFllimRVWWWOdDTbZYpsddtljnwMOOeKYE04545wLLrnimhtuueM3f7jngUeeeOaFV95454NPvvjmh1///SXAIEMMM8IoY4wzwSRTTDPDLHPMs8AiSyyzwiprrLPBJltss8Mue+xzwCFHHHPCKWecc8ElV1xzwy13/OYP9zzwyBPPvPDKG+988MkX3/zwKwAGGWKYEUYZY5wJJplimhlmmWOeBRZZYpkVVlljnQ022WKbHXbZY58DDjnimBNOOeOcCy654pobbrnjN3+454FHnnjmhVfeeOeDT7745odf//slwCBDDDPCKGOMM8EkU0wzwyxzzLPAIksss8Iqa6yzwSZbbLPDLnvsc8AhRxxzwilnnHPBJVdcc8Mtd/zmD/c88MgTz7zwyhvvfPDJF9/88OvPXwIMMsQwI4wyxjgTTDLFNDPMMsc8CyyyxDIrrLLGOhtsssU2O+yyxz4HHHLEMSeccsY5F1xyxTU33HLHb/5wzwOPPPHMC6+88c4Hn3zxzQ+//volwCBDDDPCKGOMM8EkU0wzwyxzzLPAIksss8Iqa6yzwSZbbLPDLnvsc8AhRxxzwilnnHPBJVdcc8Mtd/zmD/c88MgTz7zwyhvvfPDJF9/88P/DH2CQIYYZYZQxxplgkimmmWGWOeZZYJEllllhlTXW2WCTLbbZYZc99jngkCOOOeGUM8654JIrrrnhljt+84d7HnjkiWdeeOWNdz745Itvfvj19y8BBhlimBFGGWOcCSaZYpoZZpljngUWWWKZFVZZY50NNtlimx122WOfAw454pgTTjnjnAsuueKaG2654zd/uOeBR5545oVX3njng0+++OaHX//8EmCQIYYZYZQxxplgkimmmWGWOeZZYJEllllhlTXW2WCTLbbZYZc99jngkCOOOeGUM8654JIrrrnhlv8CkVFZCg== - - - AQAAAACAAACuAgAADwAAAA==eF7j5BwFo2CoAABgzxgf - - - - - diff --git a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_x_perTrue.vtu b/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_x_perTrue.vtu deleted file mode 100644 index adeddd7cb..000000000 --- a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_x_perTrue.vtu +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - BAAAAACAAACAGQAA9g4AADYOAADfDQAA8QIAAA==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 - - - 0 - - - 1.7320508076 - - - - - - - AQAAAACAAAAAYgAAJhYAAA==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 - - - AQAAAACAAACAGAAAPgQAAA==eF412BGjAggChdG3u81OuxMEQRAEQRAEQRAEQRAEQRAEQRAEQRAEQRAEQRAEQRAEQRAEwYMHQRAEQRAEQRAEA+/MlfMb7hf4+l2QIYYZYZQxxplgkimmmWGWOeZZYJEllllhlTXW2WCTLbbZYZc99jngkCOOOeGUM8654JIrrrnhljvueeA3f3jkiWdeeOWNdz745Itvfvj1r18CDDLEMCOMMsY4E0wyxTQzzDLHPAssssQyK6yyxjobbLLFNjvsssc+BxxyxDEnnHLGORdccsU1N9xyxz0P/OYPjzzxzAuvvPHOB5988c0Pv/79S4BBhhhmhFHGGGeCSaaYZoZZ5phngUWWWGaFVdZYZ4NNtthmh1322OeAQ4445oRTzjjngkuuuOaGW+6454Hf/OGRJ5554ZU33vngky+++eHXf34JMMgQw4wwyhjjTDDJFNPMMMsc8yywyBLLrLDKGutssMkW2+ywyx77HHDIEceccMoZ51xwyRXX3HDLHfc88Js/PPLEMy+88sY7H3zyxTc//AqAQYYYZoRRxhhngkmmmGaGWeaYZ4FFllhmhVXWWGeDTbbYZodd9tjngEOOOOaEU84454JLrrjmhlvuuOeB3/zhkSeeeeGVN9754JMvvvnh1x+/BBhkiGFGGGWMcSaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPfQ445IhjTjjljHMuuOSKa2645Y57HvjNHx554pkXXnnjnQ8++eKbH37995cAgwwxzAijjDHOBJNMMc0Ms8wxzwKLLLHMCqussc4Gm2yxzQ677LHPAYccccwJp5xxzgWXXHHNDbfccc8Dv/nDI08888Irb7zzwSdffPPDrz9/CTDIEMOMMMoY40wwyRTTzDDLHPMssMgSy6ywyhrrbLDJFtvssMse+xxwyBHHnHDKGedccMkV19xwyx33PPCbPzzyxDMvvPLGOx988sU3P/zn8AcYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSIY0445YxzLrjkimtuuOWOex74zR8eeeKZF155450PPvnimx9+/e+XAIMMMcwIo4wxzgSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzwGHHHHMCaeccc4Fl1xxzQ233HHPA7/5wyNPPPPCK2+888EnX3zzw6///xJgkCGGGWGUMcaZYJIppplhljnmWWCRJZZZYZU11tlgky222WGXPfY54JAjjjnhlDPOueCSK6654ZY77nngN3945IlnXnjljXc++OSLb3749dcvAQYZYpgRRhljnAkmmWKaGWaZY54FFllimRVWWWOdDTbZYpsddtljnwMOOeKYE04545wLLrnimhtuueOeB37zh0eeeOaFV95454NPvvjmh/+E/gCDDDHMCKOMMc4Ek0wxzQyzzDHPvwEDBI18 - - - AQAAAACAAAAQAwAAEAAAAA==eF7j5BwFo2AUwAAAR84bkQ== - - - - - diff --git a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_y_perFalse.vtu b/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_y_perFalse.vtu deleted file mode 100644 index 690af021c..000000000 --- a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_y_perFalse.vtu +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - BAAAAACAAACAGQAA9g4AADYOAADfDQAA8QIAAA==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 - - - 0 - - - 1.7320508076 - - - - - - - AQAAAACAAACAQAAAfA0AAA==eF5d21WY1QUeh/EZeoocGqQVhBlgF6RXUVJB0mCXcgUZSqR16bRI6RhEQlE6LVK6VUA6hBmUHqSFgb2Y9704f24+z7yPcM78z+977swWlvbnLJ7D7JgDf8fzmDOg/QLmCmhPwljMgxfxD8yLxfAm/oXFA9pvYQksjffxAZbBSpghPM2MWJleE6PpMViLXh/z0vNhg7BQ7fmxIf1lLEAviK/QG2EhemFsTG+BJeglsSX9TSxNL4Ot6G0xnl4e24WFaq+A7env4HP0KtiJnoBV6dWwc1io9urYJSxUew3sSu+JtekvYi/6BGxPfwsn0qdhJ3oCTqfPxm707phIX4wD6ANxCX0LTqR/hj/R3d1y+go8R3dXq+ir0T2pfQ26M7WvxWS6u9ri+0L35G520XejO3I3h/EIuiN3cwpPoztyF3/iJcwf0H4ZCwS0X0F35G6u4jV0R+7lNt7BUuhu7vt7oztyF08wLF0a7qRCoIdjRbq7yUTPjO7I3WShR6B7cQ9R9GisSXcXOeg50Z3Uwzz0vFg/PFR7PmxAb47F6SWwRXio9pLYkv4u/ov+PPag98E69LrYl97f9+n7wvfpI3wfvi6OpH+Mrej/xk/o47EdvT1OoLvbIfSh6I7d3Uj6KHSH7mo6fQa6M3e1gL4Q3Zm7WkJfiu7Mu19P34DuwHvfTt+Bd8NDte/Ee+Gh2nehe1H7bnRH7uJn/AXDA9p/xXSY0eeBv6F7cg+nfC4Yg7nwAiZhLObGZLyIwb38gX9ivoD2S5gfn8LreAOLoLu5hbfRHcVhKj7GeKyI4enTTIf/oFfDCHokVqe7t9z0PFiP3ggL0QtjY3ozLEYvjs3pbX2/9PLo3txVE3pTdGfuqiX9NXRn7iqB3hndmbvqQ++L7sxdDaQPQnfmrsbQx6I7czdT6dPQvah9OroXtc9Ad+Qe5tHn4yG6976UvgzPpAvVvhzP0s/jKvpqdC/uYi19HboT7/47+vfoDtT+A16mu4dN9M2Yki5U+xa8SXc3W+nb0B2pfTveoT/EvfR9+IiezeeF5zA7xmISJqN7ehb/xodYFuMwFR+je3EXRTKkWRTdibsoSS+F7sRdVKVXQ3fiLurQ66I7cReN6I3RnbiL1vQ26E686wRfF71z77k3vQ8uSB+qvS8upHvXg+lD0DtX+1BcQffuh9NHoDvw7kfRR6M78J4/oX+KP9I34jj6eAze+wT6RPTOvetJ9MnoPat9Cnrnap+KO9KHap+GO+m/4jz6fDxEP4pf0RfhMfo13EDfiNfpKT4Hf2+8SXc3e+h70R2pfR8+Sh+qfT+6N3d1A1PQnbmr23gH3Zm7ypIxzQh0Z+4qlp4b3Zm7KkQvjO7MXZWjx6E7c1dVfV10Z+6mjq+L7shdNKE3RXei9mY4LEOo9ubontTeAt2Zu3qN/jq6M/f0H3prHEN3N+3pb6E7cjdv0zuge1F7Rwzu5R16J3Qnak/A6fSZ2IXeFWfR3VUvem90Zyv8HHzuuJK+GkfQR+IauruZ6euie1H7bHQvak9Ed+RuNtE3oztyN9vo29EduZtjeBzdkbtJwmR0R+7mKl5Dd+RuHmEquiN3kyVTmhHojtxNLD03uiPvvhi9OHrvai+B7sC7L0V/Gt2B916OHodtMoZqj8e2dHdRgV4R3Ym7+Ce9EroHtVdGd+LdV6FXRXeg9mrYme7dv0h/Cd2B2utgH3o/rEevj/3p47AtvR2OzxiqvT1OoLubLvSu6F68+/H+PXQH3v1k+hR0B979IvrX6A68+zX0tegOvPv19A3oDrz7ffT96A68+2O+LroD7z7J10V34F3/hbfQO/eu7+Bd9M6921R8jPEB7U+wPHrXYZnTCEfv3LtOT8+A3rPaM6J37r1G0COxeqZQ7VFYg+5d56TnQu9Z7bHonas9N9bNFKo9D7oHtedFd9IMi9GLY/NModpLYAu6u4qnl0f3pPYK6M7cVXWfG7ozd9XOv4fuzF11oHdEd+au3qd/gO7MXY2kj0J35q7G0MeiO3NXs+mJ6M7c1SJfF92Zu1rj66I7S8HN9C14k+7ufqJvRXfo7rbTd6A7fOTv4ftG9+jeDtJ/xrCA9l/QHbq7Q3gYMwS0H0F3mAWP+dzQPbq3k3gKo9HdnccLmCugPQljA9qT0b2VxNt4B0uhu3iMTzC4h7AsaYRjRbq7iKRHoTup6e9Lj8FadHdTnl4B3ZG7qUSvjO7I3TSgN0R35G5a0l9Dd+RuWtPboDtyN93o3dEdfYX96e+je1L7B+jOVuMI+kgM7m0UfTSuo2/C8fQJ6B7d2yT6ZNxGd3dT6FPRHe7FWfTZ6B7dzXz6AnQval+I7uioz9Hnhu7pNC6lL8MzdHezir4a3Yva12BwL2t9rniRfgm/p/+Al+kpuJm+BW/S3dt2nyvepbu7A/SD6A7d1Qk8ie7MXZ3C0+jOsvq88CxmQ3f3BMMi0nCH7i4DPSO6Q3eXj54f3aG7K0kvhe7Q3ZWjx6E7dHc16bXQHfbDevT66B7V3gDdo9obojt1b6/T38AP6eOwLb0duke1t0d36u460t9BdzgDO9O74Mwsodq7onudh73ovdG9qr0Pule190V3rPZ++CXdXY2ij0Z3tsnn5HNB97QTp9Gn4y76IX8P3zceprubb+iL0R2pfQm6J/eynL4Cz9HdzUH6z+iO0uMh3xe6p0z4Gx7FzJgDf8fzmDOg/QLmQnf5J15Cd+oub+MddKfu8hGmojutjpGRaUZhDbq7jabHoDuui7npedA9qz0vBvecj54f3XEzLEYvjs3p7vpp+jPozt/E0vQy2Ire2udEj8M2EaHa4zH4fVGeXgH9nvD74Dl6FexET8Cq9Gro94W7rUOvi+51KDalN8NhEaHam+Nw+hifg783jqV/hv+lv42T6LOxG707JtLd7f/oA9C9rvR9+r5wFd1dfk6fi+70V5xHn4/u9RyuoK9E95iEa+hrMZnu7r6jf4/u8DpupG/CG/QU3Ezfgjfp7nYbfTu6Y3e7j74f3XEWfj6GxzEioP0ERga0n8SogPZTGB3Qfhr9nsiFFzAJYwPak9HvE78PLuFlLIAF8QpexUJYBG9gChZFvw8e4N/4bED7Qywb0P4I/T6pidFRacZgLXpbjKeXx3b0LlidXgO70nvg8/QX8D16P6xHr4/96YnYnf4uzqG7ux7+u+gO1d4Tv4gM1d4L3avae6M7drdD6cPQHbvXUfTRuI7ubj+if4zu2N1Npk9Bd7gXZ9Fno3tUeyK60wM4h/45HqT/hl/Sv8KjkaHaF6F7d5eL6UvQPap9KbrTM7iMvhzP0s/jKvpqdM8XcR39W/yDfg030Dfi9chQ7ZvQvbrL3fQ96B7z8/MlvIwFsAHmi+a/x4b0RliIXhgb07375+kvoPeu9trovQ/AV+iNcCB9ML5Kb4JDokK1N0X38CG+QX8TvXe1t0J3MBbb0NviOLq76EDviO5kBnamd8GZUaHau6J7Uns3dE9q747uzD318HPBufSF2JfeD92buxhIH4TuxF0M9nNBd7ICh9KH4cqoUO3D0T2txzH0sehetuIk+mTcRr+LO+g78R7du76K19A7966zxaSZHb1n77UQvTA2jg7V/hR6z2ovgt6zd1maXga9U+8yjh6P3ql3WYleGb3TTliFXhUTokO1V0Pv2busQa+J3qPaa6F32hNr01/EXvTe+BK9Dvahe29N6E3Re5uBneldcCZ9Lr5H74lf0L2bqfRp6B3l4ucLmISx6F1cxWtYOKD9OnoPfu738QF6B2XxIT7CcuhdpOJj9E68iwxZ08yI3ol3kZmeBb0HtUegd+JdRNGj0XtQewx6J2rPiv+iezc56DnRO1J7LvSe1B6L3tnLWIBeEF+he1fV6NXRO/OuXqDXRu9sPvam98EF9LO4nL4Cz9G9u9X0NegdXsEf6esxeI8b6BvRO1T7JrxBv4c76bvQe1X7bvSOvdd99P3ovar9AHrH3u1hPILesXd5DI9jRED7CYxE7/YknsLogPbTGBPQfga942x4Fs9hdvQekzAZc6N3eRmvoHdaCK/iNSyMT+F1vIFFAtpTsGhA+00shvUwT7Y082J9emPfD/0pfJU+FJvSm+Ew+lIcRB+My+juZgh9KLojtQ/DlXTvfhx9PLoDtU/AzVlDtU/ELfTtOIU+FXfQ3c10+gx0R+4ikT4H3YnaP8eDdHezgL4Q3ZG7+Jr+DboTtS/G4F6W0Jeie1D7MnQnal+O7sU9rKWvw4t09/AjfT26B7VvQHfiLjb6uWNwD5v9XNEdqP0n/It+D3fSd+F9+iPcR9+PqfQS/HwLb2NJfBb/xodYFt1VUf6H5GLoztxNE3pTdC9qb4buSO3NcTjd3bSjt0f3ova30B1Nxg70juie3E0CvTO6I3fxLr0HupO5+B69J35Bdzd96H3RHbmHAfSB6B7UPgjdg/c8mv4hfpstVPtH+B39B/yE/im6B7WPQfeg9rHoTrzrifTP0DtX+yTcSt+NM+gzcQ99L86iz8bgHhLpc/AA/bCfk58LHqEfx6/p3+AJurvaSt+G7sxd7aHvRXdWgJ8v4xUsiE/jXbyHz2BpvI8PsAzGYSo+xnj8PxQaYss= - - - AQAAAACAAAAgEAAA4QIAAA==eF411xGDAgYAQOG2tS0IgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiA4OAiCIAiCIAiCIAiCIAiC4CAIgsF9e/L9hhcM/BZimBFGGWOcCSaZYpoZZpljngUWWWKZFVZZY50NNtlimx122WOfAw454pgTTjnjF78554JLrrjmhlvuuOeBR5545oVX3njng0/+8MU3Pwz88UuQIYYZYZQxxplgkimmmWGWOeZZYJEllllhlTXW2WCTLbbZYZc99jngkCOOOeGUM37xm3MuuOSKa2645Y57HnjkiWdeeOWNdz745A9ffPPDwJ+/BBlimBFGGWOcCSaZYpoZZpljngUWWWKZFVZZY50NNtlimx122WOfAw454pgTTjnjF78554JLrrjmhlvuuOeBR5545oVX3njng0/+8MU3Pwz89UuQIYYZYZQxxplgkimmmWGWOeZZYJEllllhlTXW2WCTLbbZYZc99jngkCOOOeGUM37xm3MuuOSKa2645Y57HnjkiWdeeOWNdz745A9ffPPDQBAMMcwIo4wxzgSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzwGHHHHMCaec8YvfnHPBJVdcc8Mtd9zzwCNPPPPCK2+888Enf/jimx8G/v4lyBDDjDDKGONMMMkU08wwyxzzLLDIEsussMoa62ywyRbb7LDLHvsccMgRx5xwyhm/+M05F1xyxTU33HLHPQ888sQzL7zyxjsffPKHL775YeCfX4IMMcwIo4wxzgSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzwGHHHHMCaec8YvfnHPBJVdcc8Mtd9zzwCNPPPPCK2+888Enf/jimx8G/v0lyBDDjDDKGONMMMkU08wwyxzzLLDIEsussMoa62ywyRbb7LDLHvsccMgRx5xwyhm/+M05F1xyxTU33HLHPQ888sQzL7zyxjsffPKHL7754f/DH2SIYUb4H7j+A2A= - - - AQAAAACAAAAEAgAADgAAAA==eF7j5BwFIx0AAFRsEiU= - - - - - diff --git a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_y_perTrue.vtu b/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_y_perTrue.vtu deleted file mode 100644 index e130eeef7..000000000 --- a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_y_perTrue.vtu +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - BAAAAACAAACAGQAA9g4AADYOAADfDQAA8QIAAA==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 - - - 0 - - - 1.7320508076 - - - - - - - AQAAAACAAACASQAAiA8AAA==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 - - - AQAAAACAAABgEgAAQAMAAA==eF411xGXAogCgNHZt+3b9pwgCIIgCIIgCIIgCIIgCAaCIAiCIAiCIAiCIAiCIAiCIAiCIAiCYCAIgiAIgiAIFubuJ/c3fIGv34IMMcwIo4wxzgSTTDHNDLPMMc8CiyyxzAq/WWWNdTbYZIttdthlj30OOOSIY0445YxzLrjkimtuuOWOex545Ik/PPPCK2+888EnX3zzw68/fgkwyBDDjDDKGONMMMkU08wwyxzzLLDIEsus8JtV1lhng0222GaHXfbY54BDjjjmhFPOOOeCS6645oZb7rjngUee+MMzL7zyxjsffPLFNz/8+t8vAQYZYpgRRhljnAkmmWKaGWaZY54FFllimRV+s8oa62ywyRbb7LDLHvsccMgRx5xwyhnnXHDJFdfccMsd9zzwyBN/eOaFV95454NPvvjmh19//hJgkCGGGWGUMcaZYJIppplhljnmWWCRJZZZ4TerrLHOBptssc0Ou+yxzwGHHHHMCaeccc4Fl1xxzQ233HHPA4888YdnXnjljXc++OSLb374FQCDDDHMCKOMMc4Ek0wxzQyzzDHPAossscwKv1lljXU22GSLbXbYZY99DjjkiGNOOOWMcy645IprbrjljnseeOSJPzzzwitvvPPBJ19888Ovv34JMMgQw4wwyhjjTDDJFNPMMMsc8yywyBLLrPCbVdZYZ4NNtthmh1322OeAQ4445oRTzjjngkuuuOaGW+6454FHnvjDMy+88sY7H3zyxTc//Pr/LwEGGWKYEUYZY5wJJplimhlmmWOeBRZZYpkVfrPKGutssMkW2+ywyx77HHDIEceccMoZ51xwyRXX3HDLHfc88MgTf3jmhVfeeOeDT7745odff/8SYJAhhhlhlDHGmWCSKaaZYZY55llgkSWWWeE3q6yxzgabbLHNDrvssc8BhxxxzAmnnHHOBZdccc0Nt9xxzwOPPPGHZ1545Y13Pvjki29++N/wBxhkiGFGGGWMcSaYZIppZphljnkWWGSJZVb4zSprrLPBJltss8Mue+xzwCFHHHPCKWecc8ElV1xzwy133PPAI0/84ZkXXnnjnQ8++eKbH37980uAQYYYZoRRxhhngkmmmOa/1h0mPQ== - - - AQAAAACAAABMAgAADwAAAA==eF7j5BwFo4A6AADLgxSt - - - - - diff --git a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_z_perFalse.vtu b/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_z_perFalse.vtu deleted file mode 100644 index 5cba040dd..000000000 --- a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_z_perFalse.vtu +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - BAAAAACAAACAGQAA9g4AADYOAADfDQAA8QIAAA==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 - - - 0 - - - 1.7320508076 - - - - - - - AQAAAACAAAAgKwAAUggAAA==eF51znd0z/cex3EiZIid2CsxayR0IaH0XkncliLcc+teQntva4QaCdpSSez2kpixlcRIa0uomUGNmq0ZMaIkSBCJ2hK9f/ye7z/yOpd/Huf78vy9PzlXyvHvPN7A3/EiZmAOZst+CW9JZ3sm3n5NZ99/4EP0KO2wAtbGWlgDa2ITbIyN5LsttkFf9MOOGIAd0B+DMUh267tJF4J9cBAOlL0vfvKazr5H4HAcjENwLEbgFzgSJ+IEHI9f4jScilPkey7Okd1+N0+6b/E7XIRxGCv3VuBy2e3eSunW4jpMwu2yr8dk6WxPxB3SJct+ANPxDubiE3wsex4+lc72u/hMukdyz8nJYWmnkv1zLMdeFv9kL8V3BfRwKtm7oCdWQ1d0w+roheXlXi2siVWwKjbA+nLH7jZCH9lrYGPpWqMvBqA/tsU3sTO+hx2kD8Yg2e1uN+m6YiCGYG/Z7V4f6Wy3e32l64498J/YT+5YPwgHYqh8D8cwPISH8SyewV/xN7yGV2W3Pku683gBb+INvCL38jBX+mwsxAK8jXfwMT7CfHyARfhSdrtXLJ3t9u4r6ewde9epjMPS+FLuubG7lim52z33Mv+/s+/q6IWVsDLWw7roKb0Peou2+6EvtsCW2B7bYWvpA9Af43ARrsEE2RfjWunipd+KW3ATbsa9uAe3YxKmYSruwt14GA/JbveOSJci907gcTyDZ/E6ZuFFzMAczMb7mI8v8QUWYCG+wmLpi9DN2aGrc8ndenfnkl04RmA0RuEkjMRZ+F/pJ2MsxuB30i/GRbgQ4zAB43EpLsNEXI9bcRum4H5MwmRMxzTsWtZhIIZgb+yJvXAA9pfd+lDp/iX9UBwinf0uDIfh5zgYIzBctD0ao3AcjsepOAVjMBaX4zLZ5+AK6TzLOfRCH/QWbfdDX2yFrdEfO8hufYB07bA9BmJX2e1ekHS2271g6XpjCA7EUNn74CDpCrAQX2ExPsQ/sJSLgz/5LpLenf93Q7tnvQd7eXSVvjp6obv0NbGGdPa7Rugju/WNpbPd7jaRzlvu+aGv7HavjXS22722r+nsuxN2xPWYiDswGZPkOx3TpN+JB/EApkp/Ek9I/zP+hr/icekvYQaelv4qXpHd7l6T7qLcy8Fs2e3dW9Jdlvdy8Y7s9l6edDflvUIskDvWP8HH+Ei+nVwdlsZS8l0RK8huv6v0ms6+62BtbIbN8R18G8fheJyKU/BL/Aqn4zT5noELcD7GYCwux2W4EOMwAeNxCS7F9bhOdruXKN1a6ZNwu+zWJ0u3CTfjXtwjd6xPw1TcLf0RPCy73T0qnd1Lx1N4Eg/JvbN4Rna7d046u2N3L2Om7KfxinT2jt27jlnSX8U8zMVr0t/Du/gEn6Kzm8My+Iz9OZZjL+tWcn+BLm4lO2fpq2IV2a2vJp0/BmA3DMZADMI+GIK95Xsghspuvxsk3cfYDz/Hz7A/DsBhOFR2ey9MOrs3GCMwHIfIvXE4FsdIH4WRstvdaOlst3uTpYuUfjbOwmk4HefjPIzFObgCl+MiXIxrcQ0myPc23Cq7/W67dBtwI+7GXbhF7u3HfbLbvRTpDuFhPItn8Bgexwy8KP05/B2v4wXps/Gm7HY3Rzq7dwMfYL7sdq9AOtvtXuFrOvsuxiJ0dXfohtXRS3Z3rCFdefTAWlgTq6EnemND2e09H+nsXm1shk2xDtbFN7A5NpD3WmMr2e09X+nsXgtsh+/K3hLbS2e7vddBOj9sg52wo9yxPhC7Yi/sjaE4QPYQHChdf+mH4VDZrQ+T7lP8N47CkXLH+vE4DofjCPwavxJtn4HTZZ+AM6WLxskYizE4B+fiSlyB83EBrsZVsi/EeOmW4XL8ARNF23fiDkyW7wOYjimYiifwOKZJfwpPym53T0tnd+zuJcyQ3e5lSme73bss3QW8iNl4U3Z7L0c6e8fu3cZbeAWvYh7m4jXMwnt4F2/I31OAD/A+5uNLfCG79UXSeZR3WAFrYy30RT/siAHoL9/dMFh2+93fpOuMXbAHdscgudcX+8hu9/4u3Sf4KY7EL3CEfE/Ar2W3302UbjSOwUicJHs4Rkln79i9mThD+miMwdk4DafjfJwn2r4Kv8cluBTX4zrZl2GidCvl3kbcgGvlXhJux624DVNwv+zWp0q3E3/Cn/Eg7pN7x/AXzMU8fIpPsKaHw1rYFJvgm/gWdsHOou09sLvs7+NH0n0ofT/8GHtiLxyA/fEf0n+G/5HOfjcMh8oeimHSDcYhOBYjZLd746QbiaPwG5yIo3EMRuIkDJf3ojFKdntvsnSR0s/GWaLtS3EJzscFuBpX4T28jy/wOZar4NAFPbEaVsLKWA/rYn1sgK2wJXbC9/BD/AD/gn/FXtgT43ARrsEEzMLreB/v4V35fo7PMB8fYBG+xKfSl63o0Lliyb4Y3dnd0AVd0Qs9sRJWxnpYF6tgVWyA9WWvhg2ls93e85auA/pjMAZhF3wfP8IeGI4RGI1RuBrjcQtulj0Bt0q3Sfq9uEd26/dJZ/e2YQruxyRMxnRMk3fs3lE8gqnSn8QTeAgP41k8I/0pvIyZsp/GK9LZHbt7HbMwAy/hLczBa9Lfw7uYLf1DLMQ86Z/hU3yABViMRVi9ksMa2BgbYT2sjy2xBY7C0TgJv8EJOBG/xZkYjZMxFmOks9/F4UKcg3NxJa7ABdLH42rpv8eNuAFXSb8ZN8lud7dI9wP+iD/hTtntvV3S2Tt2by/uwR1y7yAewH24H4/hL5iCqXgCj2MapuMpPIlH8Ciex3Nyz/pMvIS1Kzusg82xGTZEb/TF1thU+rfxLdnt7jvS2f4Gvivd/wChwv7Z - - - AQAAAACAAADICgAAAgIAAA==eF4txRGUIgoAAMD+/e69IAiCIAiCIAiCIAiCIAiCIAiCIAiCIAiCIAiCIAiCIAiCIAgWFoIgCBaCIFgIgiA4aEYmGPgIOeyIo4457oSTTjntjLPOOe+Ciy657Iqrrrnuhptuue2Ou+6574GHHnnsiaeeee6Fl1557Y233nnvg7/87aNPPvviH199892/fvjpl98O/Pcp6JDDjjjqmONOOOmU084465zzLrjoksuuuOqa62646Zbb7rjrnvseeOiRx5546pnnXnjpldfeeOud9z74y98++uSzL/7x1Tff/euHn3757cCfT0GHHHbEUcccd8JJp5x2xlnnnHfBRZdcdsVV11x3w0233HbHXffc98BDjzz2xFPPPPfCS6+89sZb77z3wV/+9tEnn33xj6+++e5fP/z0y28H/v8UdMhhRxx1zHEnnHTKaWecdc55F1x0yWVXXHXNdTfcdMttd9x1z30PPPTIY0889cxzL7z0ymtvvPXOex/85W8fffLZF//46pvv/vXDT7/8diAohxx2xFHHHHfCSaecdsZZ55x3wUWXXHbFVddcd8NNt9x2x1333PfAQ4889sRTzzz3wkuvvPbGW++898Ff/vbRJ5998Y+vvvnuXz/89MtvB/5+CjrksCOOOua4E0465bQzzjrnvAsuuuSyK6665robbvofDZKllw== - - - AQAAAACAAABZAQAADQAAAA==eF7j5BwFNAEANB4MIg== - - - - - diff --git a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_z_perTrue.vtu b/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_z_perTrue.vtu deleted file mode 100644 index c6b422844..000000000 --- a/python/tests/reference/Geom/get_grain_boundaries_4g12x15x20_z_perTrue.vtu +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - BAAAAACAAACAGQAA9g4AADYOAADfDQAA8QIAAA==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 - - - 0 - - - 1.7320508076 - - - - - - - AQAAAACAAAAgMgAAngkAAA==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 - - - AQAAAACAAACIDAAASwIAAA==eF4txRGUKggAAMD+Xf9eEARBEARBEARBEARBsBAEQRAsBEEQBEEQBEEQBEEQBEGwEARBEARBEARBEARBECwcNCMTDHyEHHbEUcccd8JJp5x2xlnnnHfBRZf85bIrrrrmur/dcNMtt91x1z33PfDQI4898dQzz73w0iv/eO2Nt95574OPPvnsi6+++e6Hn3757V8H/nwKOuSwI4465rgTTjrltDPOOue8Cy665C+XXXHVNdf97Yabbrntjrvuue+Bhx557ImnnnnuhZde+cdrb7z1znsffPTJZ1989c13P/z0y2//OvDPp6BDDjviqGOOO+GkU04746xzzrvgokv+ctkVV11z3d9uuOmW2+646577HnjokceeeOqZ51546ZV/vPbGW++898FHn3z2xVfffPfDT7/89q8D/34KOuSwI4465rgTTjrltDPOOue8Cy665C+XXXHVNdf97Yabbrntjrvuue+Bhx557ImnnnnuhZde+cdrb7z1znsffPTJZ1989c13P/z0y2//OhCUQw474qhjjjvhpFNOO+Osc8674KJL/nLZFVddc93fbrjpltvuuOue+x546JHHnnjqmedeeOmVf7z2xlvvvPfBR5989sVX33z3w0+//PavA38/BR1y2BFHHXPcCSedctoZZ51z3gUXXfKXy6646prr/nbDTbfcdsdd99z3wEOPPPbEU88898JLr/zjtTfeeue9Dz765LMvvvrmux9++uW3fx3471PQIYcdcdQxx51w0imnnXHWOeddcNH/Axqgw5E= - - - AQAAAACAAACRAQAADgAAAA==eF7j5BwFgwoAABPXDho= - - - - - diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20.vtr b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20.vtr new file mode 100644 index 000000000..5fddfed71 --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20.vtr @@ -0,0 +1,30 @@ + + + + + + AQAAAACAAABJAAAATwAAAA==eF4FwVEKgCAMANCO4mcRG5sTUy/QOVZKQUqB4Pl7L2vT/uBe3ob91FrMECQk0PrdCsEHuI4gzsVNspktWQJmsNFwTOKT8EpMtEw/IaUSzA== + + + + + + + + AQAAAACAAABAOAAA2AAAAA==eF7t0TEOwgAQA8EkkPD/H1PQ4pMsbXdryd1U9nH8zzs0Rf/rFfqE6lmfQv27zbf761mfQv27zbf761mfQv27zbf761mfQv27zbf761mfQv27zbf761mfQv27zbf761mfQv27zbf761mfQv27zbf761mfQv27zbf761l/ht6h+tm/Qj+heta3f+ln3+6vZ337l3727f561rd/6Wff7q9nffuXfvbt/nrWt3/pZ9/ur2d9+5d+9u3+eta3f+ln3+6vZ337l3727f561rd/6Wff7q9n/ReDyzIp + + + + + AQAAAACAAABoAAAAOQAAAA==eF5jYICAmWCw0x5Cn7Q3BoPLUP5N+/9gcB8q/tg+DQyeQeVf2p8BgzdQde+h4h+h6j/bAwAS5DYX + + + AQAAAACAAACAAAAARQAAAA==eF5jYICAWTNBYKc9hD5pb2IMApeh/Jv2EFUPoOKP7dPTQOAZVP6l/dkzIPAGqu4DVPwjVP1nqPwXqL5vUHU/7AHEUzT8 + + + AQAAAACAAACoAAAAVQAAAA==eF5jYICAWTNBYKc9hD5pb2IMApeh/Jv2EFUPoOKP7dPTQOAZVP6l/dkzIPAGqu4DVPwjVP1nqPwXqL5vUHU/oOp+QtX9hqr7A1X3D6qOwQEAqKdGHg== + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_x_False.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_x_False.vtu new file mode 100644 index 000000000..1df6c0d07 --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_x_False.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + AQAAAACAAACAJQAAxAYAAA==eF5d0V0YdNUcxuH2fOyZ2TOzZ1eIQiGEklAUQlQSQiGEQgiFUBRCKApRIYRCCIWQPhCFQgiFqCiEUBFC0cG774NnrZP/df3Wc3bXa6156/b3qv5eUvSN+ntdsdM36+/NxU7fpr9NlTt9x/6uV+VO362/G1e50/fq7xZV7vT9+rttlTv94P7uXOVOP7y/u1e504/t795V7vQT+7t/lTv91P4eUuVOP7u/R1a50y/o73FV7jjyOq3vJ1XZeZ1T5U7ndWGVO53XpVXudF5XFzud1w3FTudVDXKn82oHudN5bTDInc5rk0HudF5bDnKn89pukDud1y6D3Om89hjkTue6zyB3HHkd2vcDBtl5HTXInc7r+EHudF4nD3Kn8zp9kDud13mD3Om8LhrkTud1ebHTeV1T7HReNxY7ndd4mDud1zrD3Om8NhzmTue16TB3Oteth7njyGvXvu8wzM5rz2HudF77DnOn8zpomDud12HD3Om8jhnmTud1wjB3Oq9ThrnTeZ01zJ3O6/xh7nReFw9zp/O6stjpvK4tdjqvm4qdznU2yh1HXnfp/28zys7rPqPc6bweMsqdzuvRo9zpvJ4yyp3O67mj3Om8XjbKnc7rdaPc6byOGOVO5/W+Ue50Xh8f5U7n9YVR7nReXx/lTuf1/VHudK6/GOWOI6+/9/f3Ree11jh3Oq/lOHc6r/XHudN53X2cO53X/ce503k9fJw7ndfjxrnTeT19nDud1wvGudN5vXKcO53XG8e503m9c5w7ndcHx7nTuX5qnDuOvM7t+5cLR14/GudO53VZsdN5/anY6bz+Vex0XqM6dzqvtevc6bzuWOdO53WvOnc6rwfWudN5bV/nTuf1xDp3Oq9n1bnTeb24zp3O9dV17jjyOrrvb6mz8/pInTud12fr3Om8zqxzp/P6Tp07nddP69zpvH5T7HRefy12Oq//Fjud13SSO53XrSe503ndeZI7ndfmk9zpvB48yZ3OdadJ7jjyek7fnzzJzuulk9zpvF47yZ3O622T3Om83jvJnc7rY5Pc6bw+P8mdzutrk9zpvL43yZ3O6+eT3Om8flfsdF5/K3Y6r/8XO53XYpo7nevtprnjyOt+fb/bNDuvh01zp/N67DR3Oq+nTXOn83r+NHc6r1dMc6fzesM0dzqvd0xzp/P6wDR3Oq9PTnOn8/rSNHc6r29Oc6fz+uE0dzqvX01zp3P9Y7HjyGs4W3P/WXRe3Sx3Oq87zHKn87rnLHc6rwfMcqfzeuQsdzqvJ8xyp/N65ix3Oq8XzXKn83rVLHc6rzfPcqfzevcsdzqvD89yp/P6zCx3OtczZrnjyOsn/f124cjr18VO5/WXYqfz+k+x03lNmtzpvG7V5E7ndacmdzqveze503k9qMmdzutRTe50Xk9qcqfzenaTO53XS5rc6bxe0+RO5/rWJncceX207+9psvP6XJM7nddXm9zpvL7b5E7n9bMmdzqv3xY7ndf1xU7n9b9ip/Oaz3On87rtPHc6r7vOc6fzuu88dzqvh85zp/N6zDx3OtenznPHkdfL+/68eXZer5/nTuf19nnudF7vn+dO5/WJee50Xl+c507n9Y157nReP5jnTuf1y3nudF5/KHY6r38UO53XYJE7nddqkTud1+0XudO53mORO468HtH3rRbZeT1+kTud1zMWudN5vXCRO53XgYvc6bzetMidzutdi9zpvD60yJ3O69OL3Om8vrLInc7rW4vc6bx+vMidzuuKYqfz+nOx07n+u9hx5LXucs2tl9l5bbTMnc5rs2XudF7bLHOn89pxmTud127L3Om89lrmTue13zJ3Oq+Dl7nTeR2+zJ3O69hl7nReJy5zp/M6dZk7ndfZy9zpXC9Y5o4jr6v6e0nReV1X7HReNxc7nVfT5k7ntV6bO53Xxm3udF5btLnTeW3b5k7ntXObO53X7m3udF57t7nTee3f5k7ndUibO53XkW3udK7HtbnjyOu0vp/UZud1Tps7ndeFbe50Xpe2udN5XV3sdF43FDudV7XKnc6rXeVO57XBKnc6r01WudN5bbnKnc5ru1XudF67rHKn89pjlTud6z6r3HHkdWjfD1hl53XUKnc6r+NXudN5nbzKnc7r9FXudF7nrXKn87polTud1+XFTud1TbHTed1Y7HRe4y53Oq91utzpvDbscqfz2rTLnc516y53HHnt2vcduuy89uxyp/Pat8udzuugLnc6r8O63Om8julyp/M6ocudzuuULnc6r7O63Om8zu9yp/O6uMudzuvKYqfzurbY6bxuKnY619naubsFdMV57g== + + + AQAAAACAAABgCQAAxQEAAA==eF4txRGAGgAAAMC2BUEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQPARBEARBEARBEDw8BEEQDP5OLhj4FXLYEUcdc9wJJ51y2hlnnXPeBRddctkVV11z3Q033XLbHXfdc98DDz3y2BNPPfPcCy+98tobf3nrnfc++OiTz7746pvvfvjbP3765bc/Dvz5LeiQw4446pjjTjjplNPOOOuc8y646JLLrrjqmutuuOmW2+646577HnjokceeeOqZ51546ZXX3vjLW++898FHn3z2xVfffPfD3/7x0y+//XHg729Bhxx2xFHHHHfCSaecdsZZ55x3wUWXXHbFVddcd8NNt9x2x1333PfAQ4889sRTzzz3wkuvvPbGX956570PPvrksy+++ua7H/72j59++e2PA/9+CzrksCOOOua4E0465bQzzjrnvAsuuuSyK6665robbrrltjvuuue+Bx565LEnnnrmuRdeeuW1N/7y1jvvffDRJ5998dU33/3wt3/89MtvfxwIyiGHHXHUMcedcNIpp51x1jnnXXDRJZddcdU1191w0y233XHXPfc98NAjjz3x1DPPvfDSK6+98X86Xo+t + + + AQAAAACAAAAsAQAADQAAAA==eF7j5BwFxAIANNQKjQ== + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_x_True.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_x_True.vtu new file mode 100644 index 000000000..40c429481 --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_x_True.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + AQAAAACAAACAcAAALhUAAA==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 + + + AQAAAACAAAAgHAAA1QQAAA==eF412BG/AooBxuFzt7a1u7ZfEARBEARBEARBEARBEARBEBwIgiAIDgRBEARBEARBEARBEARBEARBEARBEARBMDjPfeX5DO8/8PW7IEMMM8IoY4wzwSRTTDPDLHPMs8AiSyyzwiprrLPBbzbZYpsddvnDHvsccMgRx5xwyhnnXHDJFdfccMsd9zzwyBPPvPDKG+988MkX3/zw649fAgwyxDAjjDLGOBNMMsU0M8wyxzwLLLLEMiusssY6G/xmky222WGXP+yxzwGHHHHMCaeccc4Fl1xxzQ233HHPA4888cwLr7zxzgeffPHND7/+9kuAQYYYZoRRxhhngkmmmGaGWeaYZ4FFllhmhVXWWGeD32yyxTY77PKHPfY54JAjjjnhlDPOueCSK6654ZY77nngkSeeeeGVN9754JMvvvnh199/CTDIEMOMMMoY40wwyRTTzDDLHPMssMgSy6ywyhrrbPCbTbbYZodd/rDHPgcccsQxJ5xyxjkXXHLFNTfccsc9DzzyxDMvvPLGOx988sU3P/wKgEGGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng99sssU2O+zyhz32OeCQI4454ZQzzrngkiuuueGWO+554JEnnnnhlTfe+eCTL7754dc/fgkwyBDDjDDKGONMMMkU08wwyxzzLLDIEsussMoa62zwm0222GaHXf6wxz4HHHLEMSeccsY5F1xyxTU33HLHPQ888sQzL7zyxjsffPLFNz/8+ucvAQYZYpgRRhljnAkmmWKaGWaZY54FFllimRVWWWOdDX6zyRbb7LDLH/bY54BDjjjmhFPOOOeCS6645oZb7rjngUeeeOaFV95454NPvvjmh1//+iXAIEMMM8IoY4wzwSRTTDPDLHPMs8AiSyyzwiprrLPBbzbZYpsddvnDHvsccMgRx5xwyhnnXHDJFdfccMsd9zzwyBPPvPDKG+988MkX3/zwr8MfYJAhhhlhlDHGmWCSKaaZYZY55llgkSWWWWGVNdbZ4DebbLHNDrv8YY99DjjkiGNOOOWMcy645IprbrjljnseeOSJZ1545Y13Pvjki29++PXvXwIMMsQwI4wyxjgTTDLFNDPMMsc8CyyyxDIrrLLGOhv8ZpMtttlhlz/ssc8BhxxxzAmnnHHOBZdccc0Nt9xxzwOPPPHMC6+88c4Hn3zxzQ+//vwlwCBDDDPCKGOMM8EkU0wzwyxzzLPAIksss8Iqa6yzwW822WKbHXb5wx77HHDIEceccMoZ51xwyRXX3HDLHfc88MgTz7zwyhvvfPDJF9/88Os/vwQYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTb4zSZbbLPDLn/YY58DDjnimBNOOeOcCy654pobbrnjngceeeKZF155450PPvnimx/+FfoDDDLEMCOMMsY4E0wyxTQzzDLHPAssssQyK6yyxjob/GaTLbbZYZc/7LHPAYccccwJp5xxzgWXXHHNDbfccc8DjzzxzAuvvPHOB5988c0Pv/77S4BBhhhmhFHGGGeCSaaYZoZZ5phngUWWWGaFVdZYZ4PfbLLFNjvs8oc99jngkCOOOeGUM8654JIrrrnhljvueeCRJ5554ZU33vngky+++eHX/34JMMgQw/w/qOTQPg== + + + AQAAAACAAACEAwAAEQAAAA==eF7j5BwFo2AUDCQAALTXH6U= + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xy_False.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xy_False.vtu new file mode 100644 index 000000000..30e48881b --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xy_False.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + AQAAAACAAACAQwAAGgwAAA==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 + + + AQAAAACAAADgEAAA/wIAAA==eF411xGDAgYAQOHb1rYgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgODgIgiAIgiAIgiAIgiAIgiAIgiAIgsF9e/L9hhf4+i3IEMOMMMoY40wwyRTTzDDLHPMssMgSy6ywyhrrbLDJFtvssMse+xxwyBG/+cMxJ5xyxjkXXHLFNTfccsc9DzzyxDMvvPLGOx988sU3P/z645cAgwwxzAijjDHOBJNMMc0Ms8wxzwKLLLHMCqussc4Gm2yxzQ677LHPAYcc8Zs/HHPCKWecc8ElV1xzwy133PPAI08888Irb7zzwSdffPPDrz9/CTDIEMOMMMoY40wwyRTTzDDLHPMssMgSy6ywyhrrbLDJFtvssMse+xxwyBG/+cMxJ5xyxjkXXHLFNTfccsc9DzzyxDMvvPLGOx988sU3P/z665cAgwwxzAijjDHOBJNMMc0Ms8wxzwKLLLHMCqussc4Gm2yxzQ677LHPAYcc8Zs/HHPCKWecc8ElV1xzwy133PPAI08888Irb7zzwSdffPPDrwAYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSI3/zhmBNOOeOcCy654pobbrnjngceeeKZF155450PPvnimx9+/f1LgEGGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY54BDjvjNH4454ZQzzrngkiuuueGWO+554JEnnnnhlTfe+eCTL7754dc/vwQYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSI3/zhmBNOOeOcCy654pobbrnjngceeeKZF155450PPvnimx9+/ftLgEGGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY54BDjvjNH4454ZQzzrngkiuuueGWO+554JEnnnnhlTfe+eCTL7754f/DH2CQIYYZYZQxxplgkimmmWGWOeZZYJEllllhlTXW2WCTLbb5H2aJClA= + + + AQAAAACAAAAcAgAADwAAAA==eF7j5BwFowA7AAASjhL9 + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xy_True.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xy_True.vtu new file mode 100644 index 000000000..1a7b5daac --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xy_True.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + AgAAAACAAACASgAAGxgAAMILAAA=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 + + + AQAAAACAAACgMgAAewgAAA==eF412qHbOYB6BuBvm222Y5ttzjl2XYIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgfOELgiAIgiAIgiAIgiAIgiAIC9/9e8r9Nzzv+wS+fhNkiGFGGGWMcSaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPfQ445IhjTjjljHMu+M0fLrnimhtuueOeBx554pkXXnnjnQ8++eKbH379wy8BBhlimBFGGWOcCSaZYpoZZpljngUWWWKZFVZZY50NNtlimx122WOfAw454pgTTjnjnAt+84dLrrjmhlvuuOeBR5545oVX3njng0+++OaHX//4S4BBhhhmhFHGGGeCSaaYZoZZ5phngUWWWGaFVdZYZ4NNtthmh1322OeAQ4445oRTzjjngt/84ZIrrrnhljvueeCRJ5554ZU33vngky+++eHXP/0SYJAhhhlhlDHGmWCSKaaZYZY55llgkSWWWWGVNdbZYJMtttlhlz32OeCQI4454ZQzzrngN3+45IprbrjljnseeOSJZ1545Y13Pvjki29++BUAgwwxzAijjDHOBJNMMc0Ms8wxzwKLLLHMCqussc4Gm2yxzQ677LHPAYccccwJp5xxzgW/+cMlV1xzwy133PPAI08888Irb7zzwSdffPPDr3/+JcAgQwwzwihjjDPBJFNMM8Msc8yzwCJLLLPCKmuss8EmW2yzwy577HPAIUccc8IpZ5xzwW/+cMkV19xwyx33PPDIE8+88Mob73zwyRff/PDrX34JMMgQw4wwyhjjTDDJFNPMMMsc8yywyBLLrLDKGutssMkW2+ywyx77HHDIEceccMoZ51zwmz9ccsU1N9xyxz0PPPLEMy+88sY7H3zyxTc//PrXXwIMMsQwI4wyxjgTTDLFNDPMMsc8CyyyxDIrrLLGOhtsssU2O+yyxz4HHHLEMSeccsY5F/zmD5dccc0Nt9xxzwOPPPHMC6+88c4Hn3zxzQ//FP4AgwwxzAijjDHOBJNMMc0Ms8wxzwKLLLHMCqussc4Gm2yxzQ677LHPAYccccwJp5xxzgW/+cMlV1xzwy133PPAI08888Irb7zzwSdffPPDr3/7JcAgQwwzwihjjDPBJFNMM8Msc8yzwCJLLLPCKmuss8EmW2yzwy577HPAIUccc8IpZ5xzwW/+cMkV19xwyx33PPDIE8+88Mob73zwyRff/PDr338JMMgQw4wwyhjjTDDJFNPMMMsc8yywyBLLrLDKGutssMkW2+ywyx77HHDIEceccMoZ51zwmz9ccsU1N9xyxz0PPPLEMy+88sY7H3zyxTc//PrLLwEGGWKYEUYZY5wJJplimhlmmWOeBRZZYpkVVlljnQ022WKbHXbZY58DDjnimBNOOeOcC37zh0uuuOaGW+6454FHnnjmhVfeeOeDT7745od/Dv0BBhlimBFGGWOcCSaZYpoZZpljngUWWWKZFVZZY50NNtlimx122WOfAw454pgTTjnjnAt+84dLrrjmhlvuuOeBR5545oVX3njng0+++OaHX//xS4BBhhhmhFHGGGeCSaaYZoZZ5phngUWWWGaFVdZYZ4NNtthmh1322OeAQ4445oRTzjjngt/84ZIrrrnhljvueeCRJ5554ZU33vngky+++eHXf/4SYJAhhhlhlDHGmWCSKaaZYZY55llgkSWWWWGVNdbZYJMtttlhlz32OeCQI4454ZQzzrngN3+45IprbrjljnseeOSJZ1545Y13Pvjki29++PVfvwQYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSIY0445YxzLvjNHy654pobbrnjngceeeKZF155450PPvnimx/+efAHGGSIYUYYZYxxJphkimlmmGWOeRZYZIllVlhljXU22GSLbXbYZY99DjjkiGNOOOWMcy74zR8uueKaG265454HHnnimRdeeeOdDz754psffv33LwEGGWKYEUYZY5wJJplimhlmmWOeBRZZYpkVVlljnQ022WKbHXbZY58DDjnimBNOOeOcC37zh0uuuOaGW+6454FHnnjmhVfeeOeDT7745odf//NLgEGGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY54BDjjjmhFPOOOeC3/zhkiuuueGWO+554JEnnnnhlTfe+eCTL7754df//hJgkCGGGWGUMcaZYJIppplhljnmWWCRJZZZYZU11tlgky222WGXPfY54JAjjjnhlDPOueA3f7jkimtuuOWOex545IlnXnjljXc++OSLb374Z9gXYJAhhhlhlDHGmWCSKaaZYZY55llgkSWWWWGVNdbZYJMtttlhlz32OeCQI4454ZQzzrngN3+45IprbrjljnseeOSJZ1545Y13Pvjki29++PXXXwIMMsQwI4wyxjgTTDLFNDPMMsc8CyyyxDIrrLLGOhtsssU2O+yyxz4HHHLEMSeccsY5F/zmD5dccc0Nt9xxzwOPPPHMC6+88c4Hn3zxzQ+//vZLgEGGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY54BDjjjmhFPOOOeC3/zhkiuuueGWO+554JEnnnnhlTfe+eCTL7754dfffwkwyBDDjDDKGONMMMkU08wwyxzzLLDIEsussMoa62ywyRbb7LDLHvsccMgRx5xwyhnnXPCbP1xyxTU33HLHPQ888sQzL7zyxjsffPLFNz/8M+gPMMgQw4wwyhjjTDDJFNPMMMsc8yywyBLLrLDKGutssMkW2+ywyx77HHDIEceccMoZ51zwmz9ccsU1N9xyxz0PPPLEMy+88sY7H3zyxTc//Pq/XwIMMsQwI4wyxjgTTDLFNDPMMsc8CyyyxDL/H5qMZAM= + + + AQAAAACAAABUBgAAFQAAAA==eF7j5BwFo2AUjIJRMAqoAwBhYjj1 + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xyz_False.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xyz_False.vtu new file mode 100644 index 000000000..caaee30e1 --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xyz_False.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + AQAAAACAAAAAWgAA2Q8AAA==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 + + + AQAAAACAAACAFgAA6gMAAA==eF411xGDAggCQOHurr3tboMgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgGBgIgiAIgiAIgoFgYb598v2GFwz8FmKYEUYZY5wJJplimhlmmWOeBRZZYpkVVlljnQ022WKbHXbZY58DDjnimBNOOeOcCy654pobbrnjngce+cVvnnjmhVfeeOeDT7745g8/DPzrlyBDDDPCKGOMM8EkU0wzwyxzzLPAIksss8Iqa6yzwSZbbLPDLnvsc8AhRxxzwilnnHPBJVdcc8Mtd9zzwCO/+M0Tz7zwyhvvfPDJF9/84YeBf/8SZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSIY0445YxzLrjkimtuuOWOex545Be/eeKZF155450PPvnimz/8MPCfX4IMMcwIo4wxzgSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzwGHHHHMCaeccc4Fl1xxzQ233HHPA4/84jdPPPPCK2+888EnX3zzhx8GgmCIYUYYZYxxJphkimlmmGWOeRZYZIllVlhljXU22GSLbXbYZY99DjjkiGNOOOWMcy645IprbrjljnseeOQXv3nimRdeeeOdDz754ps//DDwxy9BhhhmhFHGGGeCSaaYZoZZ5phngUWWWGaFVdZYZ4NNtthmh1322OeAQ4445oRTzjjngkuuuOaGW+6454FHfvGbJ5554ZU33vngky+++cMPA//9JcgQw4wwyhjjTDDJFNPMMMsc8yywyBLLrLDKGutssMkW2+ywyx77HHDIEceccMoZ51xwyRXX3HDLHfc88MgvfvPEMy+88sY7H3zyxTd/+GHgz1+CDDHMCKOMMc4Ek0wxzQyzzDHPAossscwKq6yxzgabbLHNDrvssc8BhxxxzAmnnHHOBZdccc0Nt9xxzwOP/OI3TzzzwitvvPPBJ19884cf/jP8QYYYZoRRxhhngkmmmGaGWeaYZ4FFllhmhVXWWGeDTbbYZodd9tjngEOOOOaEU84454JLrrjmhlvuuOeBR37xmyeeeeGVN9754JMvvvnDDwP/+yXIEMOMMMoY40wwyRTTzDDLHPMssMgSy6ywyhrrbLDJFtvssMse+xxwyBHHnHDKGedccMkV19xwyx33PPDIL37zxDMvvPLGOx988sU3f/hh4P+/BBlimBFGGWOcCSaZYpoZZpljngUWWWKZFVZZY50NNtlimx122WOfAw454pgTTjnjnAsuueKaG265454HHvnFb5545oVX3njng0+++OYPPwz89UuQIYYZYZQxxplgkimmmWGWOeZZ4N8BWGsr + + + AQAAAACAAADQAgAADwAAAA==eF7j5BwFo2D4AACqBRlR + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xyz_True.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xyz_True.vtu new file mode 100644 index 000000000..65ad14a70 --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xyz_True.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + 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 + + + AQAAAACAAACAQwAANAsAAA==eF4126HbAYDXHuB3m2222eb7Zpttttl1CYIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCG8QBEEQBEEQFt77d8r9N5znPCfw8zdBhhhmhFHGGGeCSaaYZoZZ5phngUWWWGaFVdZYZ4NNtthmh1322OeAQ4445oRTzjjngkuuuOaGW+6454FHnnjmhb+88sY7H3zyxTc//PLnX/wRYJAhhhlhlDHGmWCSKaaZYZY55llgkSWWWWGVNdbZYJMtttlhlz32OeCQI4454ZQzzrngkiuuueGWO+554JEnnnnhL6+88c4Hn3zxzQ+//PmXfwQYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSIY0445YxzLrjkimtuuOWOex545IlnXvjLK2+888EnX3zzwy9//tUfAQYZYpgRRhljnAkmmWKaGWaZY54FFllimRVWWWOdDTbZYpsddtljnwMOOeKYE04545wLLrnimhtuueOeBx554pkX/vLKG+988MkX3/zwy58AGGSIYUYYZYxxJphkimlmmGWOeRZYZIllVlhljXU22GSLbXbYZY99DjjkiGNOOOWMcy645IprbrjljnseeOSJZ174yytvvPPBJ19888Mvf/71HwEGGWKYEUYZY5wJJplimhlmmWOeBRZZYpkVVlljnQ022WKbHXbZY58DDjnimBNOOeOcCy654pobbrnjngceeeKZF/7yyhvvfPDJF9/88Muff/NHgEGGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY54BDjjjmhFPOOOeCS6645oZb7rjngUeeeOaFv7zyxjsffPLFNz/88uff/hFgkCGGGWGUMcaZYJIppplhljnmWWCRJZZZYZU11tlgky222WGXPfY54JAjjjnhlDPOueCSK6654ZY77nngkSeeeeEvr7zxzgeffPHND7/8x8IfYJAhhhlhlDHGmWCSKaaZYZY55llgkSWWWWGVNdbZYJMtttlhlz32OeCQI4454ZQzzrngkiuuueGWO+554JEnnnnhL6+88c4Hn3zxzQ+//Pl3fwQYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSIY0445YxzLrjkimtuuOWOex545IlnXvjLK2+888EnX3zzwy9//v0fAQYZYpgRRhljnAkmmWKaGWaZY54FFllimRVWWWOdDTbZYpsddtljnwMOOeKYE04545wLLrnimhtuueOeBx554pkX/vLKG+988MkX3/zwy5//8EeAQYYYZoRRxhhngkmmmGaGWeaYZ4FFllhmhVXWWGeDTbbYZodd9tjngEOOOOaEU84454JLrrjmhlvuuOeBR5545oW/vPLGOx988sU3P/zyH0F/gEGGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY54BDjjjmhFPOOOeCS6645oZb7rjngUeeeOaFv7zyxjsffPLFNz/88uc//hFgkCGGGWGUMcaZYJIppplhljnmWWCRJZZZYZU11tlgky222WGXPfY54JAjjjnhlDPOueCSK6654ZY77nngkSeeeeEvr7zxzgeffPHND7/8+U9/BBhkiGFGGGWMcSaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPfQ445IhjTjjljHMuuOSKa2645Y57HnjkiWde+Msrb7zzwSdffPPDL3/+8x8BBhlimBFGGWOcCSaZYpoZZpljngUWWWKZFVZZY50NNtlimx122WOfAw454pgTTjnjnAsuueKaG265454HHnnimRf+8sob73zwyRff/PDLfxz4AwwyxDAjjDLGOBNMMsU0M8wyxzwLLLLEMiusssY6G2yyxTY77LLHPgcccsQxJ5xyxjkXXHLFNTfccsc9DzzyxDMv/OWVN9754JMvvvnhlz//9EeAQYYYZoRRxhhngkmmmGaGWeaYZ4FFllhmhVXWWGeDTbbYZodd9tjngEOOOOaEU84454JLrrjmhlvuuOeBR5545oW/vPLGOx988sU3P/zy55//CDDIEMOMMMoY40wwyRTTzDDLHPMssMgSy6ywyhrrbLDJFtvssMse+xxwyBHHnHDKGedccMkV19xwyx33PPDIE8+88JdX3njng0+++OaHX/78lz8CDDLEMCOMMsY4E0wyxTQzzDLHPAssssQyK6yyxjobbLLFNjvsssc+BxxyxDEnnHLGORdccsU1N9xyxz0PPPLEMy/85ZU33vngky+++eGX/yj2BRhkiGFGGGWMcSaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPfQ445IhjTjjljHMuuOSKa2645Y57HnjkiWde+Msrb7zzwSdffPPDL3/+6x8BBhlimBFGGWOcCSaZYpoZZpljngUWWWKZFVZZY50NNtlimx122WOfAw454pgTTjnjnAsuueKaG265454HHnnimRf+8sob73zwyRff/PDLn//2R4BBhhhmhFHGGGeCSaaYZoZZ5phngUWWWGaFVdZYZ4NNtthmh1322OeAQ4445oRTzjjngkuuuOaGW+6454FHnnjmhb+88sY7H3zyxTc//PLnv/8RYJAhhhlhlDHGmWCSKaaZYZY55llgkSWWWWGVNdbZYJMtttlhlz32OeCQI4454ZQzzrngkiuuueGWO+554JEnnnnhL6+88c4Hn3zxzQ+//EehP8AgQwwzwihjjDPBJFNMM8Msc8yzwCJLLLPCKmuss8EmW2yzwy577HPAIUccc8IpZ5xzwSVXXHPDLXfc88AjTzzzwl9eeeOdDz754psffvnzP/4IMMgQw4wwyhjjTDDJFNPMMMsc8yywyBLLrLDKGutssMkW2+ywyx77HHDIEceccMoZ51xwyRXX3HDLHfc88MgTz7zwl1feeOeDT7745odf/vzPPwIMMsQwI4wyxjgTTDLFNDPMMsc8CyyyxDIrrLLGOhtsssU2O+yyxz4HHHLEMSeccsY5F1xyxTU33HLHPQ888sQzL/zllTfe+eCTL7754Zc//+uPAIMMMcwIo4wxzgSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzwGHHHHMCaeccc4Fl1xxzQ233HHPA4888cwLf3nljXc++OSLb3745T8e+QIMMsQwI4wyxjgTTDLFNDPMMsc8CyyyxDIrrLLGOhtsssU2O+yyxz4HHHLEMSeccsY5F1xyxTU33HLHPQ888sQzL/zllTfe+eCTL7754Zc///uPAIMMMcwIo4wxzgSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzwGHHHHMCaeccc4Fl1xxzQ233HHPA4888cwLf3nljXc++OSLb3745c//+SPAIEMMM8IoY4wzwSRTTDPDLHPMs8AiSyyzwiprrLPBJltss8Mue+xzwCFHHHPCKWecc8ElV1xzwy133PPAI08888JfXnnjnQ8++eKbH37583//CDDIEMOMMMoY40wwyRTTzDDLHPMssMgSy6ywyhrrbLDJFtvssMse+xxwyBHHnHDKGedccMkV19xwyx33PPDIE8+88JdX3njng0+++OaHX/7E/wgwyBDDjDDKGONMMMkU08wwyxzzLLDIEsussMoa62ywyRbb7LDLHvsccMgRx5xwyhnnXHDJFdfccMsd9zzwyBPPvPCXV95454NPvvjmh1/+/L8/AgwyxDAjjDLGOBNMMsU0M8wyxzwLLLLEMiusssY6G2yyxTY77LLHPgcccsQxJ5xyxjkXXHLFNTfccsc9/z/S4axu + + + AQAAAACAAABwCAAAGQAAAA==eF7j5BwFo2AUjIJRMApGwSigLQAAnahL8Q== + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xz_False.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xz_False.vtu new file mode 100644 index 000000000..d9f592c79 --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xz_False.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + AQAAAACAAAAAPAAAagoAAA==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 + + + AQAAAACAAAAADwAArwIAAA==eF4txRGAKgYAANC2tS0IgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIDg6CIAiCIAiCIAgOgiAIguAgCIIgCD70nrxg4CPksCOOOua4E0465bQzzjrnvAsuuuSyK6665robbrrltjvuuue+Bx565LG//O2Jp5557oWXXnntjbfeee8fH3z0yWdffPWvb7774adffjvw16egQw474qhjjjvhpFNOO+Osc8674KJLLrviqmuuu+GmW26746577nvgoUce+8vfnnjqmedeeOmV19546533/vHBR5989sVX//rmux9++uW3A39/CjrksCOOOua4E0465bQzzjrnvAsuuuSyK6665robbrrltjvuuue+Bx565LG//O2Jp5557oWXXnntjbfeee8fH3z0yWdffPWvb7774adffjvwz6egQw474qhjjjvhpFNOO+Osc8674KJLLrviqmuuu+GmW26746577nvgoUce+8vfnnjqmedeeOmV19546533/vHBR5989sVX//rmux9++uW3A0E55LAjjjrmuBNOOuW0M84657wLLrrksiuuuua6G2665bY77rrnvgceeuSxv/ztiaeeee6Fl1557Y233nnvHx989MlnX3z1r2++++GnX3478O+noEMOO+KoY4474aRTTjvjrHPOu+CiSy674qprrrvhpltuu+Oue+574KFHHvvL35546pnnXnjpldfeeOud9/7xwUeffPbFV//65rsffvrltwP/fQo65LAjjjrmuBNOOuW0M84657wLLrrksiuuuua6G2665bY77rrnvgceeuSxv/ztiaeeee6Fl1557Y233nnvHx989MlnX3z1r2++++GnX3478P+noEMOO+KoY4474aRTTjvjrHPOu+CiSy674qprrrvhpltuu+Oue+574D/D/uro + + + AQAAAACAAADgAQAADgAAAA==eF7j5BwFwxkAAN0xEOE= + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xz_True.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xz_True.vtu new file mode 100644 index 000000000..194f3847e --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_xz_True.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + AgAAAACAAAAANAAAQhgAAOcHAAA=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Vy//MXhw9fKlwYOrly8PHly9fH3w4OrlpDNPPHlwdXmyvvPg6vI0fX994OryjH3n8U8/UzGh/7ry/98NtPfXdE+Ycbhn7685nTDTcM/ur6mjdW/46+smzDLcs/fXFPz/9wrds/evaU6Ydbhn71+TmTDbcM/ujym/Y7g3/PHlCbMP9+z939AT5hju2fuf////e4rueWfvOXen31W0e893Dvfs3nOe4Z7de8473LN7z/mGe/bxdyLds3vPBYZ7du+54HDP7j0XGu7ZvefCwz2791xkuGcff8fSPe/sPRfvXGzYvee7hnt277nEcM/uPZcc7tm951LDPfv8nUsP9+zec5nhnt17Ljvcs3vPdw/37N5zueGe3XsuP9yze3e/x+med/aeK3b6XU6793zvcM/uPVca7tm958rDPbv3XGW4Z/eefg/UPbv3XHW4Z/eeqw337N7z/cM9u/dcfbhn955rDPfs4++guuedvefanWsNu/dcZ7hn957rDvfs3nO94Z7de64/3LOPv8Pqnt17bjjcs3vPjYZ7du+58XDP7j03Ge7Zveemwz37mp2bDfe8s/fconPzYfeeHxju2b3nlsM9u/fcarhn955bD/fs3tPvybpn957bDPfs3nPb4Z7de2433LN7z+2He3bvucNwz+7d/d6te97Ze+7U6Xdv7d5z5+Ge3XvuMtyze89dh3t277nbcM8+/o6ve3bvuftwz+499xju2b3nnsM9u/fca7hn9557D/fs4+8Mu+edved+nfsOu/f8yHDP7j33H+7ZvecBwz37bp0HDvfs3vOg4Z7dex483LN7z0OGe3bv+dHhnt17Hjrcs3vPw4Z7du/u95Ld887e88hOv5ts955HDffs3vPo4Z7dex4z3LN7z2OHe3bv6fea3bN7z+OGe3bvefxwz+49Pz7cs3vPE4Z7du954nDPPv5OtXve2Xue0nnysHvPU4d7du952nDP7j1PH+7ZvecZwz37+DvZ7tm951nDPbv3PHu4Z/ee5wz37N7z3OGe/cTO84Z7du9+/nDPO3vPCzsvGHbvedFwz+49Lx7u2b3nJcO9/wFH9P2deF5d0EPXIGYCBeGMM57vH4xte2Lbtm3b7Njq2OqO2bFt2xpbi7zP4qu7uefUqVXtNdP723v84eMPC99n/BHx8H3HHxkP32/8UfHw/ccfHQ8/YPwx8fADxx8b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 + + + AQAAAACAAAAALQAAkgcAAA==eF412iG/AQ6aBeC7M3bG7NodM2P3JwiCIAiCIAiCIAiCIAiCIAiCIAiCIAiCINxwgyAIgiAIgiAIgiAIgiAIgiAIE+7zf8vzGc45b+Dr94IMMcwIo4wxzgSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzwGHHHHMCaf85g9nnHPBJVdcc8Mtd9zzwCNPPPPCK2+888EnX3zzw6//+CXAIEMMM8IoY4wzwSRTTDPDLHPMs8AiSyyzwiprrLPBJltss8Mue+xzwCFHHHPCKb/5wxnnXHDJFdfccMsd9zzwyBPPvPDKG+988MkX3/zw60+/BBhkiGFGGGWMcSaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPfQ445IhjTjjlN38445wLLrnimhtuueOeBx554pkXXnnjnQ8++eKbH379+ZcAgwwxzAijjDHOBJNMMc0Ms8wxzwKLLLHMCqussc4Gm2yxzQ677LHPAYccccwJp/zmD2ecc8ElV1xzwy133PPAI08888Irb7zzwSdffPPDrwAYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSIY0445Td/OOOcCy654pobbrnjngceeeKZF155450PPvnimx9+/ecvAQYZYpgRRhljnAkmmWKaGWaZY54FFllimRVWWWOdDTbZYpsddtljnwMOOeKYE075zR/OOOeCS6645oZb7rjngUeeeOaFV95454NPvvjmh19/+SXAIEMMM8IoY4wzwSRTTDPDLHPMs8AiSyyzwiprrLPBJltss8Mue+xzwCFHHHPCKb/5wxnnXHDJFdfccMsd9zzwyBPPvPDKG+988MkX3/zw66+/BBhkiGFGGGWMcSaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPfQ445IhjTjjlN38445wLLrnimhtuueOeBx554pkXXnnjnQ8++eKbH/4R+AMMMsQwI4wyxjgTTDLFNDPMMsc8CyyyxDIrrLLGOhtsssU2O+yyxz4HHHLEMSec8ps/nHHOBZdccc0Nt9xxzwOPPPHMC6+88c4Hn3zxzQ+//vZLgEGGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY54BDjjjmhFN+84czzrngkiuuueGWO+554JEnnnnhlTfe+eCTL7754dd//RJgkCGGGWGUMcaZYJIppplhljnmWWCRJZZZYZU11tlgky222WGXPfY54JAjjjnhlN/84YxzLrjkimtuuOWOex545IlnXnjljXc++OSLb3749d+/BBhkiGFGGGWMcSaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPfQ445IhjTjjlN38445wLLrnimhtuueOeBx554pkXXnnjnQ8++eKbH/5R9AcYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSIY0445Td/OOOcCy654pobbrnjngceeeKZF155450PPvnimx9+/c8vAQYZYpgRRhljnAkmmWKaGWaZY54FFllimRVWWWOdDTbZYpsddtljnwMOOeKYE075zR/OOOeCS6645oZb7rjngUeeeOaFV95454NPvvjmh1//+0uAQYYYZoRRxhhngkmmmGaGWeaYZ4FFllhmhVXWWGeDTbbYZodd9tjngEOOOOaEU37zhzPOueCSK6654ZY77nngkSeeeeGVN9754JMvvvnh199/CTDIEMOMMMoY40wwyRTTzDDLHPMssMgSy6ywyhrrbLDJFtvssMse+xxwyBHHnHDKb/5wxjkXXHLFNTfccsc9DzzyxDMvvPLGOx988sU3P/xj4A8wyBDDjDDKGONMMMkU08wwyxzzLLDIEsussMoa62ywyRbb7LDLHvsccMgRx5xwym/+cMY5F1xyxTU33HLHPQ888sQzL7zyxjsffPLFNz/8+scvAQYZYpgRRhljnAkmmWKaGWaZY54FFllimRVWWWOdDTbZYpsddtljnwMOOeKYE075zR/OOOeCS6645oZb7rjngUeeeOaFV95454NPvvjmh1///CXAIEMMM8IoY4wzwSRTTDPDLHPMs8AiSyyzwiprrLPBJltss8Mue+xzwCFHHHPCKb/5wxnnXHDJFdfccMsd9zzwyBPPvPDKG+988MkX3/zw61+/BBhkiGFGGGWMcSaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPfQ445IhjTjjlN38445wLLrnimhtuueOeBx554pkXXnnjnQ8++eKbH/7x2BdgkCGGGWGUMcaZYJIppplhljnmWWCRJZZZYZU11tlgky222WGXPfY54JAjjjnhlN/84YxzLrjkimtuuOWOex545IlnXnjljXc++OSLb3749X+/BBhkiGFGGGWMcSaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPfQ445IhjTjjlN38445wLLrnimhtuueOeBx554pkXXnnjnQ8++eKbH379/y8BBhlimBFGGWOcCSaZYpoZZpljngUWWWKZFVZZY50NNtlimx122WOf/wZBsfn1 + + + AQAAAACAAACgBQAAFAAAAA==eF7j5BwFo2AUjIJRMBgBAIlCMqE= + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_y_False.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_y_False.vtu new file mode 100644 index 000000000..b21606698 --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_y_False.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + AQAAAACAAAAAHgAAzgQAAA==eF5d0TXY1gUUxuH3S1S6dBAUEHXgK1FhsHuwewAJBR3sHuwe7B5EUBQY7B7sHhRRRB3sHuweEHT4P/fwnrPc1/UbnzOn1dyMjsa+OLfVrt4fj2u1qw/Eea129cE4v9WuPhQXtNrVd4gLW+3qM+PxrXb1HeMJrXb1neKiVrv6znFxq119Vjyx1a4+O56Ubvf18d/yh77SN5Q/9Je+sfxhoPT/4mBRb3U22Huo9I5ob+qd0d7Uu6K9qXdHe1Pvifam3hvtTX1Y9Ae7v5X+drQ39dXR3tTfifamvibam/q7sVXU3yt/6Ch9bflDZ+nvlz90lb4udhf1D2JPUf8w9hb1j6I/2P2ursYl0d7U7472pr402pv6smhv6vdEe1O/N9qb+vJob+r3RXtTvz/am/qKaG/qK6O9qa+K/mD3k7sbT4n2pn5qtDf106K9qZ8e7U39jGhv6mdGe1M/K9qb+tnR3tTPifamfm60N/Xzor2pnx/9we679DTuGu1Nfbdob+q7R3tT3yPam/qe0d7U94r2pr53tDf1faK9qe8b7U19v2hv6vtHe1M/IPqD3Yf3No6I9qY+Mtqb+qhob+qjo72pj4n2pj422pv6uGhv6uOjvalPiPamPjHam/rm0d7Ut4j+YPeP4yflDyNK/7T8YWTpn5U/jCr98/KH0aV/EccU9S/j2KL+VRxX1L+O44v6N3FCUf82Tizq38XNi/r30R/s/sCwxgejvak/FO1N/eFob+qPRHtTfzTam/pj0d7UH4/2pv5EtDf1J6O9qT8V7U396Whv6s9Ef7D7BZs0XhjtTf2iaG/qF0d7U78k2pv6pdHe1C+L9qZ+ebQ39SuivalfGe1N/apob+pXR3tTvyb6g90P3LTxoGhv6gdHe1M/JNqb+qHR3tQPi/amfni0N/Ujor2pHxntTf2oaG/qR0d7Uz8m2pv6sdEf7L7lZo2Tor2pT472pr5VtDf1raO9qU+J9qY+Ndqb+rRob+rbRHtTnx7tTX3baG/q20V7U98++oPdf4g/lj9MKv2n8ofJpf9c/rBV6b+UP2xd+q9xSlH/LU4t6r/HaUX9j7hNUf8zTi/qf8Vti/rfcbui/k/0B7s/O7zxuWhv6s9He1N/Idqb+ovR3tRfivam/nK0N/VXor2pvxrtTf21aG/qr0d7U38j2pv6m9Ef7H7tiMbror2pXx/tTf2GaG/qN0Z7U78p2pv6zdHe1G+J9qZ+a7Q39duivanfHu1N/Y5ob+p3Rn+w+5yRjXOjvakfF+1NfV60N/X50d7UF0R7U18Y7U39+Ghv6idEe1NfFO1NfXG0N/UTo72pnxT9we4zRjX2RXtT74/2pj4Q7U19MNqb+lC0N/Udor2pz4z2pr5jtDf1naK9qe8c7U19VrQ39dnRH+y+Pv5b/tBX+obyh/7SN5Y/DJT+X/nDYOmt0Q32Hiq9I9qbeme0N/WuaG/q3dHe1Huivan3RntTHxb9we5vpb8d7U19dbQ39Xeivamvifam/m5sFfX3yh86Sl9b/tBZ+vvlD12lr4vdRf2D2FPUP4y9Rf2j6A92v2tM45Job+p3R3tTXxrtTX1ZtDf1e6K9qd8b7U19ebQ39fuivanfH+1NfUW0N/WV0d7UV0V/+B9UdvXc + + + AQAAAACAAACABwAAegEAAA==eF4txRGAGgAAAMC2BUEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBEEQBA8PQRAEQfDwEARBMOhOLhj4CDnsiKOOOe6Ek0457YyzzjnvgosuueyKq6657oabbrntjrvuue+Bhx557ImnnnnuhZdeee2Nt95574OP/vK3Tz774qtvvvvHv3746ZffDvz5FHTIYUccdcxxJ5x0ymlnnHXOeRdcdMllV1x1zXU33HTLbXfcdc99Dzz0yGNPPPXMcy+89Mprb7z1znsffPSXv33y2RdfffPdP/71w0+//Hbg76egQw474qhjjjvhpFNOO+Osc8674KJLLrviqmuuu+GmW26746577nvgoUcee+KpZ5574aVXXnvjrXfe++Cjv/ztk8+++Oqb7/7xrx9++uW3A/8+BR1y2BFHHXPcCSedctoZZ51z3gUXXXLZFVddc90NN91y2x133XPfAw898tgTTz3z3AsvvfLaG2+9894H/wc8C3I0 + + + AQAAAACAAADwAAAADAAAAA==eF7j5BxZAAD51Qhx + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_y_True.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_y_True.vtu new file mode 100644 index 000000000..377a72934 --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_y_True.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + AQAAAACAAAAAWgAABA4AAA==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 + + + AQAAAACAAACAFgAA6gMAAA==eF411xGDAggCQOHurr3tboMgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgGBgIgiAIgiAIgoFgYb598v2GFwz8FmKYEUYZY5wJJplimhlmmWOeBRZZYpkVVlljnQ022WKbHXbZY58DDjnimBNOOeOcCy654pobbrnjngce+cVvnnjmhVfeeOeDT7745g8/DPzrlyBDDDPCKGOMM8EkU0wzwyxzzLPAIksss8Iqa6yzwSZbbLPDLnvsc8AhRxxzwilnnHPBJVdcc8Mtd9zzwCO/+M0Tz7zwyhvvfPDJF9/84YeBf/8SZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSIY0445YxzLrjkimtuuOWOex545Be/eeKZF155450PPvnimz/8MPCfX4IMMcwIo4wxzgSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzwGHHHHMCaeccc4Fl1xxzQ233HHPA4/84jdPPPPCK2+888EnX3zzhx8GgmCIYUYYZYxxJphkimlmmGWOeRZYZIllVlhljXU22GSLbXbYZY99DjjkiGNOOOWMcy645IprbrjljnseeOQXv3nimRdeeeOdDz754ps//DDwxy9BhhhmhFHGGGeCSaaYZoZZ5phngUWWWGaFVdZYZ4NNtthmh1322OeAQ4445oRTzjjngkuuuOaGW+6454FHfvGbJ5554ZU33vngky+++cMPA//9JcgQw4wwyhjjTDDJFNPMMMsc8yywyBLLrLDKGutssMkW2+ywyx77HHDIEceccMoZ51xwyRXX3HDLHfc88MgvfvPEMy+88sY7H3zyxTd/+GHgz1+CDDHMCKOMMc4Ek0wxzQyzzDHPAossscwKq6yxzgabbLHNDrvssc8BhxxxzAmnnHHOBZdccc0Nt9xxzwOP/OI3TzzzwitvvPPBJ19884cf/jP8QYYYZoRRxhhngkmmmGaGWeaYZ4FFllhmhVXWWGeDTbbYZodd9tjngEOOOOaEU84454JLrrjmhlvuuOeBR37xmyeeeeGVN9754JMvvvnDDwP/+yXIEMOMMMoY40wwyRTTzDDLHPMssMgSy6ywyhrrbLDJFtvssMse+xxwyBHHnHDKGedccMkV19xwyx33PPDIL37zxDMvvPLGOx988sU3f/hh4P+/BBlimBFGGWOcCSaZYpoZZpljngUWWWKZFVZZY50NNtlimx122WOfAw454pgTTjnjnAsuueKaG265454HHvnFb5545oVX3njng0+++OYPPwz89UuQIYYZYZQxxplgkimmmWGWOeZZ4N8BWGsr + + + AQAAAACAAADQAgAADwAAAA==eF7j5BwFo2D4AACqBRlR + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_z_False.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_z_False.vtu new file mode 100644 index 000000000..d74ea4e5f --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_z_False.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + AQAAAACAAACAFgAAeQMAAA==eF5d0MUOGFQABVGWfAbupbgXKe7uUoq7u2vx4lDc3d3dijsUdyv2CyTknc3czU0ms5q55pzj/809fvHxE8LnGT8xHj7v+CXi4fONXzIePv/4peLhC4xfOh6+4Phl4uELjV82Hr7w+OXi4YuMXz4evuj4FeLhi41fMZ7Oeq48fqVwPVeJh+s5KR6u56rxcD1Xi4fruXo8XM814uF6To6H67lmPFzPteLheq4dD9d9nXg667ne+HXD9Vw/Hq7nBvFwPTeMh+u5UTxcz43j4XpuEg/Xc9N4uJ6bxcP13DwerucW8XDdt4yns55bj98qXM9t4uF6bhsP13O7eLie28fD9dwhHq7njvFwPXeKh+u5czxcz13i4XpOiYfrvms8nfXcbfzUcD13j4fruUc8XM894+F67hUP13PveLie+8TD9dw3Hq7nfvFwPfePh+t5QDxc9wPj6aznweMPCtfzkHi4nofGw/U8LB6u5+HxcD2PiIfreWQ8XM+j4uF6Hh0P1/OYeLiex8bDdT8uns56njD++HA9T4yH63lSPFzPk+Phep4SD9fz1Hi4nqfFw/U8PR6u5xnxcD3PjIfrOS0ervtZ8XTW85zxZ4freW48XM/z4uF6nh8P1/OCeLie0+Phel4YD9fzoni4nhfHw/W8JB6u56XxcN0vi6eznleMvzxczyvj4XrOiIfreVU8XM+r4+F6XhMP1/PaeLie18XD9bw+Hq7nDfFwPW+Mh+t+Uzyd9bxl/M3het4aD9fztni4nrfHw/W8Ix6u553xcD3viofreXc8XM974uF63hsP1/O+eLju98fTWc8Hxz8QrudD8XA9H46H6/lIPFzPR+Phej4WD9fz8Xi4nk/Ew/V8Mh6u51PxcD2fjofr/kw8nfV8bvyz4Xo+Hw/X84V4uJ4vxsP1fCkerufL8XA9X4mH6/lqPFzP1+Lher4eD9fzjXi47jPj6aznW+PfDNfz7Xi4nu/Ew/V8Nx6u53vxcD3fj4fr+UE8XM8P4+F6fhQP1/PjeLien8TDdf80ns56fj7+s3A9Z8XD9fwiHq7nl/FwPb+Kh+v5dTxcz2/i4Xp+Gw/X87t4uJ7fx8P1/CEervuP8XTW8+fxP4Xr+Us8XM9f4+F6/hYP1/P3eLief8TD9ZwdD9fzz3i4nn/Fw/X8Ox6u5z/xcN3/jfcfB9GTqA== + + + AQAAAACAAACgBQAALAEAAA==eF4txRFwAgAAAMC2C4IgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCAaDIAiCwWAQDPqXDwbeQg474qhjjjvhpFNOO+Osc8674KJLLrviqmuuu+GmW26746577nvgoUcee+KpZ5574aVXXnvjrXfe++CjTz774qu//O2b73746R//+s8vBz7eBR1y2BFHHXPcCSedctoZZ51z3gUXXXLZFVddc90NN91y2x133XPfAw898tgTTz3z3AsvvfLaG2+9894HH33y2Rdf/eVv33z3w0//+Nd/fjnw+S7okMOOOOqY40446ZTTzjjrnPMuuOiSy6646prrbrjpltvuuOue+x546JHHnnjqmedeeOmV19546533Pvjok8+++B8y91Uz + + + AQAAAACAAAC0AAAADAAAAA==eF7j5Bx6AAA9hAZV + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_z_True.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_z_True.vtu new file mode 100644 index 000000000..b9b4c24d5 --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_z_True.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==eF51nUFqJTkWRf+evue1m841lXdSSxBkjTzSqA0BDYKGcKAk8awb7Lio3rlXMXHnSeV9ylOh0M/X+sTjka8/v66//si8GX9+XW+b8d34/76uf29yDuP/+rr+s8kfxn9+Xf/d1D03OddmPtO4rld4krfMm3F5y+O7cXnLOYdxecv5w7i85brnJufazGca1/XNG3LkjbwZr944vhuv3phzGK/emD+MV2+se25yrs18pnFdL/ffu+b89Ufmzbi85fHduLzlnMO4vOX8YVzect1zk3Nt5jON63q9/341R/cbeTP+vHPz+G683m/MOYzX+435w3i931j33ORcm/lM4/V6N2+ZN+Pylsd34/KWcw7jdV9g/jBe9wXWPTc5l3F5I9f1UubLdUrejMtbHt+N13XKnMN4XafMH8brOmXdc5NzbeYzjev6gX1e3jJvxr/z3zbju3F5yzmHcXnL+cO4vOW65ybn2sxnGtf1etevOXq+kTfj8pbHd+P1+cacw7i85fxhvD7fWPfc5Fyb+Uzjuv4uz4PlLfNmXN7y+G5c3nLOYVzecv4wLm+57rnJuTbzmcbr9WHeMm/G5S2P78blLeccxuWNXN7If5a/J+uem5zLuLyR63otf47rlLwZl7c8vhuv65Q5h/G6Tpk/jMtbrntucq7NfKZxXS/FK/dT8mZc3vL4blzecs5hXN5y/jAub7nuucm5NvOZxnV9r99f5i3zZvw7/20zvhuvzzfmHMbr8435w7i85brnJufazGca1/W9z/42b5k34/KWx3fj8pZzDuPylvOHcXnLdc9NzrWZzzRer0/zlnkzLm95fDcubznnMC5v5PJGLm/k8pZzLuPyRs7r9fZVvZE349Ubx3fj1RtzDuPVG/OH8eqNdc9NzrWZzzTOX9Nb5s24vOXx3bi85ZzDuLzl/GFc3nLdc5NzGZc38uqtIWf137K3hvy3zfhuvHpjzmG8emP+MF69se65ybmMV2/er1V/id4yb8blLY/vxuUt5xzG5S3nD+Pyluuem5xrM59pvN5vHTmr/0Zv5M+Sy/HdeL3fmHMYr/cb84fxer+x7rnJuYzLG/mjXO/mLfNmXN7y+G5c3nLOYbzuC8wfxuu+wLrnJufazGcaf9yX+kv0lnkzLm95fDcubznnMF7XKfOH8bpOWffc5Fyb+Uzj8qb+Er1l3ox/579txnfj8pZzDuPylvOHcXnLdc9NzrWZzzQub693fXrLvBmXtzy+G5e3nHMY/1eZN/OH8fp8Y91zk3MZlzdyefu71FneMm/G5S2P78blLeccxuUt5w/j8pbrnpucazOfafxRrg/zlnkzLm95fDcubznnMC5vOX8Yl7dc99zkXJv5TOOP+3q9/xy9Zd6My1se343LW845jNd1yvxh/Ge5P1j33ORcxuWNXN5eyu9zPyVvxuUtj+/G5S3nHMblLecP4/KW656bnGszn2lc3tRforfMm/Hv/LfN+G5c3nLOYbw+35g/jMtbrntucq7NfKZxeVN/id4yb8blLY/vxuUt5xzG5S3nD+Pyluuem5xrM59p/FGuT/OWeTMub3l8Ny5vOecwLm85fxiXt1z33ORcm/lM4w9c37zBGzn7bw3eOJ79twZvzGH/rW36bw3eFq/eWJf9twZvnA/7b95/pLfMm3F5y+O7cXnLOey/NeynzB/G5S3XZf9tecvzmcbpMXsjb8arN47vxqs35rD/Rm/MH8arN9Zl/43eOJ9pXN5qf2l5y5z9t+Utj2f/bXnLOey/NXwOYT77bw2fQ1iX/bflLc+H/Tfebx05ut/Im/FnyeX4brzeb8xh/433G/OH8Xq/sS77b7zfOJ9p/FGud+Sw/8Z94R357L9xX1hc3nIO+2/cF5jP/hv3BdZl/437AufD/hvX6YEc9t+4Tg/ks//GdXpg/uy/cZ0uXtcp89l/4zplXfbfuE45H/bflrfaX1reMmf/bXnL49l/W95yDvtvy1vOZ/9tect12X9b3vJ82H/j820gR8838mZc3vL4blzecg77b3y+MX8Yr8831mX/jc83zmcal7faX1reMmf/bXnL49l/W95yDvtvy1vOZ/9tect12X9b3vJ82H/jvvCBHPbfuC98IJ/9N+4LH5g/+2/cFxaXt5zP/hv3BdZl/437AufD/hvX6YUcrVPyZlze8vhuvK5T5rD/xnXK/GH8Z7k/WJf9N65Tzmcal7faX+J+Ss7+G/dTjmf/jfspc9h/437KfPbfuJ+yLvtv3E85H/bf+Hz7hRz23/h8+4V89t/4fPuF+bP/xufb4vX5xnz23/h8Y1323/h843zYf+PnkN/IYf9teSOXtzye/Td+DmEO+2/8HMJ89t/4OYR12X9b3vJ82H/jvvCJHPbfuC98Ip/9N+4Ln5g/+2/cFxaXt5zP/hv3BdZl/437AufD/hu9rX/ff+f4uZs/8efEqzeO78arN+Ycxqs35g/j1RvrnpucazOfaVy/fr090Vvmzfjz/pnHd+PylnMO4/KW84dxect1z03OtZnPNF69NeSs/lv21pD/thnfjVdvzDmMV2/MH8arN9Y9NznXZj7TOO8/esu8GX/i1/RGLm855zAubzl/GJe3XPfc5FzGv378o/9Gb6/4+8lb5s348/6Zx3fj9X5jzmG83m/MH8br/ca65ybn2sxnGn+U6928Zd6MP0sex3fj8pZzDuN1X2D+MF73BdY9NznXZj7T+OO+Xu750lvmzfjz/pnHd+N1nTLnMF7XKfOH8bpOWffc5FzGv378o/9Gbz/KvJa3zJvx5/0zj+/G5S3nHMblLecP4/KW656bnGszn2lc3l7Lfzc+38ib8ef9M4/vxuUt5xzG5S3nD+P1+ca65ybn2sxnGpc39ZfoLfNm/Hn/zOO7cXnLOYdxecv5w7i85brnJufazGcaf5Trw7xl3ow/Sx7Hd+PylnMO4/KW84dxect1z03OtZnPNP64r9f7z9Fb5s348/6Zx3fjdZ0y5zBe1ynzh3F5y3XPTc61mc80Lm8v9+/TW+bN+PP+mcd34/KWcw7j8pbzh3F5y3XPTc5l/Fl+vbi8qb9Eb5k348/7Zx7fjctbzjmM1+cb84dxect1z03OtZnPNC5v6i/RW+bN+PP+mcd34/KWcw7j8pbzh3F5y3XPTc61mc80/ijXp3nLvBl/ljyO78blLeccxuUt5w/j8pbrnpucazOfafyBS/8Oq97I/VzJE3l1PM+/dXhjDs+/9c35tw5vi1dvrMvzbx3eOB+ef+PfZ/07/s/ya/JmXN7y+G5c3nIOz7917KfMH8blLdfl+bflLc9nGq/eGnJW/y17a8h/24zvxqs35vD8G70xfxiv3liX59/ojfOZxuVN65beMm/G5S2P78blLefw/FvH5xDmD+Pyluvy/NvyluczjXPd0lvmzfgTufRGXu835vD8G+835g/j9X5jXZ5/4/3G+Uzjj3K9I4fn37gvvCOf59+4LywubzmH59+4LzCf59+4L7Auz79xX+B8eP6N6/RAzuq/0Ru5vOXx3bi85Ryef+M6Zf4wXtcp6/L8G9cp5zONy5v6S/SWOc+/LW95PM+/LW85h+fflrecz/Nvy1uuy/Nvy1ueD8+/8fk2kKPnG3kzLm95fDcubzmH59/4fGP+MF6fb6zL8298vnE+07i8/V3qLG+Z8/zb8pbH8/zb8pZzeP5tecv5PP+2vOW6PP+2vOX58Pwb94UP5PD8G/eFD+Tz/Bv3hQ/Mn+ffuC8sLm85n+ffuC+wLs+/cV/gfHj+jev0Qo7WKXkzLm95fDcubzmH59+4Tpk/jP8s9wfr8vwb1ynnM43L20v5fe6n5M24vOXx3bi85Ryef+N+yvxhXN5yXZ5/437K+Uzj8qb+Er1lzvNvfL5xPM+/LW85h+ff+HxjPs+/8fnGujz/xucb58Pzb/wc8hs5PP+2vJHLWx7P82/LW87h+Td+DmE+z7/xcwjr8vzb8pbnw/Nv3Bc+kcPzb9wXPpHP82/cFz4xf55/476wuLzlfJ5/477Aujz/xn2B8+H5N3rT9Q5v5Dw39w5vHO//nqveFq/eFq/eFv/OH8art8Wrt8Wrt8Wrt8X1v16Lp+Ut82Zc3vL4blzecs5hXN5y/jAub7ku37+wvOX5TOOP+/rmDTmr/1Z5M169cXw3Xr0xh9+DojfmD+PVG+vy/Qv0xvlM44/7Un+p5vB84fJGLm95PM8/LW85h+dX3vE5hPk8f/COzyGsy/cvLG95Pvz//3i/deSwL8z7rSOffT3eb4vX+4057DvwfmM+/93I+411+f4F3m+cDz+38X0C78jheua+8I58/vfgvrC4vOWcw3jdF5g/jNd9gXX5/gXuC5zPNP64rxfMV94yb8blLY/vxus6ZQ7fv8B1yvxhvK5T1uX7F7hOOZ9p/HFfP/DfU94y5/sXlrc8nu9fWN5yDt+/sLzlfL5/YXnLdfn+heUtz4fvX+DzbSBn9d8qb8blLY/vxuUt5xzG5S3nD+P1+ca6fP8Cn2+czzT+uC/1l2oO37+wvJHLWx7P9y8sbzmH719Y3nI+37+wvOW6fP/C8pbnw/cvcF/4QI72BfJmXN7y+G5c3nLOYVzecv4w/rP8PVmX71/gvsD5TOOP+3otf47rlLwZl7c8vhuv65Q5h/G6Tpk/jMtbrsv3L3Cdcj7T+OO+1F+qOav/VnkzLm95fDcubzmH71/gfsr8YVzecl2+f4H7KeczjT/uS/2lmsP3L/D59gv5fP8Cn2+Ly1vO4fsX+HxjPt+/wOcb6/L9C3y+cT58/wI/h/xGDt+/sLyRy1sez/cv8HMIc/j+BX4OYT7fv8DPIazL9y8sb3k+fP8C94VP5GhfIG/G5S2P78blLeccxuUt5w/j8kYubznnMi5v5A9c+lxcvZE349Ubx3fj1Rtz+P3Tw/pvNX8Yr95Yl98/PeCN85nG9evX2xO9Zd6Mf+e/bcZ34/KWc/j90wP7KfOHcXnLdfn90+Utz2car94aclb/LXtryH/bjO/Gqzfm8Pun9Mb8Ybx6Y11+/5TeOJ9pvN5/b8hZ/Td6I3+WX3N8Ny5vOYffPz3wOYT5w7i85br8/unyluczjdf7rSNn9d/ojfx55+bx3Xi935jD75/yfmP+MF7vN9bl9095v3E+0/ijXO/IWf03eiP/zn/bjO/G5S3n8Pun3BeYP4zXfYF1+f1T7guczzT+uC/9mt4yb8a/898247vxuk6Zw++fcp0yfxiv65R1+f1TrlPOZxqXtx+Yl7xlzu+fLm95PL9/urzlHH7/dHnL+fz+6fKW6/L7p8tbng+/f8rn20DO6r/RG/l3/ttmfDcubzmH3z/l8435w3h9vrEuv3/K5xvnM43L2/8BVSHWp3hedZ1Nqh05FoRzT8/z2s2tNdXbSS9BcD3KkUZtEBQIGvSETONZN+1UoD4RcTTJyq+O4+h9zp+LbPl+f/9v/OuPbx//G+2Pv36Pf/zx3fIi/Pfh407qq/D//B7/THKa8Nfv8XeS34W/97nvO5KcmcxnCb/C+BJvnhfhT/6d1Ffh8OZzmnB48/ldOLz5viPJmcl8lvBrj8/969ib50X4k38n9VU4vPmcJhzefH4XDm++70hyZjKfJRzevu3/z948L8Kf/Dupr8Lhzec04fDm87twePN9R5IzhX+E88Ph7bl/f4o3z4vwJ/9O6qtwePM5TXh8vnF+Fw5vvu9IcmYynyUc3v78Pa9/izfPi/An/07qq3B48zlNOLz5/C4c3nzfkeTMZD5L+BXGL/HmeRH+5N9JfRUObz6nCYc3n9+Fw5vvO5KcmcxnCb9o/LnnF70xL8Kf/Dupr8KjN87h++Bv8sb5XXj0xn1HkjOT+SzhOP/cntib50U4vPn6KhzefE4T/tpHn9+Fw5vvO5KcmcxnCY/eCuXAG/MiPHrj+io8euOcJvy1jz6/C4/euO9IcmYynyUc3r7tn5u9eV6Ew5uvr8Lhzec04a999PldOLz5viPJmcl8lvB4vVXKwfXGvAj/2Lm+vgqP1xvnNOGvffT5XXi83rjvSHJmMp8l/Arjh3jzvAiHN19fhcObz2nCX2G+nN+Fx/cC9x1Jzkzms4Rfe+D9yt48L8LhzddX4fE+5Zwm/LWPPr8Lj/cp9x1Jzkzms4TDG87Zm+dF+JN/J/VVOLz5nCb8RefsjTm8+b4jyZnC4Y05vH1Sf3jzvAiHN19fhcObz2nCX/vo87vw+HzjviPJmcl8lnB4+06/P0/OWX9jb8zhzddX4fDmc5rw1z76/C4c3nzfkeTMZD5L+BXGl3jzvAiHN19fhcObz2nCX2G+nN+Fw5vvO5KcmcxnCb/2+Ny/jr15XoTDm6+vwuN9yjlN+GsffX4XDm++70hyZjKfJRzevu3/z948L8LhzddX4fDmc5rw1z76/C4c3nzfkeTMZD5LOLxhfYm9eV6EP/l3Ul+Fw5vPacJf++jzu3B4831HkjOT+Szh8Pac/1u8eV6Ew5uvr8Lhzec04a999PldOLz5viPJmcLhjfkVxi/x5nkRDm++vgqHN5/ThL/CfDm/C4c333ckOTOZzxJ+0cDnlOiNeREevXF9FR69cQ7/eVGX9beY34VHb9x3JDkzmc8SHs/P59y/wjnzIhzefH0VDm8+pwmHN5/fhcOb7zuSnCkc3phHb4Vyzvqb91Yo/07qq/DojXOa8OiN87vw6I37jiRnCo/eDoc3rC+xN8+LcHjz9VU4vPmcJhzefH4XDm++70hyZjKfJTxeb5Vyzvobe2P+EXK5vgqP1xvnNOHxeuP8Ljxeb9x3JDlTOLwxv8L4Id48L8LhzddX4fDmc5rw+F7g/C48vhe470hyZjKfJfzaA+tL7M3zIhzefH0VHu9TzmnC433K+V14vE+570hyZjKfJRze8LmEvXlehD/5d1JfhcObz2nC4c3nd+Hw5vuOJGcm81nC4Q3n7M3zIhzefH0VDm8+pwl/0bzZG/P4fOO+I8mZwuGNObx9pz5Pzll/Y2/M4c3XV+Hw5nOacHjz+V04vPm+I8mZyXyW8CuML/HmeREOb76+Coc3n9OEw5vP78LhzfcdSc5M5rOEX3t87l/H3jwvwuHN11fh8T7lnCY83qec34W/w/XBfUeSM4XDG3N4+xb+P79PmRfh8Obrq3B48zlNOLz5/C4c3nzfkeTMZD5LOLxhfYm9eV6EP/l3Ul+Fw5vPacLj843zu3B4831HkjOT+Szh8Ib1JfbmeREOb76+Coc3n9OEw5vP78LhzfcdSc5M5rOEX2H8Em+eF+Hw5uurcHjzOU04vPn8LhzefN+R5MxkPkv4RQPv2yfnrL9FXoRHb1xfhUdvnMN/f/Zfsv4W8/XzVPTGfUeSM5P5LOE4/9ye2JvnRTi8+foqHN58ThMObz6/C3/vo+87kpyZzGcJj94K5Zz1N++tUP6d1Ffh0RvnNOHRG+d34e999H1HkjOT+Szh8Ib1JfbmeREOb76+Coc3n9OEw5vP78Lf++j7jiRnJvNZwuP1VinnrL/5661S/p3UV+HxeuOcJjxeb5zfhb/30fcdSc5M5rOEX2H8EG+eF+Hw5uurcHjzOU14fC9wfhf+Dj8f9x1Jzkzms4Rfe2B9ib15XoTDm6+vwuN9yjlNeLxPOb8Lf++j7zuSnJnMZwmHN6wvsTfPi/An/07qq3B48zlNOLz5/C78vY++70hyZjKfJRzePnd/9uZ5EQ5vvr4Khzef04TDm8/vwt/76PuOJGcm81nC4Q3n7M3zIhzefH0VDm8+pwl/0e83e2P+pnP25nOmcHhjfoXxJd48L8LhzddX4fDmc5pwePP5Xfg7/HzcdyQ5M5nPEn7t8Um+4c3zIhzefH0VHu9TzmnC433K+V34ex9935HkzGQ+Szi8YX2JvXlehMObr6/C4c3nNOHw5vO78Pc++r4jyZnJfJZweHvOf4o3z4vwJ/9O6qtwePM5TXh8vnF+F/7eR993JDlTOLwxhzesL7E3z4twePP1VTi8+ZwmHN58fhf+3kffdyQ5M5nPEn6F8Uu8eV6Ew5uvr8Lhzec04fDm87vwd/j5uO9IcmYynyX8suOLvDEvwqM3rq/Co7fDo7fDo7fDo7fD3/bnQ9+R5MxkPks4/uszeDrePC/C4c3XV+Hw5nOacHjz+V04vPm+I8mZyXyW8GuPhxfKOetvkRfh0RvXV+HRG+c04dEb53fh0Rv3HUnOTOazhF97YH0p5pz1t8iLcHjz9VU4vPmcJhzefH4XDm++70hyZjKfJfza43P/fDHnrL9FXoR/7FxfX4XH641zmvB4vXF+Fx6vN+47kpyZzGcJv8L4Id48L8LhzddX4fDmc5rw+F7g/C48vhe470hypnB4Y37t8S3Ml+9T5kU4vPn6Kjzep5zThMf7lPO78Hifct+R5PC/48P36eHXHn+G38/jzXPen3+8+XreX328+RzeH3u8+Xze33i8+b4jyeH9V8cb82uPz90/5pz1t8iLcHjz9fz3Yvn5xjn89xr5+cb5/PfS+PnGfUeSw39vhp9vh197xM/Bx5vn/Odhx5uv5z/PON58Dq9HH28+n9cTjzffdyQ5vN5xvDG/wviiHP6cx++FL8rn9zS/Fw6HN5/DzxF+L3A+Xwf8XuC+I8mZwuGN+bXHJ/06ePO8CIc3X1+Fx/uUc5rweJ9yfhcOb77vSHJmMp8l/NoD60sx56y/RV6Ew5uvr8Lhzec04fDm87twePN9R5Izk/ks4dcez/37U7x5XoQ/+XdSX4XDm89pwuPzjfO7cHjzfUeSM5P5LOHXHlhfijln/S3yIhzefH0VDm8+pwmHN5/fhcOb7zuSnJnMZwm/wvgl3jwvwuHN11fh8OZzmnB48/ldOLwxhzefM4XDG/OLBp5/0RvzIjx64/oqPHrjHN5/OmX9LeZ34dEb9+X9p5O88XyW8Hh+3p9/hXPmRTi8+foqHN58Du8/nfQ+5fwuHN58X95/erz5+Szh0VuhnLP+5r0Vyr+T+io8euMc3n/K3ji/C4/euC/vP2VvPJ8lHN6wvsTePOf9p8ebr+f9p8ebz+H9p5M+h3A+7z+d9DmE+/L+0+PNz4f3n/L1VinnrL+xN+YfIZfrq/B4vXEO7z/l643zu/B4vXFf3n/K1xvPZwm/wvhBOWf9jb0xhzdfX4XDm8/h/af8XuD8Ljy+F7gv7z/l9wLPZwm/9sD6EnvznPef8n3K9bz/lO9TzuH9p3yfcj7vP+X7lPvy/lO+T3k+vP/0eMP6EnvznPefHm++nvefHm8+h/efHm8+n/efHm++L+8/Pd78fHj/KT/fOuXg+ca8CIc3X1+Fw5vP4f2n/Hzj/C48Pt+4L+8/5ecbz2cJh7fvoc/x5nkRDm++vgqHN5/D+0+PN5/fhcOb78v7T483P58l/Arji3LO+ht7Yw5vvr4Khzefw/tP+b3A+V04vPm+vP+U3ws8nyX82gPn7M3zIhzefH0VHu9TzuH9p3yfcn4X/qbrg735HN5/yvcpr8/x/lN+nzLn/af8PuV63n/K71PO4f2n/D7lfN5/yu9T7sv7T/l9yvPh/af8fPtJOWf9jb0xf/LvpL4Khzefw/tP+fnG+V04vPm+vP+Un288nyUc3rC+xN485/2n/DmE63n/KX8O4Rzef8qfQzif95/y5xDuy/tPjzc/H95/yu+FX5Rz1t/YG3N48/VVOLz5HN5/yu8Fzu/C4c335f2n/F7g+SzhFw3cx0/OWX+LvAiP3ri+Co/eOKcJj944vwuP3rjvSHL0ef+xj8xx/rk9sTfPi/An/07qq3B48zlNOLz5/C4c3nzfkeTMZD5LePRWKOesv3lvhfLvpL4Kj944pwmP3ji/C4/euO9IcmYynyU8Xn835Zz1N/bG/COcc30VDm8+pwmHN5/fhcOb7zuSnCn8Yx+Zx+utUs5Zf2NvzD92rq+vwuP1xjlNeLzeOL8Lj9cb9x1Jzkzms4RfYfwQb54X4U/+ndRX4fDmc5rw+F7g/C48vhe470hyZjKfJfza49ueL3vzvAh/8u+kvgqP9ynnNOHxPuX8Ljzep9x3JDlT+Mc+Moe3P8O8jjfPi/An/07qq3B48zlNOLz5/C4c3nzfkeTMZD5LOLx9ht83fr4xL8Kf/Dupr8Lhzec04fDm87vw+HzjviPJmcl8lnB4w/oSe/O8CH/y76S+Coc3n8Pff3q8+fwuHN58X/7+0+PNz2cJv8L4opyz/sbemD/5d1JfhcObz2nC4c3nd+Hw5vvy95/ye4Hns4Rfe3zuX8fePC/Cn/w7qa/C433KOU14vE85vwuHN9+Xv/+U71OezxIObzhnb54X4U/+ndRX4fDmc/j7T/l9yvldOLz5vvz9p/w+5fks4fCG9SX25nkR/uTfSX0VDm8+h7//lJ9vnN+Fw5vvy99/ys83ns8SDm9YX2JvnhfhT/6d1Ffh8OZz+PtP+XMI53fh8Ob78vefHm9+Pkv4FcYvyjnrb+yN+ZN/J/VVOLz5nCYc3nx+Fw5vvi9//ym/F3g+S/hFA9dj9Ma8CH/y76S+Co/eOIf//befsv4W87vw6I378r//9pO88Xz0eYbzz+2JvXlehMObr6/C4c3nNOHw5vO78N+H/1t/Y28+ZybzWcKjt/LHfwG796iTeF51nUGuHDmSRONO+vu6jXSmzpv0EQhIq1hx1QIIDEBgAIpgY6DBNKq7JjLsM9zMnBt2vnaZu54iMn6xVIgf3/9//fOPv13r73+8rr388SPwIvzLtc6kvgr/97X+keQ04V+v9V9Jfhd+bd//O+k7kpyZzLOEH/f6uH/fMefvf3hehMObr6/C4c3nNOHw5vO78Gv7y5vvO5KcmcyzhMPb6/79sTfPi/Avd66vr8Lj9cY5TXi83ji/C7+2z+uN+44kZybzLOFHWD/Fm+dFOLz5+ioc3nxOEw5vPr8Lv7a/vPm+I8mZyTxL+HGvj3te9uZ5EQ5vvr4Kj/cp5zTh8T7l/C782j7vU+47kpyZzLOEw9u3ey725nkR/s4/k/oqHN58ThMObz6/C7+2v7z5viPJmck8Szi8ve7+7M3zIhzefH0VDm8+pwmHN5/fhV/b5/cb9x1JzkzmWcLh7cfdh715XoTDm6+vwuHN5zThX8OfN+d34d/DZ+47kpyZzLOEH2H9Em+eF+Hw5uurcHjzOU04vPn8LvzaPp8L3HckOTOZZwk/7vUKvvk+ZV6Ew5uvr8Ljfco5TXi8Tzm/C7+2z/uU+44kZybzLOHw9hGuR36eMi/C4c3XV+Hw5nOacHjz+V34tX0+T7nvSHJmMs8SDm/4zN48L8Lf+WdSX4XDm89pwuP3G+d34df2+D5ibz5nCoc35vD2fs7+j3jzvAiHN19fhcObz2nC4c3nd+HX9vlzCPcdSc5M5lnCj7B+izfPi3B48/VVOLz5nCYc3nx+F35tn88F7juSnJnMs4QftOA1emNehEdvXF+FR2+c04RHb5zfhUdv3HckOTOZZwnH59ftib15XoTDm6+vwuHN5zTh1/b5POX8LhzefN+R5MxkniU8eiuUs8/fvLdC+WdSX4VHb5zThF/b4/zNe9s8euO+I8mZyTxLOLzhfIm9eV6Ew5uvr8Lhzec04df2OH9jb8zhzfcdSc5M5lnC4/VWKWefv/nrrVL+mdRX4fF645wm/Noe52/+ets8Xm/cdyQ5M5lnCT/C+inePC/C4c3XV+Hw5nOa8Gt7nL+xN+bxucB9R5Izk3mW8ONeOF9ib54X4fDm66vweJ9yThN+bY/zN/bGPN6n3HckOTOZZwmHt2/3XOzN8yL8nX8m9VU4vPmcJvxr+Mz5XTi8+b4jyZnC4Y05vL1Cf/5+Y16Ew5uvr8Lhzec04V/vuX1+Fx6/37jvSHJmMs8SDm8/wp/P9uZ5EQ5vvr4Khzef04Rf2+P8jb0xhzffdyQ5M5lnCT/C+iXePC/C4c3XV+Hw5nOa8Gt7nL+xN+bw5vuOJGcm8yzhx71e969jb54X4fDm66vweJ9yThN+bY/zN/bGHN5835HkzGSeJRzecL7E3jwvwuHN11fh8OZzmvBre5y/sTfm8Ob7jiRnJvMs4fCGf85nb54X4e/8M6mvwuHN5zTh1/Y4f2NvzOHN9x1JzkzmWcLhDZ/Zm+dFOLz5+ioc3nxOE35tj58D2BtzePN9R5IzhcMb8yOs3+LN8yIc3nx9FQ5vPqcJv7bH+Rt7Yw5vvu9IcmYyzxJ+2PWbvDEvwqM3rq/Co7fNo7fNo7fNo7fNo7fNo7fNo7fNo7fN8b9ewdP25nkRDm++vgqHN5/ThMObz+/C4c33HUnOTOZZwo97vXmhnH3+FnkRHr1xfRUevXFOEx69cX4XHr1x35HkzGSeJfy4F86XYs4+f4u8CIc3X1+Fw5vPacLhzed34fDm+44kZybzLOHHvV737y/m7PO3yIvwL3eur6/C4/XGOU14vN44vwuP1xv3HUnOTOZZwo+wfoo3z4twePP1VTi8+ZwmPD4XOL8Lj88F7juSnCkc3pgf9/oI8/J9yrwIhzdfX4XH+5RzmvB4n3J+Fx7vU+47kpyZzLOEH/f6Fv48tzfPi/B3/pnUV+Hw5nOacHjz+V04vPm+I8mZyTxL+HGv190/5uzzt8iLcHjz9VU4vPmcJhzefH4XHr/fuO9IcmYyzxJ+3AvnSzFnn79FXoTDm6+vwuHN5zTh8Obzu3B4831HkjOTeZbwI6xf4s3zIhzefH0VDm8+pwmHN5/fhX8Pv0/uO5KcKRzemB/3eoVfx/cp8yIc3nx9FR7vU85pwuN9yvldOLz5viPJmck8S/hxL5wvxZx9/hZ5EQ5vvr4Khzef04TDm8/vwuHN9x1JzkzmWcKPe+F8Kebs87fI+b+34e83ruf/XoK/3ziH/747f79xPv99Zf5+474jyeG/T8nfb5sf94rnI9ub5/z3JPjnEK7nf8/NP4dwDv97Sv45hPP53zPxzyHcdyQ5fA7OP4dsfoT1m3L4n//5ufCb8vmf3/i5sDm8+Rz++ZKfC5zPPx/wc4H7jiSHv7/4ufCb9r3e33//S96YF+HRG9dX4dEb5zTh0Rvnd+HRG/cdSc5M5lnC42c8P7c3z4twePP1VTi8+ZwmHN58fhcOb77vSHKmcHhjHr0Vytnnb95bofwzqa/CozfOacKjN87vwqM37juSnCk8etsc3nC+xN48L8LhzddX4fDmc5pwePP5XTi8+b4jyZnJPEt4vN4q5ezzN/bG/EvI5foqPF5vnNOEx+uN87vweL1x35HkTOHwxvwI66d487wIhzdfX4XDm89pwuNzgfO78Phc4L4jyZnJPEv4cS+cL7E3z4twePP1VTi8+ZwmPN6nnN+Fx/uU+44kZybzLOHwhvMl9uZ5Ef7OP5P6KhzefE4TDm8+vwuHN993JDkzmWcJh7fX3Z+9eV6Ew5uvr8Lhzec04V/D3JzfhcfvN+47kpwpHN6Yw9uP0Gd787wIhzdfX4XDm89pwuHN53fh8Ob7jiRnJvMs4UdYv8Sb50U4vPn6KhzefE4TDm8+vwuHN993JDkzmWcJP+71un8de/O8CIc3X1+Fw5vPacLjfcr5Xfj3cH1w35HkTOHwxhzePsL/z89T5kU4vPn6KhzefE4TDm8+vwuHN993JDkzmWcJhzecL7E3z4vwd/6Z1Ffh8OZzmvD4/cb5XTi8+b4jyZnJPEs4vOF8ib15XoTDm6+vwuHN5zTh8Obzu3B4831HkjOTeZbwI6zf4s3zIhzefH0VDm8+pwmHN5/fhcOb7zuSnJnMs4QftN738f+RN+ZFePTG9VV49MY5TXj0xvldePTGfUeSM5N5lnB8ft2e2JvnRfg7/0zqq3B48zlNOLz5/C4c3nzfkeTMZJ4lPHorlLPP37y3QvlnUl+FR2+c04RHb5zfhUdv3HckOTOZZwmP199JOfv8jb0x/xI+c30VDm8+pwmHN5/fhcOb7zuSnCn82h7nb+ztFX5/fL0xL8K/3Lm+vgqP1xvnNOHxeuP8Ljxeb9x3JDkzmWcJP8L6Kd48L8Lf+WdSX4XDm89pwuNzgfO78Phc4L4jyZnJPEv4ca+Pe1725nkR/s4/k/oqPN6nnNOEx/uU87vweJ9y35HkTOHX9jh/Y2/fwlzbm+dF+Dv/TOqrcHjzOU04vPn8LhzefN+R5MxkniUc3l7hz42/35gX4e/8M6mvwuHN5zTh8Obzu/D4/cZ9R5Izk3mWcHjD+RJ787wIf+efSX0VDm8+pwmHN5/fhcOb7zuSnJnMs4QfYf0Sb54X4e/8M6mvwuHN5zTh8Obzu3B4831HkjOTeZbw416v+9exN8+L8Hf+mdRX4fE+5ZwmPN6nnN+Fw5vvO5KcmcyzhMPbx/3/szfPi/B3/pnUV+Hw5nOacHjz+V04vPm+I8mZwr+Ez5vDG86X2JvnRfg7/0zqq3B48zlNePx+4/wuHN5835HkzGSeJRzecL7E3jwvwt/5Z1JfhcObz2nC4c3nd+Hw5vuOJGcm8yzhR1i/xZvnRfg7/0zqq3B48zlNOLz5/C4c3nzfkeTMZJ4l/KD1vh7/Rd6YF+Hv/DOpr8KjN85pwqM3zu/CozfuO5KcmcyzhOPz6/bE3jwvwuHN11fh8OZzmnB48/ld+LU9zt/Ym8+ZyTxLePRWKGefv3lvhfLPpL4Kj944pwmP3ji/C7+2x/mb98Y5M5lnCYe3j/v3zd48L8LhzddX4fDmc/j9C/+in0M4vwu/tsf5G3vzOfz+he2NebzeKuXs8zd/vVXKP5P6Kjxeb5zThMfrjfO78Gt7nL/5641zZjLPEn6E9ZNy9vkbe2MOb76+Coc3n9OEx+cC53fh1/Y4f2NvPmcm8yzhx70+7nnZm+dFOLz5+io83qecw+9f4PuU87vwa3ucv7E3n8PvX+D7dHN4w/kSe/O8CH/nn0l9FQ5vPoffv7C9+fwu/Noe52/szefw+xe2N+bw9rr7szfPi3B48/VVOLz5nCYc3nx+F35tj/M39uZzZjLPEg5vP+4+7M3zIhzefH0VDm8+h9+/sL35/C78e/jMffn9C9ubn2cJP8L6RTn7/I29MYc3X1+Fw5vPacLhzed34df2OH9jbz5nJvMs4ce9XsE336fMi3B48/VVeLxPOacJj/cp53fh1/Y4f2NvPmcm8yzh8PYRrkd+njIvwuHN11fh8OZz+P0L/Dzl/C782h7nb+zN5/D7F/h5ujm8vT//k3L2+Rt7Y/7OP5P6KhzefA6/f4G/3zi/C7+2x/kbe/M5/P4F/n7bHN5wvsTePC/C4c3XV+Hw5nP4/Qv8cwjnd+HX9jh/Y28+h9+/wD+HbH6E9Zty9vkbe2MOb76+Coc3n9OEw5vP78Kv7XH+xt58zkzmWcIPWm+vf5I35kV49Mb1VXj0xjlNePTG+V149MZ9+f0Lf5I3nmcJx+fX7Ym9eV6Ew5uvr8Lhzec04df2OH9jb8zhzffl9y9sb36eJTx6K5Szz9+8t0L5Z1JfhUdvnNOEX9vj/M172zx64778/gX2xvMs4fCG8yX25nkRDm++vgqHN5/D71/4k34O4fwuHN58X37/wvbm51nC4/VWKWefv/nrrVL+mdRX4fF645wm/Noe52/+ets8Xm/cl9+/wNcbz7OEH2H9pJx9/sbemMObr6/C4c3nNOHX9jh/Y2/M43OB+/L7F/i5wPMs4ce9cL7E3jwvwuHN11fh8T7lHH7/At+nnN+Fx/uU+/L7F/g+5XmWcHj7ds/F3jwvwt/5Z1JfhcObz+H3L2xvPr8Lhzffl9+/sL35eZZweHuF/vz9xrwIhzdfX4XDm89pwr/ec/v8Ljx+v3Fffv8Cf7/xPEs4vP0Ifz7bm+dFOLz5+ioc3nwOv39he/P5XTi8+b78/oXtzc+zhB9h/aKcff7G3pjDm6+vwuHN5zTh1/Y4f2NvzOHN9+X3L/BzgedZwo97ve5fB/4fALzdHHhedZhBahwxEEX7Ts5+fBrnTOOb+AgCZzUrrRIQGAQGWSgEr5KMNfojqv4vbcp+Ln7Jj1ZPT5/PX+vl9DxrP31/+lp/T/PXcyL+MNcl6M/E/831M8gpxGd5egvyK/HXud6DuS3I6cF+BvFjrW/r7zbn5aR5Ig5vuj8ThzedU4jPcvWm8ytxeNNzW5DTg/0M4vD2Y875Td40T8Rv+ZegPxOHN51TiM9y9abzK3F403NbkNOD/Qzi8Hb7/Q950zwRhzfdn4nDm84pxGe5etP5lTi86bktyOnE4c3zw6xP8qZ5Ig5vuj8ThzedU4jPcvWm8ytxeNNzW5DTg/0M4odej9ab54m49eb7M3HrbXPrbXPrbXPrbXPrbXPrbXPrbXPrbXP88Gw93b1pnojDm+7PxOFN5xTi8KbzK3F403NbkNOD/Qziqy5vyeXAm+eJuPXm+zNx683nFOLWm8+vxK03P7cFOT3YzyC+6noOubgcPId4nojDm+7PxOFN5xTi8KbzK3F403NbkNOD/Qziq67rLbscXG+eJ+IPK1f3Z+L2evM5hbi93nx+JW6vNz+3BTk92M8gfpj1i7xpnojDm+7PxOFN5xTi9nPB51fi9nPBz21BTicOb56vus5pcTk4p54n4vCm+zNxe059TiFuz6nPr8TtOfVzW5DTg/0M4quu5943l4PnXs8T8Vv+JejPxOFN5xTi8KbzK3F403NbkNOD/Qziq677W3U5uL95nojDm+7PxO39zecU4vCm8ytxe3/zc1uQ04P9DOKrru+n7y4H3089T8ThTfdn4vCmcwpxeNP5lTi86bktyOnBfgbxw6wP8qZ5Ig5vuj8ThzedU4jDm+fw5vmr+T/93BbkdOLw5vmq65x2l7Pfv1meiMOb7s/E7Tn1OYW4Pac+vxKHNz23BTk92M8gvur9/ZvN2e/fLE/E4U33Z+LwpnMKcXjT+ZU4vOm5LcjpwX4G8VXv799szn7/Znkifsu/BP2ZuL2/+ZxC3N7ffH4lDm96bgtyerCfQXzV+/s3m7Pfv1meiMOb7s/E4U3nFOLwpvMrcXjTc1uQ04P9DOKHWZ/kTfNEHN50fyYObzqnEIc3z+HNc3jzHN50TicOb57/B/t+InM= + + + 0 + + + 2.7730849248 + + + + + + + AQAAAACAAACAQwAAMgoAAA==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 + + + AQAAAACAAADgEAAA/wIAAA==eF411xGDAgYAQOHb1rYgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgODgIgiAIgiAIgiAIgiAIgiAIgiAIgsF9e/L9hhf4+i3IEMOMMMoY40wwyRTTzDDLHPMssMgSy6ywyhrrbLDJFtvssMse+xxwyBG/+cMxJ5xyxjkXXHLFNTfccsc9DzzyxDMvvPLGOx988sU3P/z645cAgwwxzAijjDHOBJNMMc0Ms8wxzwKLLLHMCqussc4Gm2yxzQ677LHPAYcc8Zs/HHPCKWecc8ElV1xzwy133PPAI08888Irb7zzwSdffPPDrz9/CTDIEMOMMMoY40wwyRTTzDDLHPMssMgSy6ywyhrrbLDJFtvssMse+xxwyBG/+cMxJ5xyxjkXXHLFNTfccsc9DzzyxDMvvPLGOx988sU3P/z665cAgwwxzAijjDHOBJNMMc0Ms8wxzwKLLLHMCqussc4Gm2yxzQ677LHPAYcc8Zs/HHPCKWecc8ElV1xzwy133PPAI08888Irb7zzwSdffPPDrwAYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSI3/zhmBNOOeOcCy654pobbrnjngceeeKZF155450PPvnimx9+/f1LgEGGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY54BDjvjNH4454ZQzzrngkiuuueGWO+554JEnnnnhlTfe+eCTL7754dc/vwQYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSI3/zhmBNOOeOcCy654pobbrnjngceeeKZF155450PPvnimx9+/ftLgEGGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY54BDjvjNH4454ZQzzrngkiuuueGWO+554JEnnnnhlTfe+eCTL7754f/DH2CQIYYZYZQxxplgkimmmWGWOeZZYJEllllhlTXW2WCTLbb5H2aJClA= + + + AQAAAACAAAAcAgAADwAAAA==eF7j5BwFowA7AAASjhL9 + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_zy_False.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_zy_False.vtu new file mode 100644 index 000000000..c351b263e --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_zy_False.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + AQAAAACAAACANAAAZggAAA==eF5d2UXU7XUdhfFzE5W6pAOpS6rcolW6VbpVulW6VaRLpVulW6VbpVulW6VbpWwHlIOzP4N3n8mz1rP26PmOzv+3+WD4W3TUkJPCLQYjyU8OtxyMJD8l3GowkvzUcOvBSPLTwm0GI8kvFm47GEl+8XC7wUjyS4TbD0aSXzLcYTCS/FLhjoOR5JcOdxqMJL9MuHO87h+EH9YdJpX/qO4wufzHdYcp5T8Jpxb5wegh9J5WflSoN/KjQ72RHxPqjfzYUG/kx4V6Iz8+1Bv56UJ30P3B+IdCvZF/ONQb+UdCvZF/NNQb+cfCQZF/vO4wqvwTdYfR5Z+sO4wp/1Q4tsg/HY4r8s+E44v8s6E76H7WmCHPDvVG/pxQb+TPDfVG/rxQb+TPD/VG/oJQb+QvDPVG/qJQb+QvDvVG/pJQb+QvDfVG/rLQHXTfZeyQu4Z6I79bqDfyu4d6I79HqDfye4Z6I79XqDfye4d6I79PqDfy+4Z6I79fqDfy+4d6I39A6A66LztuyOVCvZFfPtQb+RVCvZFfMdQb+ZVCvZFfOdQb+VVCvZFfNdQb+dVCvZFfPdQb+TVCvZFfM3QH3acfP+QMod7IzxjqjfxMod7IzxzqjfyEUG/kZwn1Rn7WUG/kZwv1Rn72UG/k5wj1Rn7OUG/kPxu6g+7Phc/XHWYo/0LdYcbyL9YdZir/Ut1h5vIvhxOK/CvhLEX+1XDWIv9aOFuRfz2cvci/Ec5R5N8M5yzyb4XuoPvl0w15Rag38leGeiN/Vag38leHeiN/Tag38teGeiN/Xag38teHeiN/Q6g38jeGeiN/U6g38jeH7qD7gZ8a8oeh3sgfFOqN/MGh3sgfEuqN/KGh3sgfFuqN/OGh3sgfEeqN/JGh3sgfFeqN/NGh3sgfE7qD7mt9esi1Q72RXyfUG/l1Q72RXy/UG/n1Q72R3yDUG/kNQ72R3yjUG/mNQ72R3yTUG/lNQ72R3yx0B90/95kh5wr1Rn7uUG/k5wn1Rn7eUG/k5wv1Rn5iqDfy84d6I79AqDfyC4Z6I79QqDfyC4d6I79I6A66vx2+U3eYq/y7dYe5y79Xd5in/Pt1h3nL/z2cr8j/I5xY5P8Zzl/k/xUuUOT/HS5Y5P8TLlTk/xsuXOT/F7qD7rdMP+Stod7I3xbqjfztod7I3xHqjfydod7I3xXqjfzdod7I3xPqjfy9od7I3xfqjfz9od7IPxC6g+7HzjDkcaHeyB8f6o38CaHeyJ8Y6o38SaHeyJ8c6o38KaHeyJ8a6o38aaHeyJ8e6o38GaHeyJ8ZuoPum8845Bah3shvGeqN/Fah3shvHeqN/Dah3shvG+qN/Hah3shvH+qN/A6h3sjvGOqN/E6h3sjvHLqD7ovONOSkUG/kJ4d6Iz8l1Bv5qaHeyE8L9UZ+sVBv5BcP9UZ+iVBv5JcM9UZ+qVBv5JcO9UZ+mdAddP8g/LDuMKn8R3WHyeU/rjtMKf9J3WFq+cHMQ+g9rfyoUG/kR4d6Iz8m1Bv5saHeyI8L9UZ+fKg38tOF7qD7g/EPhXoj/3CoN/KPhHoj/2ioN/KPhYMi/3jdYVT5J+oOo8s/WXcYU/6pcGyRfzocV+SfCccX+WdDd9D9rAlDnh3qjfw5od7Inxvqjfx5od7Inx/qjfwFod7IXxjqjfxFod7IXxzqjfwlod7IXxrqjfxloTvMl/9hE8Mp4eTy84dTa8cvEE6rHb9guFjt+IXCxWvHLxwuUTt+kXDJ2vGfD5eqHf+FcOna8V8Ml6kdv2j4pdrxk8Iv105nPZcNv1Jez+Vqx+u5fO14PVeoHa/nirXj9VypdryeK9eO13OV2vF6rlo7Xs/VasfruXrteN3XqJ3Oen41XLO8nl+rHa/n12vH67lW7Xg9164dr+c6teP1XLd2vJ7r1Y7Xc/3a8XpuUDtezw1rx+u+Ue101nOTcOPyem5aO17PzWrH6/mN2vF6frN2vJ7fqh2v5+a14/Xcona8nlvWjtdzq9rxem5dO173bWqns57bhduW13P72vF67lA7Xs8da8fruVPteD13rh2v57drx+v5ndrxen63dryeu9SO13PX2vG671Y7nfXcI9y9vJ571o7Xc6/a8XruXTtez31qx+u5b+14PferHa/n/rXj9Tygdrye36sdr+f3a8fr/oPa6ayn7/0HltfzoNrxeh5cO17PQ2rH63lo7Xg9D6sdr+fhteP1PKJ2vJ5H1o7X86ja8XoeXTte92Nq1+9ePw5/VF7Pn9Su372OrV2/ex1Xu373Or52/e51Qu363evE2vW710m163evk2vX716n1K7fvU6tXb97nVY7nfU8Izy9vJ5n1o7X86e14/X8We14PX9eO17Ps2rH63l27Xg9z6kdr+e5teP1PK92vJ7n147X/YLa6aznReGF5fW8uHa8npfUjtfz0trxel5WO17PX9SO1/OXteP1/FXteD0vrx2v5xW14/W8sna87lfVTmc9rwmvLq/ntbXj9byudrye19eO1/OG2vF63lg7Xs+basfreXPteD1/XTtez9/Ujtfzt7Xjdb+ldjrreVt4a3k9b68dr+cdteP1vLN2vJ531Y7X8+7a8XreUztez3trx+t5X+14Pe+vHa/nA7Xjdf9d7XTW8w/h78vr+WDteD0fqh2v58O14/V8pHa8no/Wjtfzsdrxej5eO17PJ2rH6/lk7Xg9n6odr/vTtdNZz2fDZ8rr+cfa8Xr+qXa8nn+uHa/nc7Xj9Xy+dryeL9SO1/PF2vF6vlQ7Xs+Xa8fr+UrteN1frZ3Oer4evlZezzdqx+v5Zu14Pd+qHa/nX2rH6/nX2vF6/q12vJ5v147X853a8Xq+Wztez/dqx+v+fu3+D1/6iZI= + + + AQAAAACAAAAgDQAAYgIAAA==eF4txRGUKggAAMD+Xf9eEARBEARBEARBEARBEARBEARBEARBEARBEARBsLAQBEGwsBAEQRAEQRAEQRAEQRAcNCMTDHyEHHbEUcccd8JJp5x2xlnnnHfBRZdcdsVV11x3w0233HbHXffc98BDjzz2xFPP/OVvz73w0iv/+Ndrb7z1znsffPTJZ1989c13P/z0y28H/nwKOuSwI4465rgTTjrltDPOOue8Cy665LIrrrrmuhtuuuW2O+66574HHnrksSeeeuYvf3vuhZde+ce/XnvjrXfe++CjTz774qtvvvvhp19+O/DPp6BDDjviqGOOO+GkU04746xzzrvgoksuu+Kqa6674aZbbrvjrnvue+ChRx574qln/vK351546ZV//Ou1N956570PPvrksy+++ua7H3765bcD/34KOuSwI4465rgTTjrltDPOOue8Cy665LIrrrrmuhtuuuW2O+66574HHnrksSeeeuYvf3vuhZde+ce/XnvjrXfe++CjTz774qtvvvvhp19+OxCUQw474qhjjjvhpFNOO+Osc8674KJLLrviqmuuu+GmW26746577nvgoUcee+KpZ/7yt+deeOmVf/zrtTfeeue9Dz765LMvvvrmux9++uW3A38/BR1y2BFHHXPcCSedctoZZ51z3gUXXXLZFVddc90NN91y2x133XPfAw898tgTTz3zl78998JLr/zjX6+98dY7733w0SefffHVN9/98NMvvx3471PQIYcdcdQxx51w0imnnXHWOeddcNEll11x1TXX3XDTLbfdcdc99z3w0COPPfH/nNDMBw== + + + AQAAAACAAACkAQAADgAAAA==eF7j5BwFgx0AACaCDsU= + + + + + diff --git a/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_zy_True.vtu b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_zy_True.vtu new file mode 100644 index 000000000..2a9053c41 --- /dev/null +++ b/python/tests/reference/Geom/get_grain_boundaries_8g12x15x20_zy_True.vtu @@ -0,0 +1,35 @@ + + + + + + + + + + + BAAAAACAAACAGQAAtQ8AAF8PAACIDwAATAMAAA==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 + + + 0 + + + 2.7730849248 + + + + + + + AgAAAACAAACAHQAAnRQAAJUEAAA=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e0N94mhvqE8S7Q31SaO9oT5ZtDfUJ492yD/5DN4gvxz6baH/93yu6f+/LHqn53MqA/9a7/T8uPdgynqn58cxB1PVOz0/rjOYut7p+eP0g2nqnZ4/7jeYtt7p+eM4g+nqnZ7/XTuYvt7p+d8pgxnqne6322asd+7snjPHmaq75yz1TnfPWeud7p6z1TvdPWevd7p7zlHvdPecs97p7jlXvdPdc+56p7vnPPVOd895653u7vPVO3d2zwXi/NXdc8F6p7vnQvVOd8+F653unqPrnT5HHFPvdPccW+9091yk3unuuWi9091zsXqnu+fi9U539yXqnTu757i4ZHX3XKre6e65dL3T3XOZeqe757L1TnfP5eqd7p7L1zvdPVeod7p7rljvdPdcqd7p7rlyvdPdfZV6587uuVpctbp7rl7vdPdco97p7rlmvdPdc616p7vn2vVOd8916p3unuvWO90916t3unuuX+9099yg3umrxA3rnTu758Zxo+ruuUm9091z03qnu+dm9U53z83rne6eW9Q73T23rHe6e25V73T33Lre6e65Tb3T3XPbeqe7+3b1zp3d09+34O9l0N3T37fgne6e/r4F73T39PcteKe75y71TnfPXeud7p671TvdPXevd7p77lHvdPfcs97p7rlXvdPdfe96587uuW/cp7p77lfvdPfcv97p7nlAvdN3iQfWO909D6p3unseXO909zyk3unueWi9093zsHqnu+fh9U539yPqnTu751HxyOrueXS9093zmHqnu+ex9U53z+Pqne6ex9c73T1PqHe6e55Y73T3PKne6e55cr3T3fOUeqe7+6n1zp3d8/R4WnX3PKPe6e55Zr3T3fOseqe759n1TnfPc+qd7p7n1jvdPc+rd7p7nl/vdPe8oN7pp8QL653u7hfVO3d2z0vixdXd89J6p7vnZfVOd8/L653unlfUO909r6x3unteVe9097y63unueU29093z2nqnu+d19U539+vrnTu7543xhurueVO9093z5nqnu+ct9U53z1vrne6et9U73T1vr3e6e95R73T3vLPe6e55V73T3fPueqe7+z31zp3d8754b3X3vL/e6e75QL3Tb4kP1jvdPR+qd7p7PlzvdPd8pN7p7vlovdPd87F6p7vn4/VOd88n6p3u7k/WO3d2z6fjU9Xd80v1TnfPL9c73T2/Uu9093ym3unu+Wy9093zq/VOd8+v1TvdPb9e73T3fK7e6e75jXqnu/s36507u+cL8fnq7vlivdPd86V6p7vny/VOd89X6p3unq/WO909X6t3unu+Xu9093yj3unPxTfrne6eb9U73d3frnf93Z+xcUx139lYpN71d38WrXf93Z/F6l1/92fxetff/Vmi3vV3f5asd/3dn3H1rr/7s1S96+/+LF3v+rs/y9S7/u7PsvXOnd1z+bhcdfdcod7p7rlivdPdc6V6p7vnyvVOd89V6p3unqvWO909V6t3unuuXu9091yj3unuuWa90919rXrnzu65Tly7unuuW+9091yv3unuuX69091zg3qnu+eG9U53z43qne6eG9c73T03qXe6e25a73T33Kze6e6+eb1zZ/fcMm5R3T23qne6e25d73T33Kbe6e65bb3T3XO7eqe75/b1TnfPHeqd7p471jvdPXeqd7p77lzvdHffpd65s3vuFnet7p671zvdPfeod7p77lnvdPfcq97p7rl3vdPdc596p7vnvvVOd8/96p3unvvXO909D6h3ursfWO/c2T0PjgdVd89D6p3unofWO909D6t3unseXu909zyi3unueWS9093zqHqnu+fR9U53z2Pqne6ex9Y73d2Pq3fu7J6+d3V8dfc8sd7p7nlSvdPd8+R6p7vnKfVOd89T653unqfVO909T693unueUe909zyz3unueVa909397HrX3z08N55T3T3Pq3f93cPz611/9/CCetffPbyw3vV3Dy+qd/3dw4vrXX/38JJ61989vLTe9XcPL6t3/d3Dy+tdf/fwinrnzu55VbyyunteXe9097ym3unueW29093zunqnu+f19U53zxvqne6eN9Y73T1vqne6e95c73T3vKXe6e5+a71zZ/e8Pd5W3T3vqHe6e95Z73T3vKve6e55d73T3fOeeqe75731TnfP++qd7p731zvdPR+od7p7PljvdHd/qN65s3s+Eh+u7p6P1jvdPR+rd7p7Pl7v/gdsZOEeeF5d0MMSYAcYROGJJ859o9i2bdu2bXNs27Zte2KrKvm/xe3edNWpszqdOnb4b53rB9T3D96lfmB4eNf6QeHh3eoHh4d3rx8SHt6jfmh4eM/6YeHhveqHh9e7vk/9yPoRwfvWjwoP71c/OjxczzHh4XqODQ/Xc1x4uJ7jw8P1nBAerufE8HA9J4WH6zk5PFz3KeHprOe0+qnB9ZweHq7njPBwPWeGh+s5Kzxcz9nh4XrOCQ/Xc254uJ7zwsP1nB8erueC8HDdF4ans56L6xcF13NJeLieS8PD9VwWHq7n8vBwPVeEh+u5Mjxcz1Xh4XquDg/Xc014uJ5rw8N1XxeeznpuqF8fXM+N4eF6bgoP13NzeLieW8LD9dwaHq7ntvBwPbeHh+u5Izxcz53h4XruCg/XfXd4hx/2/x9Rf2r9KcGPrD8tPPyo+tPDw4+uPyM8/Jj6M8PDj60/Kzz8uPqzw8OPrz8nPPyE+nPDw0+sPy88/KT688PDT66/IDyd9byo/sLgel4cHq7nJeHhel4aHq7nZeHhel4eHq7nFeHhel4ZHq7nVeHhel4dHq7nNeHhul8bns56Xl9/XXA9bwgP1/PG8HA9bwoP1/Pm8HA9bwkP1/PW8HA9bwsP1/P28HA97wgP1/PO8HDd7wpPZz3vqb87uJ73hofreV94uJ73h4fr+UB4uJ4Phofr+VB4uJ4Ph4fr+Uh4uJ6Phofr+Vh4uO6Ph6eznk/WPxFcz6fCw/V8Ojxcz2fCw/V8Njxcz+fCw/V8PjxczxfCw/V8MTxcz5fCw/V8OTxc91fC01nP1+pfDa7n6+Hher4RHq7nm+Hher4VHq7n2+Hher4THq7nu+Hher4XHq7n++Hhen4QHq77h+HprOfH9R8F1/OT8HA9Pw0P1/Oz8HA9Pw8P1/OL8HA9vwwP1/Or8HA9vw4P1/Ob8HA9O4WH6945PJ317FrfJbie3cLD9eweHq5nj/BwPXuGh+vZKzxcz97h4Xr2CQ/Xs294uJ79wsP17B8ervuA8HTWc1D9wOB6Dg4P13NIeLieQ8PD9RwWHq7n8PBwPUeEh+s5Mjxcz1Hh4XqODg/Xc0x4uO5jw9NZz/H144LrOSE8XM+J4eF6TgoP13NyeLieU8LD9ZwaHq7ntPBwPaeHh+s5Izxcz5nh4brPCk9nPefUzw6u59zwcD3nhYfrOT88XM8F4eF6LgwP13NReLiei8PD9VwSHq7n0vBwPZeFh+u+PDyd9VxZvyK4nqvCw/VcHR6u55rwcD3XhofruS48XM/14eF6bggP13NjeLiem8LD9dwcHq77lvB01nNb/dbgem4PD9dzR3i4njvDw/XcFR6u5+7wcD2/DQ/X87vwcD2/Dw/X84fwcD1/DA/X/afwdNbzl/qfg+v5a3i4nr+Fh+v5e3i4nn+Eh+v5Z3i4nn+Fh+v5d3i4nv+Eh+vZoWl7uJ57NG0P133Ppu3prOfexfdq2lzPfZq2h+u5b9P2cD33a9oermfHpu3heu7ftD1czwOatofreWDT9nA9D2raHq7nwU3bw/U8JDxc90PD+xfOspqp + + + AQAAAACAAABgJwAApgYAAA==eF412ZGjAo6hBeC7t/Ze29pe24IgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCIIgCC5cCIIgCIIgCAb3+x35/oZzTuDrN0GGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY54BDjjjmhFPOOOeCS6645oZb7rjngUeeeOY3f3jhlTfe+eCTL7754deffgkwyBDDjDDKGONMMMkU08wwyxzzLLDIEsussMoa62ywyRbb7LDLHvsccMgRx5xwyhnnXHDJFdfccMsd9zzwyBPP/OYPL7zyxjsffPLFNz/8+p9fAgwyxDAjjDLGOBNMMsU0M8wyxzwLLLLEMiusssY6G2yyxTY77LLHPgcccsQxJ5xyxjkXXHLFNTfccsc9DzzyxDO/+cMLr7zxzgeffPHND7/+/EuAQYYYZoRRxhhngkmmmGaGWeaYZ4FFllhmhVXWWGeDTbbYZodd9tjngEOOOOaEU84454JLrrjmhlvuuOeBR5545jd/eOGVN9754JMvvvnhVwAMMsQwI4wyxjgTTDLFNDPMMsc8CyyyxDIrrLLGOhtsssU2O+yyxz4HHHLEMSeccsY5F1xyxTU33HLHPQ888sQzv/nDC6+88c4Hn3zxzQ+//vJLgEGGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY54BDjjjmhFPOOOeCS6645oZb7rjngUeeeOY3f3jhlTfe+eCTL7754df//hJgkCGGGWGUMcaZYJIppplhljnmWWCRJZZZYZU11tlgky222WGXPfY54JAjjjnhlDPOueCSK6654ZY77nngkSee+c0fXnjljXc++OSLb3749X+/BBhkiGFGGGWMcSaYZIppZphljnkWWGSJZVZYZY11Nthki2122GWPfQ445IhjTjjljHMuuOSKa2645Y57HnjkiWd+84cXXnnjnQ8++eKbH/5R+AMMMsQwI4wyxjgTTDLFNDPMMsc8CyyyxDIrrLLGOhtsssU2O+yyxz4HHHLEMSeccsY5F1xyxTU33HLHPQ888sQzv/nDC6+88c4Hn3zxzQ+//vpLgEGGGGaEUcYYZ4JJpphmhlnmmGeBRZZYZoVV1lhng0222GaHXfbY54BDjjjmhFPOOOeCS6645oZb7rjngUeeeOY3f3jhlTfe+eCTL7754dfffgkwyBDDjDDKGONMMMkU08wwyxzzLLDIEsussMoa62ywyRbb7LDLHvsccMgRx5xwyhnnXHDJFdfccMsd9zzwyBPP/OYPL7zyxjsffPLFNz/8+vsvAQYZYpgRRhljnAkmmWKaGWaZY54FFllimRVWWWOdDTbZYpsddtljnwMOOeKYE04545wLLrnimhtuueOeBx554pnf/OGFV95454NPvvjmh38M/QEGGWKYEUYZY5wJJplimhlmmWOeBRZZYpkVVlljnQ022WKbHXbZY58DDjnimBNOOeOcCy654pobbrnjngceeeKZ3/zhhVfeeOeDT7745odf//glwCBDDDPCKGOMM8EkU0wzwyxzzLPAIksss8Iqa6yzwSZbbLPDLnvsc8AhRxxzwilnnHPBJVdcc8Mtd9zzwCNPPPObP7zwyhvvfPDJF9/88OufvwQYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSIY0445YxzLrjkimtuuOWOex545IlnfvOHF155450PPvnimx9+/f8vAQYZYpgRRhljnAkmmWKaGWaZY54FFllimRVWWWOdDTbZYpsddtljnwMOOeKYE04545wLLrnimhtuueOeBx554pnf/OGFV95454NPvvjmh38c/AEGGWKYEUYZY5wJJplimhlmmWOeBRZZYpkVVlljnQ022WKbHXbZY58DDjnimBNOOeOcCy654pobbrnjngceeeKZ3/zhhVfeeOeDT7745odf//olwCBDDDPCKGOMM8EkU0wzwyxzzLPAIksss8Iqa6yzwSZbbLPDLnvsc8AhRxxzwilnnHPBJVdcc8Mtd9zzwCNPPPObP7zwyhvvfPDJF9/88OvfvwQYZIhhRhhljHEmmGSKaWaYZY55FlhkiWVWWGWNdTbYZIttdthlj30OOOSIY0445YxzLrjkimtuuOWOex545IlnfvOHF155450PPvnimx9+/eeXAIMMMcwIo4wxzgSTTDHNDLPMMc8CiyyxzAqrrLHOBptssc0Ou+yxzwGHHHHMCaeccc4Fl1xxzf8ClcWULg== + + + AQAAAACAAADsBAAAEgAAAA==eF7j5BwFo2AUjILhDwAkfSxN + + + + + diff --git a/python/tests/reference/Orientation/cF_Bain.txt b/python/tests/reference/Orientation/cF_Bain.txt new file mode 100644 index 000000000..876cf3888 --- /dev/null +++ b/python/tests/reference/Orientation/cF_Bain.txt @@ -0,0 +1,5 @@ +1 header +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +180.0 45.00000000000001 180.0 1 1 +270.0 45.00000000000001 90.0 1 2 +315.0 0.0 0.0 1 3 diff --git a/python/tests/reference/Orientation/cF_GT.txt b/python/tests/reference/Orientation/cF_GT.txt new file mode 100644 index 000000000..cefae431a --- /dev/null +++ b/python/tests/reference/Orientation/cF_GT.txt @@ -0,0 +1,26 @@ +1 header +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +146.75362934444064 9.976439066337804 256.395594327347 1 1 +356.59977719102034 43.39784965440254 12.173896584899929 1 2 +75.92521636876346 43.82007387041961 277.8843642946069 1 3 +326.75362934444064 9.976439066337806 76.39559432734703 1 4 +176.59977719102034 43.397849654402556 192.17389658489986 1 5 +255.92521636876344 43.82007387041961 97.88436429460687 1 6 +213.24637065555936 9.976439066337804 103.604405672653 1 7 +3.400222808979685 43.39784965440255 347.8261034151001 1 8 +284.0747836312365 43.82007387041961 82.11563570539313 1 9 +33.24637065555936 9.976439066337804 283.60440567265294 1 10 +183.40022280897963 43.397849654402556 167.8261034151001 1 11 +104.07478363123654 43.82007387041961 262.1156357053931 1 12 +273.4002228089796 43.397849654402556 77.82610341510008 1 13 +123.24637065555939 9.976439066337806 193.60440567265297 1 14 +194.07478363123653 43.82007387041961 172.11563570539317 1 15 +93.40022280897969 43.39784965440255 257.8261034151001 1 16 +303.24637065555936 9.976439066337804 13.604405672652977 1 17 +14.074783631236542 43.82007387041961 352.1156357053931 1 18 +86.59977719102032 43.39784965440254 282.17389658489986 1 19 +236.75362934444058 9.976439066337804 166.39559432734703 1 20 +165.92521636876344 43.82007387041961 187.88436429460683 1 21 +266.59977719102034 43.39784965440254 102.17389658489992 1 22 +56.75362934444064 9.976439066337804 346.395594327347 1 23 +345.9252163687635 43.82007387041961 7.884364294606862 1 24 diff --git a/python/tests/reference/Orientation/cF_GT_prime.txt b/python/tests/reference/Orientation/cF_GT_prime.txt new file mode 100644 index 000000000..44a9b25ec --- /dev/null +++ b/python/tests/reference/Orientation/cF_GT_prime.txt @@ -0,0 +1,26 @@ +1 header +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +166.39559432734697 9.976439066337804 236.75362934444058 1 1 +352.1156357053931 43.82007387041961 14.074783631236542 1 2 +77.82610341510008 43.397849654402556 273.4002228089796 1 3 +346.395594327347 9.976439066337804 56.75362934444064 1 4 +172.11563570539317 43.82007387041961 194.07478363123653 1 5 +257.8261034151001 43.39784965440255 93.40022280897969 1 6 +193.604405672653 9.976439066337804 123.24637065555939 1 7 +7.884364294606862 43.82007387041961 345.9252163687635 1 8 +282.17389658489986 43.39784965440254 86.59977719102032 1 9 +13.604405672652977 9.976439066337804 303.24637065555936 1 10 +187.88436429460683 43.82007387041961 165.92521636876344 1 11 +102.17389658489992 43.39784965440254 266.59977719102034 1 12 +277.8843642946069 43.82007387041961 75.92521636876346 1 13 +103.604405672653 9.976439066337804 213.24637065555936 1 14 +192.17389658489986 43.397849654402556 176.59977719102034 1 15 +97.88436429460687 43.82007387041961 255.92521636876344 1 16 +283.60440567265294 9.976439066337804 33.24637065555936 1 17 +12.173896584899929 43.39784965440254 356.59977719102034 1 18 +82.11563570539313 43.82007387041961 284.0747836312365 1 19 +256.395594327347 9.976439066337804 146.75362934444064 1 20 +167.8261034151001 43.397849654402556 183.40022280897963 1 21 +262.1156357053931 43.82007387041961 104.07478363123654 1 22 +76.39559432734703 9.976439066337806 326.75362934444064 1 23 +347.8261034151001 43.39784965440255 3.400222808979685 1 24 diff --git a/python/tests/reference/Orientation/cF_KS.txt b/python/tests/reference/Orientation/cF_KS.txt new file mode 100644 index 000000000..93fdcf07e --- /dev/null +++ b/python/tests/reference/Orientation/cF_KS.txt @@ -0,0 +1,26 @@ +1 header +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +114.20342833932975 10.52877936550932 204.20342833932972 1 1 +94.3573968784815 80.40593177313954 311.22729452432543 1 2 +175.6426031215185 80.40593177313954 48.77270547567447 1 3 +155.79657166067025 10.52877936550932 155.79657166067025 1 4 +99.62136089109411 85.70366403943004 318.04510841542015 1 5 +170.37863910890587 85.70366403943002 41.954891584579855 1 6 +85.64260312151852 80.40593177313954 48.77270547567448 1 7 +65.79657166067024 10.52877936550932 155.79657166067025 1 8 +9.621360891094124 85.70366403943004 318.04510841542015 1 9 +80.37863910890587 85.70366403943004 41.95489158457987 1 10 +24.203428339329758 10.52877936550932 204.20342833932975 1 11 +4.357396878481486 80.40593177313954 311.2272945243255 1 12 +204.20342833932972 10.52877936550932 204.20342833932972 1 13 +184.35739687848147 80.40593177313954 311.2272945243255 1 14 +265.64260312151845 80.40593177313953 48.77270547567449 1 15 +245.79657166067025 10.528779365509317 155.79657166067025 1 16 +189.62136089109413 85.70366403943004 318.04510841542015 1 17 +260.3786391089059 85.70366403943002 41.954891584579855 1 18 +170.37863910890587 94.29633596056996 138.04510841542015 1 19 +99.62136089109411 94.29633596056998 221.95489158457983 1 20 +155.79657166067025 169.4712206344907 24.203428339329754 1 21 +175.64260312151848 99.59406822686046 131.22729452432552 1 22 +94.35739687848151 99.59406822686046 228.77270547567446 1 23 +114.20342833932975 169.4712206344907 335.7965716606702 1 24 diff --git a/python/tests/reference/Orientation/cF_NW.txt b/python/tests/reference/Orientation/cF_NW.txt new file mode 100644 index 000000000..cc9c95a05 --- /dev/null +++ b/python/tests/reference/Orientation/cF_NW.txt @@ -0,0 +1,14 @@ +1 header +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +96.91733794010702 83.13253115922213 314.5844440567886 1 1 +173.082662059893 83.13253115922211 45.41555594321143 1 2 +135.0 9.735610317245317 180.0 1 3 +263.082662059893 83.13253115922213 45.415555943211444 1 4 +186.91733794010702 83.13253115922211 314.5844440567886 1 5 +224.99999999999997 9.735610317245317 180.0 1 6 +83.082662059893 83.13253115922213 45.415555943211444 1 7 +6.917337940106983 83.13253115922211 314.5844440567886 1 8 +45.0 9.73561031724532 180.0 1 9 +13.638707279476469 45.81931182053557 80.40196970123216 1 10 +256.36129272052347 45.81931182053556 279.59803029876775 1 11 +315.0 99.73561031724536 0.0 1 12 diff --git a/python/tests/reference/Orientation/cF_Pitsch.txt b/python/tests/reference/Orientation/cF_Pitsch.txt new file mode 100644 index 000000000..aa0c32365 --- /dev/null +++ b/python/tests/reference/Orientation/cF_Pitsch.txt @@ -0,0 +1,14 @@ +1 header +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +135.41555594321144 83.13253115922213 173.082662059893 1 1 +260.26438968275465 90.0 135.0 1 2 +260.40196970123213 45.81931182053557 13.638707279476478 1 3 +314.5844440567886 83.13253115922213 96.91733794010702 1 4 +350.40196970123213 45.81931182053557 283.6387072794765 1 5 +170.26438968275465 90.0 224.99999999999997 1 6 +315.4155559432114 83.13253115922213 353.08266205989304 1 7 +99.73561031724536 90.0 225.0 1 8 +279.59803029876787 45.819311820535574 166.36129272052352 1 9 +134.58444405678856 83.13253115922213 276.91733794010696 1 10 +9.598030298767851 45.819311820535574 76.36129272052355 1 11 +9.735610317245369 90.0 315.0 1 12 diff --git a/python/tests/reference/Orientation/cF_slip.txt b/python/tests/reference/Orientation/cF_slip.txt new file mode 100644 index 000000000..18aa03f24 --- /dev/null +++ b/python/tests/reference/Orientation/cF_slip.txt @@ -0,0 +1,19 @@ +3x3:1_Schmid 3x3:2_Schmid 3x3:3_Schmid 3x3:4_Schmid 3x3:5_Schmid 3x3:6_Schmid 3x3:7_Schmid 3x3:8_Schmid 3x3:9_Schmid +0.0 0.0 0.0 0.408248290463863 0.408248290463863 0.40824829046386296 -0.4082482904638631 -0.4082482904638631 -0.408248290463863 +-0.408248290463863 -0.408248290463863 -0.40824829046386296 2.4997998108697446e-17 2.4997998108697446e-17 2.499799810869744e-17 0.4082482904638631 0.4082482904638631 0.408248290463863 +0.408248290463863 0.408248290463863 0.40824829046386296 -0.4082482904638631 -0.4082482904638631 -0.408248290463863 0.0 0.0 0.0 +4.999599621739488e-17 4.9995996217394874e-17 -4.999599621739488e-17 0.408248290463863 0.40824829046386296 -0.408248290463863 0.408248290463863 0.40824829046386296 -0.408248290463863 +-0.408248290463863 -0.40824829046386296 0.408248290463863 -2.499799810869744e-17 -2.4997998108697437e-17 2.499799810869744e-17 -0.408248290463863 -0.40824829046386296 0.408248290463863 +0.408248290463863 0.40824829046386296 -0.408248290463863 -0.4082482904638631 -0.408248290463863 0.4082482904638631 0.0 0.0 0.0 +0.0 0.0 0.0 -0.408248290463863 0.408248290463863 0.408248290463863 0.4082482904638631 -0.4082482904638631 -0.4082482904638631 +-0.408248290463863 0.408248290463863 0.408248290463863 -2.499799810869744e-17 2.499799810869744e-17 2.499799810869744e-17 -0.408248290463863 0.408248290463863 0.408248290463863 +0.408248290463863 -0.408248290463863 -0.408248290463863 0.408248290463863 -0.408248290463863 -0.408248290463863 0.0 0.0 0.0 +-4.999599621739488e-17 4.999599621739488e-17 -4.9995996217394874e-17 -0.408248290463863 0.408248290463863 -0.40824829046386296 -0.408248290463863 0.408248290463863 -0.40824829046386296 +-0.408248290463863 0.408248290463863 -0.40824829046386296 2.4997998108697446e-17 -2.4997998108697446e-17 2.499799810869744e-17 0.4082482904638631 -0.4082482904638631 0.408248290463863 +0.408248290463863 -0.408248290463863 0.40824829046386296 0.408248290463863 -0.408248290463863 0.40824829046386296 0.0 0.0 0.0 +0.4999999999999999 -0.4999999999999999 0.0 0.4999999999999999 -0.4999999999999999 0.0 0.0 0.0 0.0 +0.5 0.4999999999999999 -6.123233995736766e-17 -0.5000000000000001 -0.5 6.123233995736767e-17 0.0 0.0 0.0 +0.4999999999999999 -3.0616169978683824e-17 -0.4999999999999999 3.0616169978683824e-17 -1.874699728327322e-33 -3.0616169978683824e-17 0.4999999999999999 -3.0616169978683824e-17 -0.4999999999999999 +0.5 -3.061616997868383e-17 0.4999999999999999 -3.0616169978683836e-17 1.8746997283273227e-33 -3.061616997868383e-17 -0.5000000000000001 3.0616169978683836e-17 -0.5 +0.0 6.123233995736765e-17 -6.123233995736765e-17 0.0 0.4999999999999999 -0.4999999999999999 0.0 0.4999999999999999 -0.4999999999999999 +0.0 0.0 0.0 0.0 0.5 0.4999999999999999 0.0 -0.5000000000000001 -0.5 diff --git a/python/tests/reference/Orientation/cF_twin.txt b/python/tests/reference/Orientation/cF_twin.txt new file mode 100644 index 000000000..8b0961823 --- /dev/null +++ b/python/tests/reference/Orientation/cF_twin.txt @@ -0,0 +1,13 @@ +3x3:1_Schmid 3x3:2_Schmid 3x3:3_Schmid 3x3:4_Schmid 3x3:5_Schmid 3x3:6_Schmid 3x3:7_Schmid 3x3:8_Schmid 3x3:9_Schmid +-0.4714045207910318 -0.4714045207910318 -0.47140452079103173 0.2357022603955159 0.2357022603955159 0.23570226039551587 0.2357022603955159 0.2357022603955159 0.23570226039551587 +0.2357022603955159 0.2357022603955159 0.23570226039551587 -0.4714045207910318 -0.4714045207910318 -0.47140452079103173 0.2357022603955159 0.2357022603955159 0.23570226039551587 +0.23570226039551587 0.23570226039551587 0.23570226039551584 0.23570226039551587 0.23570226039551587 0.23570226039551584 -0.4714045207910318 -0.4714045207910318 -0.47140452079103173 +-0.4714045207910318 -0.47140452079103173 0.4714045207910318 0.23570226039551587 0.23570226039551584 -0.23570226039551587 -0.2357022603955159 -0.23570226039551587 0.2357022603955159 +0.23570226039551584 0.23570226039551578 -0.23570226039551584 -0.4714045207910318 -0.47140452079103173 0.4714045207910318 -0.2357022603955159 -0.23570226039551587 0.2357022603955159 +0.2357022603955159 0.23570226039551587 -0.2357022603955159 0.2357022603955159 0.23570226039551587 -0.2357022603955159 0.4714045207910317 0.47140452079103157 -0.4714045207910317 +-0.4714045207910318 0.4714045207910318 0.4714045207910318 -0.2357022603955159 0.2357022603955159 0.2357022603955159 -0.2357022603955159 0.2357022603955159 0.2357022603955159 +0.23570226039551595 -0.23570226039551595 -0.23570226039551595 0.4714045207910318 -0.4714045207910318 -0.4714045207910318 -0.2357022603955159 0.2357022603955159 0.2357022603955159 +0.2357022603955159 -0.2357022603955159 -0.2357022603955159 -0.23570226039551587 0.23570226039551587 0.23570226039551587 0.4714045207910318 -0.4714045207910318 -0.4714045207910318 +-0.4714045207910318 0.4714045207910318 -0.47140452079103173 -0.23570226039551587 0.23570226039551587 -0.23570226039551584 0.2357022603955159 -0.2357022603955159 0.23570226039551587 +0.23570226039551595 -0.23570226039551595 0.2357022603955159 0.4714045207910318 -0.4714045207910318 0.47140452079103173 0.2357022603955159 -0.2357022603955159 0.23570226039551587 +0.23570226039551584 -0.23570226039551584 0.23570226039551578 -0.23570226039551595 0.23570226039551595 -0.2357022603955159 -0.4714045207910318 0.4714045207910318 -0.47140452079103173 diff --git a/python/tests/reference/Orientation/cI_Bain.txt b/python/tests/reference/Orientation/cI_Bain.txt new file mode 100644 index 000000000..e0bc4f6c7 --- /dev/null +++ b/python/tests/reference/Orientation/cI_Bain.txt @@ -0,0 +1,5 @@ +1 header +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +0.0 45.00000000000001 0.0 1 1 +90.0 45.00000000000001 270.0 1 2 +45.00000000000001 0.0 0.0 1 3 diff --git a/python/tests/reference/Orientation/cI_GT.txt b/python/tests/reference/Orientation/cI_GT.txt new file mode 100644 index 000000000..5d5102698 --- /dev/null +++ b/python/tests/reference/Orientation/cI_GT.txt @@ -0,0 +1,26 @@ +1 header +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +283.60440567265294 9.976439066337804 33.24637065555936 1 1 +167.8261034151001 43.397849654402556 183.40022280897963 1 2 +262.1156357053931 43.82007387041961 104.07478363123654 1 3 +103.604405672653 9.976439066337804 213.24637065555936 1 4 +347.8261034151001 43.39784965440255 3.400222808979685 1 5 +82.11563570539313 43.82007387041961 284.0747836312365 1 6 +76.39559432734703 9.976439066337806 326.75362934444064 1 7 +192.17389658489986 43.397849654402556 176.59977719102034 1 8 +97.88436429460687 43.82007387041961 255.92521636876344 1 9 +256.395594327347 9.976439066337804 146.75362934444064 1 10 +12.173896584899929 43.39784965440254 356.59977719102034 1 11 +277.8843642946069 43.82007387041961 75.92521636876346 1 12 +102.17389658489992 43.39784965440254 266.59977719102034 1 13 +346.395594327347 9.976439066337804 56.75362934444064 1 14 +7.884364294606862 43.82007387041961 345.9252163687635 1 15 +282.17389658489986 43.39784965440254 86.59977719102032 1 16 +166.39559432734703 9.976439066337804 236.75362934444058 1 17 +187.88436429460683 43.82007387041961 165.92521636876344 1 18 +257.8261034151001 43.39784965440255 93.40022280897969 1 19 +13.604405672652977 9.976439066337804 303.24637065555936 1 20 +352.1156357053931 43.82007387041961 14.074783631236542 1 21 +77.82610341510008 43.397849654402556 273.4002228089796 1 22 +193.60440567265297 9.976439066337806 123.24637065555939 1 23 +172.11563570539317 43.82007387041961 194.07478363123653 1 24 diff --git a/python/tests/reference/Orientation/cI_GT_prime.txt b/python/tests/reference/Orientation/cI_GT_prime.txt new file mode 100644 index 000000000..e398d3139 --- /dev/null +++ b/python/tests/reference/Orientation/cI_GT_prime.txt @@ -0,0 +1,26 @@ +1 header +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +303.24637065555936 9.976439066337804 13.604405672652977 1 1 +165.92521636876344 43.82007387041961 187.88436429460683 1 2 +266.59977719102034 43.39784965440254 102.17389658489992 1 3 +123.24637065555939 9.976439066337804 193.604405672653 1 4 +345.9252163687635 43.82007387041961 7.884364294606862 1 5 +86.59977719102032 43.39784965440254 282.17389658489986 1 6 +56.75362934444064 9.976439066337804 346.395594327347 1 7 +194.07478363123653 43.82007387041961 172.11563570539317 1 8 +93.40022280897969 43.39784965440255 257.8261034151001 1 9 +236.75362934444058 9.976439066337804 166.39559432734697 1 10 +14.074783631236542 43.82007387041961 352.1156357053931 1 11 +273.4002228089796 43.397849654402556 77.82610341510008 1 12 +104.07478363123654 43.82007387041961 262.1156357053931 1 13 +326.75362934444064 9.976439066337806 76.39559432734703 1 14 +3.400222808979685 43.39784965440255 347.8261034151001 1 15 +284.0747836312365 43.82007387041961 82.11563570539313 1 16 +146.75362934444064 9.976439066337804 256.395594327347 1 17 +183.40022280897963 43.397849654402556 167.8261034151001 1 18 +255.92521636876344 43.82007387041961 97.88436429460687 1 19 +33.24637065555936 9.976439066337804 283.60440567265294 1 20 +356.59977719102034 43.39784965440254 12.173896584899929 1 21 +75.92521636876346 43.82007387041961 277.8843642946069 1 22 +213.24637065555936 9.976439066337804 103.604405672653 1 23 +176.59977719102034 43.397849654402556 192.17389658489986 1 24 diff --git a/python/tests/reference/Orientation/cI_KS.txt b/python/tests/reference/Orientation/cI_KS.txt new file mode 100644 index 000000000..34b393358 --- /dev/null +++ b/python/tests/reference/Orientation/cI_KS.txt @@ -0,0 +1,26 @@ +1 header +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +335.7965716606702 10.528779365509317 65.79657166067024 1 1 +228.77270547567446 80.40593177313953 85.64260312151849 1 2 +131.22729452432552 80.40593177313954 4.357396878481506 1 3 +24.20342833932977 10.52877936550932 24.20342833932976 1 4 +221.95489158457983 85.70366403943002 80.37863910890589 1 5 +138.04510841542015 85.70366403943004 9.621360891094124 1 6 +131.22729452432552 80.40593177313953 94.35739687848151 1 7 +24.203428339329765 10.52877936550932 114.20342833932976 1 8 +221.95489158457983 85.70366403943004 170.37863910890587 1 9 +138.04510841542015 85.70366403943004 99.62136089109411 1 10 +335.7965716606702 10.52877936550932 155.79657166067025 1 11 +228.77270547567448 80.40593177313954 175.6426031215185 1 12 +335.7965716606702 10.52877936550932 335.7965716606702 1 13 +228.77270547567448 80.40593177313954 355.6426031215185 1 14 +131.2272945243255 80.40593177313954 274.35739687848144 1 15 +24.203428339329747 10.52877936550932 294.2034283393298 1 16 +221.95489158457985 85.70366403943004 350.3786391089059 1 17 +138.04510841542015 85.70366403943004 279.6213608910941 1 18 +41.95489158457986 94.29633596056998 9.621360891094133 1 19 +318.04510841542015 94.29633596056996 80.37863910890589 1 20 +155.79657166067025 169.4712206344907 24.203428339329754 1 21 +48.77270547567448 99.59406822686046 4.357396878481504 1 22 +311.2272945243255 99.59406822686046 85.64260312151852 1 23 +204.20342833932975 169.4712206344907 65.79657166067024 1 24 diff --git a/python/tests/reference/Orientation/cI_NW.txt b/python/tests/reference/Orientation/cI_NW.txt new file mode 100644 index 000000000..754c69bba --- /dev/null +++ b/python/tests/reference/Orientation/cI_NW.txt @@ -0,0 +1,14 @@ +1 header +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +225.41555594321144 83.13253115922213 83.08266205989301 1 1 +134.58444405678856 83.13253115922211 6.917337940107012 1 2 +4.702125169424418e-15 9.735610317245317 45.0 1 3 +134.58444405678856 83.13253115922213 276.91733794010696 1 4 +225.4155559432114 83.13253115922213 353.082662059893 1 5 +0.0 9.735610317245317 315.0 1 6 +134.58444405678858 83.13253115922213 96.91733794010702 1 7 +225.41555594321142 83.13253115922213 173.082662059893 1 8 +0.0 9.735610317245317 135.0 1 9 +99.59803029876785 45.81931182053557 166.36129272052355 1 10 +260.40196970123213 45.81931182053556 283.6387072794765 1 11 +180.0 99.73561031724535 225.0 1 12 diff --git a/python/tests/reference/Orientation/cI_Pitsch.txt b/python/tests/reference/Orientation/cI_Pitsch.txt new file mode 100644 index 000000000..ef28bbb4d --- /dev/null +++ b/python/tests/reference/Orientation/cI_Pitsch.txt @@ -0,0 +1,14 @@ +1 header +1_Eulers 2_Eulers 3_Eulers 1_pos 2_pos +6.9173379401070045 83.13253115922213 44.58444405678856 1 1 +45.0 89.99999999999999 279.7356103172453 1 2 +166.36129272052352 45.819311820535574 279.59803029876787 1 3 +83.08266205989301 83.13253115922213 225.41555594321144 1 4 +256.3612927205235 45.819311820535574 189.59803029876787 1 5 +315.0 90.0 9.735610317245369 1 6 +186.917337940107 83.13253115922213 224.58444405678856 1 7 +315.0 90.0 80.26438968275463 1 8 +13.638707279476478 45.81931182053557 260.40196970123213 1 9 +263.082662059893 83.13253115922213 45.415555943211444 1 10 +103.63870727947646 45.819311820535574 170.40196970123213 1 11 +224.99999999999997 90.0 170.26438968275465 1 12 diff --git a/python/tests/reference/Orientation/cI_slip.txt b/python/tests/reference/Orientation/cI_slip.txt new file mode 100644 index 000000000..5401392c8 --- /dev/null +++ b/python/tests/reference/Orientation/cI_slip.txt @@ -0,0 +1,25 @@ +3x3:1_Schmid 3x3:2_Schmid 3x3:3_Schmid 3x3:4_Schmid 3x3:5_Schmid 3x3:6_Schmid 3x3:7_Schmid 3x3:8_Schmid 3x3:9_Schmid +0.0 0.4082482904638631 0.408248290463863 0.0 -0.408248290463863 -0.40824829046386296 0.0 0.4082482904638631 0.408248290463863 +0.0 -0.408248290463863 -0.40824829046386296 0.0 -0.408248290463863 -0.40824829046386296 0.0 0.4082482904638631 0.408248290463863 +0.0 -0.408248290463863 0.408248290463863 0.0 -0.408248290463863 0.408248290463863 0.0 -0.408248290463863 0.408248290463863 +0.0 0.40824829046386285 -0.40824829046386285 0.0 -0.408248290463863 0.408248290463863 0.0 -0.408248290463863 0.408248290463863 +-0.40824829046386296 2.4997998108697434e-17 -0.40824829046386285 0.4082482904638631 -2.4997998108697446e-17 0.408248290463863 0.4082482904638631 -2.4997998108697446e-17 0.408248290463863 +-0.408248290463863 2.499799810869744e-17 -0.40824829046386296 -0.408248290463863 2.499799810869744e-17 -0.40824829046386296 0.4082482904638631 -2.4997998108697446e-17 0.408248290463863 +-0.408248290463863 2.499799810869744e-17 0.408248290463863 -0.408248290463863 2.499799810869744e-17 0.408248290463863 -0.408248290463863 2.499799810869744e-17 0.408248290463863 +-0.408248290463863 2.499799810869744e-17 0.408248290463863 0.40824829046386296 -2.4997998108697437e-17 -0.40824829046386296 -0.408248290463863 2.499799810869744e-17 0.408248290463863 +-0.40824829046386296 -0.40824829046386285 4.999599621739487e-17 0.4082482904638631 0.408248290463863 -4.999599621739489e-17 0.4082482904638631 0.408248290463863 -4.999599621739489e-17 +-0.4082482904638631 -0.408248290463863 4.999599621739489e-17 0.408248290463863 0.40824829046386296 -4.999599621739488e-17 -0.4082482904638631 -0.408248290463863 4.999599621739489e-17 +-0.408248290463863 0.408248290463863 0.0 -0.408248290463863 0.408248290463863 0.0 -0.408248290463863 0.408248290463863 0.0 +-0.40824829046386296 0.40824829046386296 0.0 -0.40824829046386296 0.40824829046386296 0.0 0.408248290463863 -0.408248290463863 0.0 +-0.4714045207910316 -0.23570226039551578 -0.23570226039551576 0.4714045207910318 0.23570226039551587 0.23570226039551584 0.4714045207910318 0.23570226039551587 0.23570226039551584 +-0.4714045207910318 0.23570226039551595 0.2357022603955159 -0.4714045207910318 0.23570226039551595 0.2357022603955159 -0.4714045207910318 0.23570226039551595 0.2357022603955159 +0.47140452079103173 -0.2357022603955159 0.23570226039551584 0.47140452079103173 -0.2357022603955159 0.23570226039551584 -0.4714045207910318 0.23570226039551595 -0.23570226039551587 +0.4714045207910318 0.23570226039551587 -0.23570226039551595 -0.47140452079103173 -0.23570226039551584 0.2357022603955159 0.4714045207910318 0.23570226039551587 -0.23570226039551595 +0.2357022603955159 0.4714045207910318 0.23570226039551584 -0.23570226039551587 -0.47140452079103173 -0.23570226039551578 0.2357022603955159 0.4714045207910318 0.23570226039551584 +-0.23570226039551587 0.47140452079103173 0.23570226039551587 -0.23570226039551587 0.47140452079103173 0.23570226039551587 0.2357022603955159 -0.4714045207910318 -0.2357022603955159 +0.2357022603955159 -0.4714045207910318 0.23570226039551595 0.2357022603955159 -0.4714045207910318 0.23570226039551595 0.2357022603955159 -0.4714045207910318 0.23570226039551595 +-0.2357022603955158 -0.4714045207910316 0.23570226039551584 0.2357022603955159 0.4714045207910318 -0.23570226039551595 0.2357022603955159 0.4714045207910318 -0.23570226039551595 +0.23570226039551587 0.23570226039551584 0.47140452079103173 0.23570226039551587 0.23570226039551584 0.47140452079103173 -0.2357022603955159 -0.23570226039551587 -0.4714045207910318 +-0.2357022603955159 0.2357022603955159 0.4714045207910318 0.23570226039551587 -0.23570226039551587 -0.47140452079103173 -0.2357022603955159 0.2357022603955159 0.4714045207910318 +-0.2357022603955158 0.2357022603955158 -0.4714045207910316 0.2357022603955159 -0.2357022603955159 0.4714045207910318 0.2357022603955159 -0.2357022603955159 0.4714045207910318 +0.2357022603955159 0.23570226039551587 -0.4714045207910318 0.2357022603955159 0.23570226039551587 -0.4714045207910318 0.2357022603955159 0.23570226039551587 -0.4714045207910318 diff --git a/python/tests/reference/Orientation/cI_twin.txt b/python/tests/reference/Orientation/cI_twin.txt new file mode 100644 index 000000000..accbd02d8 --- /dev/null +++ b/python/tests/reference/Orientation/cI_twin.txt @@ -0,0 +1,13 @@ +3x3:1_Schmid 3x3:2_Schmid 3x3:3_Schmid 3x3:4_Schmid 3x3:5_Schmid 3x3:6_Schmid 3x3:7_Schmid 3x3:8_Schmid 3x3:9_Schmid +-0.4714045207910316 -0.23570226039551578 -0.23570226039551576 0.4714045207910318 0.23570226039551587 0.23570226039551584 0.4714045207910318 0.23570226039551587 0.23570226039551584 +-0.4714045207910318 0.23570226039551595 0.2357022603955159 -0.4714045207910318 0.23570226039551595 0.2357022603955159 -0.4714045207910318 0.23570226039551595 0.2357022603955159 +0.47140452079103173 -0.2357022603955159 0.23570226039551584 0.47140452079103173 -0.2357022603955159 0.23570226039551584 -0.4714045207910318 0.23570226039551595 -0.23570226039551587 +0.4714045207910318 0.23570226039551587 -0.23570226039551595 -0.47140452079103173 -0.23570226039551584 0.2357022603955159 0.4714045207910318 0.23570226039551587 -0.23570226039551595 +0.2357022603955159 0.4714045207910318 0.23570226039551584 -0.23570226039551587 -0.47140452079103173 -0.23570226039551578 0.2357022603955159 0.4714045207910318 0.23570226039551584 +-0.23570226039551587 0.47140452079103173 0.23570226039551587 -0.23570226039551587 0.47140452079103173 0.23570226039551587 0.2357022603955159 -0.4714045207910318 -0.2357022603955159 +0.2357022603955159 -0.4714045207910318 0.23570226039551595 0.2357022603955159 -0.4714045207910318 0.23570226039551595 0.2357022603955159 -0.4714045207910318 0.23570226039551595 +-0.2357022603955158 -0.4714045207910316 0.23570226039551584 0.2357022603955159 0.4714045207910318 -0.23570226039551595 0.2357022603955159 0.4714045207910318 -0.23570226039551595 +0.23570226039551587 0.23570226039551584 0.47140452079103173 0.23570226039551587 0.23570226039551584 0.47140452079103173 -0.2357022603955159 -0.23570226039551587 -0.4714045207910318 +-0.2357022603955159 0.2357022603955159 0.4714045207910318 0.23570226039551587 -0.23570226039551587 -0.47140452079103173 -0.2357022603955159 0.2357022603955159 0.4714045207910318 +-0.2357022603955158 0.2357022603955158 -0.4714045207910316 0.2357022603955159 -0.2357022603955159 0.4714045207910318 0.2357022603955159 -0.2357022603955159 0.4714045207910318 +0.2357022603955159 0.23570226039551587 -0.4714045207910318 0.2357022603955159 0.23570226039551587 -0.4714045207910318 0.2357022603955159 0.23570226039551587 -0.4714045207910318 diff --git a/python/tests/reference/Orientation/hP_slip.txt b/python/tests/reference/Orientation/hP_slip.txt new file mode 100644 index 000000000..a67eea150 --- /dev/null +++ b/python/tests/reference/Orientation/hP_slip.txt @@ -0,0 +1,34 @@ +3x3:1_Schmid 3x3:2_Schmid 3x3:3_Schmid 3x3:4_Schmid 3x3:5_Schmid 3x3:6_Schmid 3x3:7_Schmid 3x3:8_Schmid 3x3:9_Schmid +0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 +0.0 0.0 -0.4999999999999998 0.0 0.0 0.8660254037844388 0.0 0.0 0.0 +0.0 0.0 -0.5000000000000001 0.0 0.0 -0.8660254037844386 0.0 0.0 0.0 +0.0 1.0 -5.914589856893347e-17 0.0 0.0 0.0 0.0 0.0 0.0 +0.4330127018922192 0.24999999999999975 -3.102315069664884e-17 -0.7500000000000002 -0.4330127018922192 5.373367321746164e-17 0.0 0.0 0.0 +-0.43301270189221935 0.25000000000000006 1.4502014121821253e-18 -0.7499999999999998 0.4330127018922193 2.5118225271075755e-18 0.0 0.0 0.0 +-0.4330127018922194 -0.7499999999999999 6.059609998111558e-17 0.2500000000000001 0.4330127018922194 -3.498517463593857e-17 0.0 0.0 0.0 +2.563950248511418e-16 -5.693113199781536e-32 -9.614043519462407e-33 1.0 -2.220446049250313e-16 -3.7496997163046135e-17 0.0 0.0 0.0 +0.4330127018922194 -0.75 2.8122747872284606e-17 0.25000000000000006 -0.43301270189221935 1.6236676054415494e-17 0.0 0.0 0.0 +-0.38254602783800284 -0.22086305214969287 -0.23426064283290896 0.6625891564490795 0.38254602783800284 0.40575133560034454 0.0 0.0 0.0 +0.0 -0.8834522085987724 -0.4685212856658182 0.0 0.0 0.0 0.0 0.0 0.0 +0.38254602783800307 -0.22086305214969315 -0.23426064283290912 0.6625891564490792 -0.38254602783800296 -0.40575133560034443 0.0 0.0 0.0 +-0.38254602783800284 -0.22086305214969287 0.23426064283290904 0.6625891564490795 0.38254602783800284 -0.4057513356003447 0.0 0.0 0.0 +0.0 -0.8834522085987724 0.46852128566581835 0.0 0.0 0.0 0.0 0.0 0.0 +0.38254602783800307 -0.22086305214969315 0.23426064283290912 0.6625891564490792 -0.38254602783800296 0.40575133560034443 0.0 0.0 0.0 +-0.39955629492721617 -0.23068393443263763 -0.24467726152216654 3.8591083978971935e-17 2.228057272357889e-17 2.3632116092343994e-17 0.6524726973924442 0.3767052874784087 0.39955629492721606 +-0.19977814746360817 -0.11534196721631886 -0.12233863076108333 -0.34602590164895664 -0.19977814746360797 -0.21189672420660496 0.6524726973924442 0.3767052874784087 0.39955629492721606 +0.0 -0.23068393443263785 -0.12233863076108334 0.0 -0.39955629492721617 -0.211896724206605 0.0 0.7534105749568177 0.3995562949272161 +0.0 0.23068393443263768 0.12233863076108326 0.0 -0.3995562949272162 -0.21189672420660502 0.0 0.7534105749568178 0.39955629492721617 +-0.199778147463608 0.11534196721631884 0.12233863076108324 0.34602590164895664 -0.1997781474636081 -0.211896724206605 -0.6524726973924442 0.3767052874784089 0.3995562949272161 +-0.3995562949272161 0.23068393443263774 0.24467726152216654 -3.859108397897193e-17 2.22805727235789e-17 2.3632116092343994e-17 -0.6524726973924441 0.37670528747840887 0.39955629492721606 +-0.39955629492721617 -0.23068393443263763 0.24467726152216662 -3.8591083978971935e-17 -2.228057272357889e-17 2.3632116092344003e-17 -0.6524726973924442 -0.3767052874784087 0.39955629492721617 +-0.1997781474636082 -0.11534196721631888 0.1223386307610834 -0.3460259016489568 -0.19977814746360806 0.21189672420660513 -0.6524726973924442 -0.3767052874784087 0.39955629492721617 +0.0 -0.23068393443263788 0.12233863076108341 0.0 -0.3995562949272163 0.21189672420660516 0.0 -0.7534105749568177 0.3995562949272162 +0.0 0.2306839344326376 -0.12233863076108324 0.0 -0.3995562949272163 0.21189672420660516 0.0 -0.7534105749568177 0.3995562949272162 +-0.19977814746360792 0.1153419672163188 -0.12233863076108319 0.34602590164895675 -0.19977814746360814 0.21189672420660505 0.6524726973924441 -0.37670528747840887 0.39955629492721606 +-0.3995562949272161 0.23068393443263774 -0.24467726152216654 3.859108397897193e-17 -2.22805727235789e-17 2.3632116092343994e-17 0.6524726973924441 -0.37670528747840887 0.39955629492721606 +-0.11134044285378089 -0.19284730395996755 -0.1363636363636364 -0.19284730395996755 -0.3340213285613424 -0.23618874648666507 0.36363636363636365 0.6298366572977734 0.44536177141512323 +-0.11134044285378081 0.1928473039599675 0.13636363636363633 0.19284730395996758 -0.3340213285613426 -0.2361887464866651 -0.3636363636363637 0.6298366572977737 0.4453617714151233 +-0.44536177141512323 9.889017858258314e-17 0.2727272727272727 -4.301519895922435e-17 9.551292858672588e-33 2.634132215859942e-17 -0.7272727272727272 1.6148698540002275e-16 0.44536177141512323 +-0.1113404428537809 -0.1928473039599676 0.13636363636363644 -0.1928473039599676 -0.33402132856134253 0.23618874648666516 -0.36363636363636365 -0.6298366572977734 0.44536177141512323 +-0.11134044285378074 0.19284730395996735 -0.13636363636363627 0.19284730395996758 -0.33402132856134253 0.23618874648666516 0.3636363636363636 -0.6298366572977734 0.44536177141512323 +-0.44536177141512334 9.889017858258316e-17 -0.2727272727272727 4.3015198959224354e-17 -9.55129285867259e-33 2.634132215859942e-17 0.7272727272727273 -1.6148698540002277e-16 0.44536177141512323 diff --git a/python/tests/reference/Orientation/hP_twin.txt b/python/tests/reference/Orientation/hP_twin.txt new file mode 100644 index 000000000..62ce6fdef --- /dev/null +++ b/python/tests/reference/Orientation/hP_twin.txt @@ -0,0 +1,25 @@ +3x3:1_Schmid 3x3:2_Schmid 3x3:3_Schmid 3x3:4_Schmid 3x3:5_Schmid 3x3:6_Schmid 3x3:7_Schmid 3x3:8_Schmid 3x3:9_Schmid +-0.3743506488634663 -0.21613144789263322 -0.4584840372976439 -0.21613144789263333 -0.12478354962115536 -0.2647058823529411 0.4075413664867946 0.23529411764705865 0.4991341984846217 +0.0 -1.1032987950073291e-16 -1.1702250894369439e-16 0.0 -0.4991341984846217 -0.5294117647058824 0.0 0.47058823529411753 0.4991341984846218 +-0.3743506488634663 0.2161314478926334 0.4584840372976439 0.2161314478926334 -0.1247835496211555 -0.2647058823529412 -0.4075413664867947 0.23529411764705888 0.4991341984846218 +-0.3743506488634663 -0.21613144789263322 0.4584840372976439 -0.2161314478926334 -0.12478354962115538 0.2647058823529412 -0.4075413664867946 -0.23529411764705865 0.4991341984846217 +0.0 -1.4562117094830577e-16 1.5445457619280876e-16 0.0 -0.4991341984846217 0.5294117647058824 0.0 -0.47058823529411753 0.4991341984846218 +-0.3743506488634663 0.2161314478926334 -0.4584840372976439 0.21613144789263342 -0.12478354962115551 0.26470588235294124 0.4075413664867947 -0.23529411764705888 0.4991341984846218 +-0.06998542122237655 -0.1212183053462653 -0.04285714285714286 -0.12121830534626532 -0.20995626366712955 -0.07423074889580901 0.45714285714285724 0.7917946548886295 0.279941684889506 +-0.06998542122237653 0.12121830534626528 0.04285714285714286 0.12121830534626532 -0.20995626366712958 -0.07423074889580904 -0.45714285714285724 0.7917946548886297 0.2799416848895061 +-0.27994168488950616 6.2159540823338e-17 0.08571428571428573 -5.407625012016776e-17 1.2007339593759827e-32 1.6557402499691063e-17 -0.9142857142857143 2.0301221021717148e-16 0.27994168488950605 +-0.0699854212223766 -0.12121830534626538 0.04285714285714291 -0.12121830534626538 -0.20995626366712963 0.07423074889580909 -0.45714285714285724 -0.7917946548886295 0.2799416848895062 +-0.06998542122237648 0.12121830534626521 -0.04285714285714283 0.12121830534626538 -0.20995626366712966 0.07423074889580906 0.45714285714285724 -0.7917946548886297 0.2799416848895061 +-0.27994168488950605 6.215954082333798e-17 -0.08571428571428569 5.407625012016776e-17 -1.2007339593759827e-32 1.6557402499691063e-17 0.9142857142857143 -2.0301221021717148e-16 0.27994168488950605 +0.3104371234477526 0.17923095678901296 0.19010313741609627 0.17923095678901305 0.1034790411492508 0.1097560975609756 -0.6759222663683424 -0.39024390243902424 -0.41391616459700337 +0.0 7.68600963028337e-17 4.07612214737886e-17 0.0 0.4139161645970035 0.21951219512195125 0.0 -0.7804878048780488 -0.4139161645970034 +0.31043712344775254 -0.17923095678901305 -0.19010313741609627 -0.17923095678901302 0.10347904114925086 0.1097560975609756 0.6759222663683423 -0.3902439024390244 -0.41391616459700337 +0.3104371234477527 0.179230956789013 -0.19010313741609638 0.17923095678901313 0.10347904114925086 -0.10975609756097568 0.6759222663683424 0.3902439024390242 -0.4139161645970035 +0.0 1.3539199431344235e-16 -7.180244797305419e-17 0.0 0.4139161645970036 -0.21951219512195136 0.0 0.7804878048780487 -0.41391616459700353 +0.3104371234477525 -0.179230956789013 0.19010313741609622 -0.17923095678901313 0.10347904114925092 -0.10975609756097565 -0.6759222663683423 0.3902439024390244 -0.41391616459700337 +0.11134044285378089 0.19284730395996755 0.1363636363636364 0.19284730395996755 0.3340213285613424 0.23618874648666507 -0.36363636363636365 -0.6298366572977734 -0.44536177141512323 +0.11134044285378081 -0.1928473039599675 -0.13636363636363633 -0.19284730395996758 0.3340213285613426 0.2361887464866651 0.3636363636363637 -0.6298366572977737 -0.4453617714151233 +0.44536177141512323 -9.889017858258314e-17 -0.2727272727272727 4.301519895922435e-17 -9.551292858672588e-33 -2.634132215859942e-17 0.7272727272727272 -1.6148698540002275e-16 -0.44536177141512323 +0.1113404428537809 0.1928473039599676 -0.13636363636363644 0.1928473039599676 0.33402132856134253 -0.23618874648666516 0.36363636363636365 0.6298366572977734 -0.44536177141512323 +0.11134044285378074 -0.19284730395996735 0.13636363636363627 -0.19284730395996758 0.33402132856134253 -0.23618874648666516 -0.3636363636363636 0.6298366572977734 -0.44536177141512323 +0.44536177141512334 -9.889017858258316e-17 0.2727272727272727 -4.3015198959224354e-17 9.55129285867259e-33 -2.634132215859942e-17 -0.7272727272727273 1.6148698540002277e-16 -0.44536177141512323 diff --git a/python/tests/reference/Orientation/unitcell_cubic_0_0_0.pdf b/python/tests/reference/Orientation/unitcell_cubic_0_0_0.pdf new file mode 100644 index 000000000..f8ed27163 Binary files /dev/null and b/python/tests/reference/Orientation/unitcell_cubic_0_0_0.pdf differ diff --git a/python/tests/reference/Orientation/unitcell_cubic_0_45_0.pdf b/python/tests/reference/Orientation/unitcell_cubic_0_45_0.pdf new file mode 100644 index 000000000..1ebbb10d3 Binary files /dev/null and b/python/tests/reference/Orientation/unitcell_cubic_0_45_0.pdf differ diff --git a/python/tests/reference/Orientation/unitcell_cubic_180_45_180.pdf b/python/tests/reference/Orientation/unitcell_cubic_180_45_180.pdf new file mode 100644 index 000000000..9ecb9f44b Binary files /dev/null and b/python/tests/reference/Orientation/unitcell_cubic_180_45_180.pdf differ diff --git a/python/tests/reference/Orientation/unitcell_cubic_45_0_0.pdf b/python/tests/reference/Orientation/unitcell_cubic_45_0_0.pdf new file mode 100644 index 000000000..433a01bb2 Binary files /dev/null and b/python/tests/reference/Orientation/unitcell_cubic_45_0_0.pdf differ diff --git a/python/tests/reference/Orientation/unitcell_cubic_90_45_270.pdf b/python/tests/reference/Orientation/unitcell_cubic_90_45_270.pdf new file mode 100644 index 000000000..46d32c470 Binary files /dev/null and b/python/tests/reference/Orientation/unitcell_cubic_90_45_270.pdf differ diff --git a/python/tests/reference/Result/12grains6x7x8.geom b/python/tests/reference/Result/12grains6x7x8.geom deleted file mode 100644 index 4e6836bb8..000000000 --- a/python/tests/reference/Result/12grains6x7x8.geom +++ /dev/null @@ -1,61 +0,0 @@ -4 header -grid a 6 b 7 c 8 -size x 0.75 y 0.875 z 1.0 -origin x 0.0 y 0.0 z 0.0 -homogenization 1 - 9 3 3 10 9 9 - 9 1 1 1 9 9 - 9 11 1 1 7 9 - 7 11 11 7 7 7 - 7 11 11 7 7 7 -12 3 3 10 7 12 -12 3 3 10 10 12 -12 3 3 1 9 9 - 9 1 1 1 9 9 - 9 1 1 1 7 7 - 7 1 1 7 7 7 -12 12 3 7 7 7 -12 3 3 3 12 12 -12 3 3 3 12 12 -12 3 3 1 1 12 - 9 1 1 1 1 9 - 6 1 1 1 8 8 - 7 6 8 8 8 8 -12 12 8 8 8 12 -12 3 3 3 12 12 -12 3 3 3 12 12 - 5 6 6 6 1 12 - 6 6 6 6 8 8 - 6 6 6 8 8 8 - 8 6 8 8 8 8 -12 5 8 8 8 8 -12 5 5 8 8 12 - 5 5 5 3 12 12 - 5 5 6 6 6 5 - 6 6 6 6 6 6 - 6 6 6 6 8 8 - 4 4 6 8 8 8 - 4 4 2 2 2 8 - 5 5 5 2 2 2 - 5 5 5 5 2 5 - 5 5 5 10 10 5 - 6 6 6 6 10 4 - 4 4 11 11 2 4 - 4 4 11 2 2 4 - 4 4 2 2 2 2 - 5 5 5 2 2 2 - 5 5 5 10 10 5 - 5 5 10 10 10 9 - 4 11 11 11 10 9 - 4 4 11 11 11 4 - 4 4 11 11 2 4 - 4 4 2 2 2 2 - 5 5 2 2 2 2 - 5 5 10 10 10 10 - 9 10 10 10 10 9 - 9 11 11 10 9 9 - 4 11 11 11 9 9 - 4 11 11 11 7 7 - 4 4 11 2 7 7 -12 10 10 10 10 7 - 9 10 10 10 10 9 diff --git a/python/tests/reference/Result/12grains6x7x8.vtr b/python/tests/reference/Result/12grains6x7x8.vtr new file mode 100644 index 000000000..02ba9d4e6 --- /dev/null +++ b/python/tests/reference/Result/12grains6x7x8.vtr @@ -0,0 +1,30 @@ + + + + + + AQAAAACAAAARAAAAGQAAAA==eF7LyM/NT0/Ny6xKLMnMz1MwZAAAPsIGPQ== + + + + + + + + AQAAAACAAACACgAA2wAAAA==eF6tlssOgkAQBNcHiPj//2uM25eOZc8oc6kYagzdISzbeM/ZeJ/cgDTk7x/c16zm6fduXAOr/mOS8rqXfDH5mqP6pKHcVY9yJN/ziv5/R/k+8lI/GnnLV2uMm1G5tefXnZ6j6v3bD/nXyQWokU8e5a96tJc8z9H1aY88MfWZek3Xf6XnuBhTr+6fgPKpH9oj3/eS5+/bap/yPafo54buJ/mek3zqJeXu+tRP8vycp15E8tNe6rPaf7fPrk/9eO6q598/1GO1/67v53V6nul8T3n9Oy758p47Sgdl + + + + + AQAAAACAAAA4AAAAHAAAAA==eF5jYEAGB+wh9AUofQNKP4DST6D0C3sAisIGjw== + + + AQAAAACAAABAAAAAHwAAAA==eF5jYEAGB+wh9AUofQNKP4DST6D0Cyj9xh4AwVEHug== + + + AQAAAACAAABIAAAAIgAAAA==eF5jYEAGB+wh9AUofQNKP4DST6D0Cyj9Bkp/sAcAAU8I6Q== + + + + + diff --git a/python/tests/reference/Result/12grains6x7x8_tensionY.hdf5 b/python/tests/reference/Result/12grains6x7x8_tensionY.hdf5 index 39c17fadb..4cb07593b 100644 Binary files a/python/tests/reference/Result/12grains6x7x8_tensionY.hdf5 and b/python/tests/reference/Result/12grains6x7x8_tensionY.hdf5 differ diff --git a/python/tests/reference/Result/12grains6x7x8_tensionY.yaml b/python/tests/reference/Result/12grains6x7x8_tensionY.yaml new file mode 100644 index 000000000..50557edda --- /dev/null +++ b/python/tests/reference/Result/12grains6x7x8_tensionY.yaml @@ -0,0 +1,103 @@ +--- +homogenization: + SX: + N_constituents: 1 + mechanics: {type: none} + +phase: + pheno_fcc: + lattice: cF + mechanics: + output: [F, P, F_e, F_p, L_p, O] + elasticity: {C_11: 106.75e9, C_12: 60.41e9, C_44: 28.34e9, type: hooke} + plasticity: + N_sl: [12] + a_sl: 2.25 + atol_xi: 1.0 + dot_gamma_0_sl: 0.001 + h_0_sl_sl: 75e6 + h_sl_sl: [1, 1, 1.4, 1.4, 1.4, 1.4] + n_sl: 20 + output: [xi_sl] + type: phenopowerlaw + xi_0_sl: [31e6] + xi_inf_sl: [63e6] + pheno_bcc: + lattice: cI + mechanics: + output: [F, P, F_e, F_p, L_p, O] + elasticity: {C_11: 106.75e9, C_12: 60.41e9, C_44: 28.34e9, type: hooke} + plasticity: + N_sl: [12] + a_sl: 2.25 + atol_xi: 1.0 + dot_gamma_0_sl: 0.001 + h_0_sl_sl: 75e6 + h_sl_sl: [1, 1, 1.4, 1.4, 1.4, 1.4] + n_sl: 20 + output: [xi_sl] + type: phenopowerlaw + xi_0_sl: [31e6] + xi_inf_sl: [63e6] + +material: + - constituents: + - fraction: 1.0 + O: [0.8229200444892315, 0.5284940239127993, -0.11958598847729246, 0.17086795611292308] + phase: pheno_fcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.029934934533052786, -0.0463822071939717, 0.9983440440417412, 0.01617900728410769] + phase: pheno_fcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.5285808688806949, 0.7326575088838098, 0.4051997815944012, 0.1401013087924221] + phase: pheno_fcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.1839974517790312, 0.49550065903084944, -0.1541415483910751, -0.8347840545305227] + phase: pheno_fcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.8055693100147384, -0.22778497057116814, -0.028331746016454287, 0.5462320075864553] + phase: pheno_fcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.8025842700117737, -0.33640019337884963, -0.3847408071640489, 0.3076815085881779] + phase: pheno_fcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.6048933483394416, 0.7565005822419409, -0.08545681892422426, -0.2334695661144201] + phase: pheno_bcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.2012339360745425, -0.3580127491130033, -0.7798091137625135, 0.47247171400774884] + phase: pheno_bcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.7949688202267222, 0.3623793306926909, -0.18836147613310203, -0.4485819321629098] + phase: pheno_bcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.19733162113429173, -0.06559103894055797, -0.40230149937129567, 0.8915781236183501] + phase: pheno_bcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.8659916384140512, -0.2761459420825848, 0.38479354764225004, -0.1604238964779258] + phase: pheno_bcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.5951846978175659, 0.4476701545571293, -0.6038886363266418, -0.2840160613735736] + phase: pheno_bcc + homogenization: SX diff --git a/python/tests/reference/Result/6grains6x7x8_single_phase.geom b/python/tests/reference/Result/6grains6x7x8_single_phase.geom deleted file mode 100644 index 14cae0973..000000000 --- a/python/tests/reference/Result/6grains6x7x8_single_phase.geom +++ /dev/null @@ -1,61 +0,0 @@ -4 header -grid a 6 b 7 c 8 -size x 0.75 y 0.875 z 1.0 -origin x 0.0 y 0.0 z 0.0 -homogenization 1 -3 3 3 4 3 3 -3 1 1 1 3 3 -3 5 1 1 1 3 -1 5 5 1 1 1 -1 5 5 1 1 1 -6 3 3 4 1 6 -6 3 3 4 4 6 -6 3 3 1 3 3 -3 1 1 1 3 3 -3 1 1 1 1 1 -1 1 1 1 1 1 -6 6 3 1 1 1 -6 3 3 3 6 6 -6 3 3 3 6 6 -6 3 3 1 1 6 -3 1 1 1 1 3 -6 1 1 1 2 2 -1 6 2 2 2 2 -6 6 2 2 2 6 -6 3 3 3 6 6 -6 3 3 3 6 6 -5 6 6 6 1 6 -6 6 6 6 2 2 -6 6 6 2 2 2 -2 6 2 2 2 2 -6 5 2 2 2 2 -6 5 5 2 2 6 -5 5 5 3 6 6 -5 5 6 6 6 5 -6 6 6 6 6 6 -6 6 6 6 2 2 -4 4 6 2 2 2 -4 4 2 2 2 2 -5 5 5 2 2 2 -5 5 5 5 2 5 -5 5 5 4 4 5 -6 6 6 6 4 4 -4 4 5 5 2 4 -4 4 5 2 2 4 -4 4 2 2 2 2 -5 5 5 2 2 2 -5 5 5 4 4 5 -5 5 4 4 4 3 -4 5 5 5 4 3 -4 4 5 5 5 4 -4 4 5 5 2 4 -4 4 2 2 2 2 -5 5 2 2 2 2 -5 5 4 4 4 4 -3 4 4 4 4 3 -3 5 5 4 3 3 -4 5 5 5 3 3 -4 5 5 5 1 1 -4 4 5 2 1 1 -6 4 4 4 4 1 -3 4 4 4 4 3 diff --git a/python/tests/reference/Result/6grains6x7x8_single_phase.vtr b/python/tests/reference/Result/6grains6x7x8_single_phase.vtr new file mode 100644 index 000000000..9a7b7dd7d --- /dev/null +++ b/python/tests/reference/Result/6grains6x7x8_single_phase.vtr @@ -0,0 +1,30 @@ + + + + + + AQAAAACAAAARAAAAGQAAAA==eF7LyM/NT0/Ny6xKLMnMz1MwZAAAPsIGPQ== + + + + + + + + AQAAAACAAACACgAAwAAAAA==eF69lcsOwjAQA6GU//9lDsUSGjHyBgq+WGpm09jqY7sc2uA3uR43Gb+/YV/FfXd405S/P93ykmt8vPHRWX3+SpbDZHnj3O8snpqeN+L9TFd4lDmu05ljyn3bj/F5P2yfyNZbnilnc1MuOVZ5mzMu3vpsvbb1T5057Ltk/ZBvfVo/qzznGsdzTvvknO3D8zS+9fjvPlsu4/ifn87bf7DNNf7sPlf59rxYbuPircdp/6s81Z5navoeRav9PACxsANv + + + + + AQAAAACAAAA4AAAAHAAAAA==eF5jYEAGB+wh9AUofQNKP4DST6D0C3sAisIGjw== + + + AQAAAACAAABAAAAAHwAAAA==eF5jYEAGB+wh9AUofQNKP4DST6D0Cyj9xh4AwVEHug== + + + AQAAAACAAABIAAAAIgAAAA==eF5jYEAGB+wh9AUofQNKP4DST6D0Cyj9Bkp/sAcAAU8I6Q== + + + + + diff --git a/python/tests/reference/Result/6grains6x7x8_single_phase_tensionY.hdf5 b/python/tests/reference/Result/6grains6x7x8_single_phase_tensionY.hdf5 index 7812f82e8..f0ac836c6 100644 Binary files a/python/tests/reference/Result/6grains6x7x8_single_phase_tensionY.hdf5 and b/python/tests/reference/Result/6grains6x7x8_single_phase_tensionY.hdf5 differ diff --git a/python/tests/reference/Result/6grains6x7x8_single_phase_tensionY.yaml b/python/tests/reference/Result/6grains6x7x8_single_phase_tensionY.yaml new file mode 100644 index 000000000..0fc5b0897 --- /dev/null +++ b/python/tests/reference/Result/6grains6x7x8_single_phase_tensionY.yaml @@ -0,0 +1,56 @@ +--- +homogenization: + SX: + N_constituents: 1 + mechanics: {type: none} + +phase: + pheno_fcc: + lattice: cF + mechanics: + output: [F, P, F_e, F_p, L_p, O] + elasticity: {C_11: 106.75e9, C_12: 60.41e9, C_44: 28.34e9, type: hooke} + plasticity: + N_sl: [12] + a_sl: 2.25 + atol_xi: 1.0 + dot_gamma_0_sl: 0.001 + h_0_sl_sl: 75e6 + h_sl_sl: [1, 1, 1.4, 1.4, 1.4, 1.4] + n_sl: 20 + output: [xi_sl] + type: phenopowerlaw + xi_0_sl: [31e6] + xi_inf_sl: [63e6] + +material: + - constituents: + - fraction: 1.0 + O: [0.8229200444892315, 0.5284940239127993, -0.11958598847729246, 0.17086795611292308] + phase: pheno_fcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.029934934533052786, -0.0463822071939717, 0.9983440440417412, 0.01617900728410769] + phase: pheno_fcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.5285808688806949, 0.7326575088838098, 0.4051997815944012, 0.1401013087924221] + phase: pheno_fcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.1839974517790312, 0.49550065903084944, -0.1541415483910751, -0.8347840545305227] + phase: pheno_fcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.8055693100147384, -0.22778497057116814, -0.028331746016454287, 0.5462320075864553] + phase: pheno_fcc + homogenization: SX + - constituents: + - fraction: 1.0 + O: [0.8025842700117737, -0.33640019337884963, -0.3847408071640489, 0.3076815085881779] + phase: pheno_fcc + homogenization: SX diff --git a/python/tests/reference/Result/material.config b/python/tests/reference/Result/material.config deleted file mode 100644 index 4820b1eed..000000000 --- a/python/tests/reference/Result/material.config +++ /dev/null @@ -1,126 +0,0 @@ - -[none] -mech none -ngrains 1 - - -[Grain1] -(gauss) phi1 358.98 Phi 65.62 phi2 24.48 -[Grain2] -(gauss) phi1 121.05 Phi 176.11 phi2 295.73 -[Grain3] -(gauss) phi1 43.79 Phi 113.76 phi2 345.90 -[Grain4] -(gauss) phi1 265.15 Phi 62.52 phi2 299.71 -[Grain5] -(gauss) phi1 221.23 Phi 26.54 phi2 207.05 -[Grain6] -(gauss) phi1 249.81 Phi 61.47 phi2 152.14 -[Grain7] -(gauss) phi1 332.45 Phi 99.16 phi2 345.34 -[Grain8] -(gauss) phi1 312.27 Phi 118.27 phi2 181.59 -[Grain9] -(gauss) phi1 303.10 Phi 48.21 phi2 358.03 -[Grain10] -(gauss) phi1 338.26 Phi 48.11 phi2 176.78 -[Grain11] -(gauss) phi1 115.17 Phi 56.54 phi2 223.84 -[Grain12] -(gauss) phi1 281.04 Phi 97.48 phi2 27.94 - - -[Grain1] -crystallite 1 -(constituent) phase 1 texture 1 fraction 1.0 -[Grain2] -crystallite 1 -(constituent) phase 1 texture 2 fraction 1.0 -[Grain3] -crystallite 1 -(constituent) phase 1 texture 3 fraction 1.0 -[Grain4] -crystallite 1 -(constituent) phase 1 texture 4 fraction 1.0 -[Grain5] -crystallite 1 -(constituent) phase 1 texture 5 fraction 1.0 -[Grain6] -crystallite 1 -(constituent) phase 1 texture 6 fraction 1.0 -[Grain7] -crystallite 1 -(constituent) phase 2 texture 7 fraction 1.0 -[Grain8] -crystallite 1 -(constituent) phase 2 texture 8 fraction 1.0 -[Grain9] -crystallite 1 -(constituent) phase 2 texture 9 fraction 1.0 -[Grain10] -crystallite 1 -(constituent) phase 2 texture 10 fraction 1.0 -[Grain11] -crystallite 1 -(constituent) phase 2 texture 11 fraction 1.0 -[Grain12] -crystallite 1 -(constituent) phase 2 texture 12 fraction 1.0 - - -[pheno_fcc] -elasticity hooke -plasticity phenopowerlaw - -(output) orientation # quaternion -(output) F # deformation gradient tensor -(output) Fe # elastic deformation gradient tensor -(output) Fp # plastic deformation gradient tensor -(output) P # first Piola-Kichhoff stress tensor -(output) Lp # plastic velocity gradient tensor - - -lattice_structure fcc -Nslip 12 # per family -Ntwin 0 # per family - -c11 106.75e9 -c12 60.41e9 -c44 28.34e9 - -gdot0_slip 0.001 -n_slip 20 -tau0_slip 31e6 # per family -tausat_slip 63e6 # per family -a_slip 2.25 -h0_slipslip 75e6 -interaction_slipslip 1 1 1.4 1.4 1.4 1.4 -atol_resistance 1 - -[pheno_bcc] -elasticity hooke -plasticity phenopowerlaw - -(output) orientation # quaternion -(output) F # deformation gradient tensor -(output) Fe # elastic deformation gradient tensor -(output) Fp # plastic deformation gradient tensor -(output) P # first Piola-Kichhoff stress tensor -(output) Lp # plastic velocity gradient tensor - - -lattice_structure bcc -Nslip 12 # per family - -c11 106.75e9 -c12 60.41e9 -c44 28.34e9 - -gdot0_slip 0.001 -n_slip 20 -tau0_slip 31e6 # per family -tausat_slip 63e6 # per family -a_slip 2.25 -h0_slipslip 75e6 -interaction_slipslip 1 1 1.4 1.4 1.4 1.4 -atol_resistance 1 diff --git a/python/tests/reference/Result/tensionY.load b/python/tests/reference/Result/tensionY.load deleted file mode 100644 index 9332144d8..000000000 --- a/python/tests/reference/Result/tensionY.load +++ /dev/null @@ -1 +0,0 @@ -fdot * 0 0 0 1.0e-3 0 0 0 * stress 0 * * * * * * * 0 time 20 incs 40 freq 4 diff --git a/python/tests/reference/Result/tensionY.yaml b/python/tests/reference/Result/tensionY.yaml new file mode 100644 index 000000000..4cc041729 --- /dev/null +++ b/python/tests/reference/Result/tensionY.yaml @@ -0,0 +1,14 @@ +--- + +step: + - discretization: + t: 20 + N: 40 + f_out: 4 + mechanics: + dot_F: [x, 0, 0, + 0, 1.0e-3, 0, + 0, 0, x] + P: [0, x, x, + x, x, x, + x, x, 0] diff --git a/python/tests/reference/VTK/polyData.vtp b/python/tests/reference/VTK/polyData.vtp index 6ed05f67f..dc4b5f149 100644 --- a/python/tests/reference/VTK/polyData.vtp +++ b/python/tests/reference/VTK/polyData.vtp @@ -1,7 +1,7 @@ - + AQAAAACAAAB4AAAAVgAAAA==eF5jYICBhv2WfY9tLfuS7Ypk3PeDaCDf7okF3/7Vq1bZrV6lZQ+k94HEgHL2QHovUM7+iUUfiG0LlQdhkH77Ipnj9iB5qFp7kBjQDiBmcADRANsaLXM= @@ -31,11 +31,11 @@ - - AAAAAACAAAAAAAAA + + AQAAAACAAABQAAAAIgAAAA==eF4txbcBACAIADAsiP7/sAPJkog2PL28nT4uXz9/BXgALg== - - AAAAAACAAAAAAAAA + + AQAAAACAAABQAAAAIgAAAA==eF4txbcBACAIADA76v8HM5As6a0MTy9vH4evn78TBzAAOA== diff --git a/python/tests/test_Colormap.py b/python/tests/test_Colormap.py index 71d896016..81bf8d2f6 100644 --- a/python/tests/test_Colormap.py +++ b/python/tests/test_Colormap.py @@ -10,16 +10,19 @@ from PIL import ImageChops from damask import Colormap @pytest.fixture -def reference_dir(reference_dir_base): +def ref_path(ref_path_base): """Directory containing reference results.""" - return reference_dir_base/'Colormap' + return ref_path_base/'Colormap' class TestColormap: @pytest.fixture(autouse=True) - def _execution_stamp(self, execution_stamp): + def _patch_execution_stamp(self, patch_execution_stamp): print('patched damask.util.execution_stamp') + def test_repr(self,patch_plt_show): + print(Colormap.from_predefined('stress')) + def test_conversion(self): specials = np.array([[0.,0.,0.], [1.,0.,0.], @@ -128,32 +131,32 @@ class TestColormap: assert (np.allclose(c.colors[:len(c.colors)//2],c.colors[len(c.colors)//2:])) @pytest.mark.parametrize('bounds',[None,[2,10]]) - def test_shade(self,reference_dir,update,bounds): + def test_shade(self,ref_path,update,bounds): data = np.add(*np.indices((10, 11))) img_current = Colormap.from_predefined('orientation').shade(data,bounds=bounds) if update: - img_current.save(reference_dir/f'shade_{bounds}.png') + img_current.save(ref_path/f'shade_{bounds}.png') else: - img_reference = Image.open(reference_dir/f'shade_{bounds}.png') + img_reference = Image.open(ref_path/f'shade_{bounds}.png') diff = ImageChops.difference(img_reference.convert('RGB'),img_current.convert('RGB')) assert not diff.getbbox() - def test_list(self): - Colormap.list_predefined() + def test_predefined(self): + assert (isinstance(Colormap.predefined,dict)) @pytest.mark.parametrize('format,ext',[('ASCII','.txt'), ('paraview','.json'), ('GOM','.legend'), ('gmsh','.msh') ]) - def test_compare_reference(self,format,ext,tmp_path,reference_dir,update): + def test_compare_reference(self,format,ext,tmp_path,ref_path,update): name = 'binary' c = Colormap.from_predefined(name) # noqa if update: - os.chdir(reference_dir) + os.chdir(ref_path) eval(f'c.save_{format}()') else: os.chdir(tmp_path) eval(f'c.save_{format}()') time.sleep(.5) - assert filecmp.cmp(tmp_path/(name+ext),reference_dir/(name+ext)) + assert filecmp.cmp(tmp_path/(name+ext),ref_path/(name+ext)) diff --git a/python/tests/test_Config.py b/python/tests/test_Config.py index e715ad763..67c419b3e 100644 --- a/python/tests/test_Config.py +++ b/python/tests/test_Config.py @@ -1,4 +1,5 @@ import pytest +import numpy as np from damask import Config @@ -29,6 +30,8 @@ class TestConfig: f.write(config.__repr__()) assert Config.load(tmp_path/'config.yaml') == config + def test_numpy(self,tmp_path): + assert Config({'A':np.ones(3,'i')}).__repr__() == Config({'A':[1,1,1]}).__repr__() def test_abstract_is_valid(self): assert Config().is_valid is None diff --git a/python/tests/test_ConfigMaterial.py b/python/tests/test_ConfigMaterial.py index 4863a8ac4..45dc3b97f 100644 --- a/python/tests/test_ConfigMaterial.py +++ b/python/tests/test_ConfigMaterial.py @@ -1,20 +1,22 @@ import os import pytest +import numpy as np from damask import ConfigMaterial +from damask import Table @pytest.fixture -def reference_dir(reference_dir_base): +def ref_path(ref_path_base): """Directory containing reference results.""" - return reference_dir_base/'ConfigMaterial' + return ref_path_base/'ConfigMaterial' class TestConfigMaterial: @pytest.mark.parametrize('fname',[None,'test.yaml']) - def test_load_save(self,reference_dir,tmp_path,fname): - reference = ConfigMaterial.load(reference_dir/'material.yaml') + def test_load_save(self,ref_path,tmp_path,fname): + reference = ConfigMaterial.load(ref_path/'material.yaml') os.chdir(tmp_path) if fname is None: reference.save() @@ -24,53 +26,101 @@ class TestConfigMaterial: new = ConfigMaterial.load(fname) assert reference == new - def test_valid_complete(self,reference_dir): - material_config = ConfigMaterial.load(reference_dir/'material.yaml') + def test_valid_complete(self,ref_path): + material_config = ConfigMaterial.load(ref_path/'material.yaml') assert material_config.is_valid and material_config.is_complete - def test_invalid_lattice(self,reference_dir): - material_config = ConfigMaterial.load(reference_dir/'material.yaml') + def test_invalid_lattice(self,ref_path): + material_config = ConfigMaterial.load(ref_path/'material.yaml') material_config['phase']['Aluminum']['lattice']='fxc' assert not material_config.is_valid - def test_invalid_orientation(self,reference_dir): - material_config = ConfigMaterial.load(reference_dir/'material.yaml') + def test_invalid_orientation(self,ref_path): + material_config = ConfigMaterial.load(ref_path/'material.yaml') material_config['material'][0]['constituents'][0]['O']=[0,0,0,0] assert not material_config.is_valid - def test_invalid_fraction(self,reference_dir): - material_config = ConfigMaterial.load(reference_dir/'material.yaml') + def test_invalid_fraction(self,ref_path): + material_config = ConfigMaterial.load(ref_path/'material.yaml') material_config['material'][0]['constituents'][0]['fraction']=.9 assert not material_config.is_valid @pytest.mark.parametrize('item',['homogenization','phase','material']) - def test_incomplete_missing(self,reference_dir,item): - material_config = ConfigMaterial.load(reference_dir/'material.yaml') + def test_incomplete_missing(self,ref_path,item): + material_config = ConfigMaterial.load(ref_path/'material.yaml') del material_config[item] assert not material_config.is_complete @pytest.mark.parametrize('item',['O','phase']) - def test_incomplete_material_constituent(self,reference_dir,item): - material_config = ConfigMaterial.load(reference_dir/'material.yaml') + def test_incomplete_material_constituent(self,ref_path,item): + material_config = ConfigMaterial.load(ref_path/'material.yaml') del material_config['material'][0]['constituents'][0][item] assert not material_config.is_complete - def test_incomplete_material_homogenization(self,reference_dir): - material_config = ConfigMaterial.load(reference_dir/'material.yaml') + def test_incomplete_material_homogenization(self,ref_path): + material_config = ConfigMaterial.load(ref_path/'material.yaml') del material_config['material'][0]['homogenization'] assert not material_config.is_complete - def test_incomplete_phase_lattice(self,reference_dir): - material_config = ConfigMaterial.load(reference_dir/'material.yaml') + def test_incomplete_homogenization_N_constituents(self,ref_path): + material_config = ConfigMaterial.load(ref_path/'material.yaml') + for h in material_config['homogenization'].keys(): + del material_config['homogenization'][h]['N_constituents'] + assert not material_config.is_complete + + def test_incomplete_phase_lattice(self,ref_path): + material_config = ConfigMaterial.load(ref_path/'material.yaml') del material_config['phase']['Aluminum']['lattice'] assert not material_config.is_complete - def test_incomplete_wrong_phase(self,reference_dir): - material_config = ConfigMaterial.load(reference_dir/'material.yaml') + def test_incomplete_wrong_phase(self,ref_path): + material_config = ConfigMaterial.load(ref_path/'material.yaml') new = material_config.material_rename_phase({'Steel':'FeNbC'}) assert not new.is_complete - def test_incomplete_wrong_homogenization(self,reference_dir): - material_config = ConfigMaterial.load(reference_dir/'material.yaml') + def test_incomplete_wrong_homogenization(self,ref_path): + material_config = ConfigMaterial.load(ref_path/'material.yaml') new = material_config.material_rename_homogenization({'Taylor':'isostrain'}) assert not new.is_complete + + def test_from_table(self): + N = np.random.randint(3,10) + a = np.vstack((np.hstack((np.arange(N),np.arange(N)[::-1])),np.ones(N*2),np.zeros(N*2),np.ones(N*2))).T + t = Table(a,{'varying':2,'constant':2}) + c = ConfigMaterial.from_table(t,constituents={'a':'varying','b':'1_constant'},c='2_constant') + assert len(c['material']) == N + for i,m in enumerate(c['material']): + c = m['constituents'][0] + assert m['c'] == 1 and c['b'] == 0 and (c['a'] == [i,1]).all() + + def test_constituents(self): + c = ConfigMaterial._constituents(c=1,v=[2,3]) + assert c[0][0]['c'] == c[1][0]['c'] == 1 + assert c[0][0]['v'] == c[1][0]['v'] -1 ==2 + + @pytest.mark.parametrize('constituents',[{'W':1,'X':[2,3]},{'Y':4},{'Z':[5,6]}]) + @pytest.mark.parametrize('a',[[7.,8.],9.]) + @pytest.mark.parametrize('b',['bd',['efg','hi']]) + def test_material_add(self,tmp_path,constituents,a,b): + len_c = len(ConfigMaterial()._constituents(1,**constituents)) + len_a = len(a) if isinstance(a,list) else 1 + len_b = len(b) if isinstance(b,list) else 1 + m = ConfigMaterial().material_add(constituents,a=a,b=b) + m.save() + assert len(m['material']) == np.max([len_a,len_b,len_c]) + + @pytest.mark.parametrize('constituents',[{'W':1,'X':np.array([2,3])},{'Y':4},{'Z':np.array([5,6])}]) + @pytest.mark.parametrize('a',[np.array([7,8]),9]) + def test_material_add_np(self,tmp_path,constituents,a): + len_c = len(ConfigMaterial()._constituents(1,**constituents)) + len_a = len(a) if isinstance(a,np.ndarray) else 1 + m = ConfigMaterial().material_add(constituents,ld=a) + m.save() + assert len(m['material']) == np.max([len_a,len_c]) + + @pytest.mark.parametrize('constituents',[{'X':np.array([2,3,4,5])},{'Y':4}]) + @pytest.mark.parametrize('a',[np.array([1,2,3]),[4,5,6]]) + @pytest.mark.parametrize('b',[np.array([6.,7.]),[8.,9.]]) + def test_material_add_invalid(self,constituents,a,b): + with pytest.raises(ValueError): + ConfigMaterial().material_add(constituents,a=a,u=b) diff --git a/python/tests/test_Geom.py b/python/tests/test_Geom.py index 8c44daba7..85da049fa 100644 --- a/python/tests/test_Geom.py +++ b/python/tests/test_Geom.py @@ -3,8 +3,11 @@ import numpy as np from damask import VTK from damask import Geom +from damask import Table from damask import Rotation from damask import util +from damask import seeds +from damask import grid_filters def geom_equal(a,b): @@ -23,17 +26,21 @@ def default(): return Geom(x,[8e-6,5e-6,4e-6]) @pytest.fixture -def reference_dir(reference_dir_base): +def ref_path(ref_path_base): """Directory containing reference results.""" - return reference_dir_base/'Geom' + return ref_path_base/'Geom' class TestGeom: @pytest.fixture(autouse=True) - def _execution_stamp(self, execution_stamp): + def _patch_execution_stamp(self, patch_execution_stamp): print('patched damask.util.execution_stamp') + @pytest.fixture(autouse=True) + def _patch_datetime_now(self, patch_datetime_now): + print('patched datetime.datetime.now') + def test_diff_equal(self,default): assert str(default.diff(default)) == '' @@ -42,6 +49,8 @@ class TestGeom: new = Geom(default.material[1:,1:,1:]+1,default.size*.9,np.ones(3)-default.origin,comments=['modified']) assert str(default.diff(new)) != '' + def test_repr(self,default): + print(default) def test_read_write_vtr(self,default,tmp_path): default.save(tmp_path/'default') @@ -49,8 +58,8 @@ class TestGeom: assert geom_equal(new,default) def test_invalid_vtr(self,tmp_path): - v = VTK.from_rectilinearGrid(np.random.randint(5,10,3)*2,np.random.random(3) + 1.0) - v.save(tmp_path/'no_materialpoint.vtr') + v = VTK.from_rectilinear_grid(np.random.randint(5,10,3)*2,np.random.random(3) + 1.0) + v.save(tmp_path/'no_materialpoint.vtr',parallel=False) with pytest.raises(ValueError): Geom.load(tmp_path/'no_materialpoint.vtr') @@ -67,6 +76,10 @@ class TestGeom: Geom(default.material[1:,1:,1:], size=np.ones(2)) + def test_save_load_ASCII(self,default,tmp_path): + default.save_ASCII(tmp_path/'ASCII') + default.material -= 1 + assert geom_equal(Geom.load_ASCII(tmp_path/'ASCII'),default) def test_invalid_origin(self,default): with pytest.raises(ValueError): @@ -95,10 +108,10 @@ class TestGeom: (['y','z'], False) ] ) - def test_mirror(self,default,update,reference_dir,directions,reflect): + def test_mirror(self,default,update,ref_path,directions,reflect): modified = default.mirror(directions,reflect) tag = f'directions_{"-".join(directions)}+reflect_{reflect}' - reference = reference_dir/f'mirror_{tag}.vtr' + reference = ref_path/f'mirror_{tag}.vtr' if update: modified.save(reference) assert geom_equal(Geom.load(reference), modified) @@ -117,10 +130,10 @@ class TestGeom: ['y','z'], ] ) - def test_flip(self,default,update,reference_dir,directions): + def test_flip(self,default,update,ref_path,directions): modified = default.flip(directions) tag = f'directions_{"-".join(directions)}' - reference = reference_dir/f'flip_{tag}.vtr' + reference = ref_path/f'flip_{tag}.vtr' if update: modified.save(reference) assert geom_equal(Geom.load(reference), modified) @@ -144,9 +157,9 @@ class TestGeom: @pytest.mark.parametrize('stencil',[1,2,3,4]) @pytest.mark.parametrize('selection',[None,[1],[1,2,3]]) @pytest.mark.parametrize('periodic',[True,False]) - def test_clean(self,default,update,reference_dir,stencil,selection,periodic): + def test_clean(self,default,update,ref_path,stencil,selection,periodic): current = default.clean(stencil,selection,periodic) - reference = reference_dir/f'clean_{stencil}_{"+".join(map(str,[None] if selection is None else selection))}_{periodic}' + reference = ref_path/f'clean_{stencil}_{"+".join(map(str,[None] if selection is None else selection))}_{periodic}' if update and stencil > 1: current.save(reference) assert geom_equal(Geom.load(reference) if stencil > 1 else default, @@ -163,10 +176,10 @@ class TestGeom: np.array((10,20,2)) ] ) - def test_scale(self,default,update,reference_dir,grid): + def test_scale(self,default,update,ref_path,grid): modified = default.scale(grid) tag = f'grid_{util.srepr(grid,"-")}' - reference = reference_dir/f'scale_{tag}.vtr' + reference = ref_path/f'scale_{tag}.vtr' if update: modified.save(reference) assert geom_equal(Geom.load(reference), modified) @@ -176,6 +189,7 @@ class TestGeom: material = default.material.copy() for m in np.unique(material): material[material==m] = material.max() + np.random.randint(1,30) + default.material -= 1 modified = Geom(material, default.size, default.origin) @@ -194,6 +208,18 @@ class TestGeom: modified.substitute(np.arange(default.material.max())+1+offset, np.arange(default.material.max())+1)) + def test_substitute_invariant(self,default): + f = np.unique(default.material.flatten())[:np.random.randint(1,default.material.max())] + t = np.random.permutation(f) + modified = default.substitute(f,t) + assert np.array_equiv(t,f) or (not geom_equal(modified,default)) + assert geom_equal(default, modified.substitute(t,f)) + + def test_sort(self): + grid = np.random.randint(5,20,3) + m = Geom(np.random.randint(1,20,grid)*3,np.ones(3)).sort().material.flatten(order='F') + for i,v in enumerate(m): + assert i==0 or v > m[:i].max() or v in m[:i] @pytest.mark.parametrize('axis_angle',[np.array([1,0,0,86.7]), np.array([0,1,0,90.4]), np.array([0,0,1,90]), np.array([1,0,0,175]),np.array([0,-1,0,178]),np.array([0,0,1,180])]) @@ -206,10 +232,10 @@ class TestGeom: @pytest.mark.parametrize('Eulers',[[32.0,68.0,21.0], [0.0,32.0,240.0]]) - def test_rotate(self,default,update,reference_dir,Eulers): - modified = default.rotate(Rotation.from_Eulers(Eulers,degrees=True)) + def test_rotate(self,default,update,ref_path,Eulers): + modified = default.rotate(Rotation.from_Euler_angles(Eulers,degrees=True)) tag = f'Eulers_{util.srepr(Eulers,"-")}' - reference = reference_dir/f'rotate_{tag}.vtr' + reference = ref_path/f'rotate_{tag}.vtr' if update: modified.save(reference) assert geom_equal(Geom.load(reference), modified) @@ -249,12 +275,12 @@ class TestGeom: @pytest.mark.parametrize('inverse',[True,False]) @pytest.mark.parametrize('periodic',[True,False]) def test_add_primitive_rotation(self,center,inverse,periodic): - """Rotation should not change result for sphere (except for discretization errors).""" - g = np.array([32,32,32]) + """Rotation should not change result for sphere.""" + g = np.random.randint(8,32,(3)) + s = np.random.random(3)+.5 fill = np.random.randint(10)+2 - eu=np.array([np.random.randint(4),np.random.randint(2),np.random.randint(4)])*.5*np.pi - G_1 = Geom(np.ones(g,'i'),[1.,1.,1.]).add_primitive(.3,center,1,fill,inverse=inverse,periodic=periodic) - G_2 = Geom(np.ones(g,'i'),[1.,1.,1.]).add_primitive(.3,center,1,fill,Rotation.from_Eulers(eu),inverse,periodic=periodic) + G_1 = Geom(np.ones(g,'i'),s).add_primitive(.3,center,1,fill,inverse=inverse,periodic=periodic) + G_2 = Geom(np.ones(g,'i'),s).add_primitive(.3,center,1,fill,Rotation.from_random(),inverse,periodic=periodic) assert geom_equal(G_1,G_2) @@ -364,11 +390,33 @@ class TestGeom: geom = Geom.from_minimal_surface(grid,np.ones(3),surface,threshold) assert np.isclose(np.count_nonzero(geom.material==1)/np.prod(geom.grid),.5,rtol=1e-3) - @pytest.mark.parametrize('periodic',[True,False]) - @pytest.mark.parametrize('direction',['x','y','z']) - def test_get_grain_boundaries(self,periodic,direction,reference_dir): - geom=Geom.load(reference_dir/'get_grain_boundaries_4g12x15x20.vtr') - current=geom.get_grain_boundaries(periodic,direction) - reference=VTK.load(reference_dir/f'get_grain_boundaries_4g12x15x20_{direction}_per{periodic}.vtu') - assert current == reference + def test_from_table(self): + grid = np.random.randint(60,100,3) + size = np.ones(3)+np.random.rand(3) + coords = grid_filters.cell_coord0(grid,size).reshape(-1,3,order='F') + z=np.ones(grid.prod()) + z[grid[:2].prod()*int(grid[2]/2):]=0 + t = Table(np.column_stack((coords,z)),{'coords':3,'z':1}) + g = Geom.from_table(t,'coords',['1_coords','z']) + assert g.N_materials == g.grid[0]*2 and (g.material[:,:,-1]-g.material[:,:,0] == grid[0]).all() + + + def test_from_table_recover(self,tmp_path): + grid = np.random.randint(60,100,3) + size = np.ones(3)+np.random.rand(3) + s = seeds.from_random(size,np.random.randint(60,100)) + geom = Geom.from_Voronoi_tessellation(grid,size,s) + coords = grid_filters.cell_coord0(grid,size) + t = Table(np.column_stack((coords.reshape(-1,3,order='F'),geom.material.flatten(order='F'))),{'c':3,'m':1}) + assert geom_equal(geom.sort().renumber(),Geom.from_table(t,'c',['m'])) + + @pytest.mark.parametrize('periodic',[True,False]) + @pytest.mark.parametrize('direction',['x','y','z',['x','y'],'zy','xz',['x','y','z']]) + def test_get_grain_boundaries(self,update,ref_path,periodic,direction): + geom=Geom.load(ref_path/'get_grain_boundaries_8g12x15x20.vtr') + current=geom.get_grain_boundaries(periodic,direction) + if update: + current.save(ref_path/f'get_grain_boundaries_8g12x15x20_{direction}_{periodic}.vtu',parallel=False) + reference=VTK.load(ref_path/f'get_grain_boundaries_8g12x15x20_{"".join(direction)}_{periodic}.vtu') + assert current.__repr__() == reference.__repr__() diff --git a/python/tests/test_Lattice.py b/python/tests/test_Lattice.py deleted file mode 100644 index 93fc56445..000000000 --- a/python/tests/test_Lattice.py +++ /dev/null @@ -1,157 +0,0 @@ -import random - -import pytest -import numpy as np - -from damask import Rotation -from damask import Symmetry - -def in_FZ(system,rho): - """Non-vectorized version of 'in_FZ'.""" - rho_abs = abs(rho) - - if system == 'cubic': - return np.sqrt(2.0)-1.0 >= rho_abs[0] \ - and np.sqrt(2.0)-1.0 >= rho_abs[1] \ - and np.sqrt(2.0)-1.0 >= rho_abs[2] \ - and 1.0 >= rho_abs[0] + rho_abs[1] + rho_abs[2] - elif system == 'hexagonal': - return 1.0 >= rho_abs[0] and 1.0 >= rho_abs[1] and 1.0 >= rho_abs[2] \ - and 2.0 >= np.sqrt(3)*rho_abs[0] + rho_abs[1] \ - and 2.0 >= np.sqrt(3)*rho_abs[1] + rho_abs[0] \ - and 2.0 >= np.sqrt(3) + rho_abs[2] - elif system == 'tetragonal': - return 1.0 >= rho_abs[0] and 1.0 >= rho_abs[1] \ - and np.sqrt(2.0) >= rho_abs[0] + rho_abs[1] \ - and np.sqrt(2.0) >= rho_abs[2] + 1.0 - elif system == 'orthorhombic': - return 1.0 >= rho_abs[0] and 1.0 >= rho_abs[1] and 1.0 >= rho_abs[2] - else: - return np.all(np.isfinite(rho_abs)) - - -def in_disorientation_SST(system,rho): - """Non-vectorized version of 'in_Disorientation_SST'.""" - epsilon = 0.0 - if system == 'cubic': - return rho[0] >= rho[1]+epsilon and rho[1] >= rho[2]+epsilon and rho[2] >= epsilon - elif system == 'hexagonal': - return rho[0] >= np.sqrt(3)*(rho[1]-epsilon) and rho[1] >= epsilon and rho[2] >= epsilon - elif system == 'tetragonal': - return rho[0] >= rho[1]-epsilon and rho[1] >= epsilon and rho[2] >= epsilon - elif system == 'orthorhombic': - return rho[0] >= epsilon and rho[1] >= epsilon and rho[2] >= epsilon - else: - return True - - -def in_SST(system,vector,proper = False): - """Non-vectorized version of 'in_SST'.""" - if system == 'cubic': - basis = {'improper':np.array([ [-1. , 0. , 1. ], - [ np.sqrt(2.) , -np.sqrt(2.) , 0. ], - [ 0. , np.sqrt(3.) , 0. ] ]), - 'proper':np.array([ [ 0. , -1. , 1. ], - [-np.sqrt(2.) , np.sqrt(2.) , 0. ], - [ np.sqrt(3.) , 0. , 0. ] ]), - } - elif system == 'hexagonal': - basis = {'improper':np.array([ [ 0. , 0. , 1. ], - [ 1. , -np.sqrt(3.) , 0. ], - [ 0. , 2. , 0. ] ]), - 'proper':np.array([ [ 0. , 0. , 1. ], - [-1. , np.sqrt(3.) , 0. ], - [ np.sqrt(3.) , -1. , 0. ] ]), - } - elif system == 'tetragonal': - basis = {'improper':np.array([ [ 0. , 0. , 1. ], - [ 1. , -1. , 0. ], - [ 0. , np.sqrt(2.) , 0. ] ]), - 'proper':np.array([ [ 0. , 0. , 1. ], - [-1. , 1. , 0. ], - [ np.sqrt(2.) , 0. , 0. ] ]), - } - elif system == 'orthorhombic': - basis = {'improper':np.array([ [ 0., 0., 1.], - [ 1., 0., 0.], - [ 0., 1., 0.] ]), - 'proper':np.array([ [ 0., 0., 1.], - [-1., 0., 0.], - [ 0., 1., 0.] ]), - } - else: - return True - - v = np.array(vector,dtype=float) - if proper: - theComponents = np.around(np.dot(basis['improper'],v),12) - inSST = np.all(theComponents >= 0.0) - if not inSST: - theComponents = np.around(np.dot(basis['proper'],v),12) - inSST = np.all(theComponents >= 0.0) - else: - v[2] = abs(v[2]) - theComponents = np.around(np.dot(basis['improper'],v),12) - inSST = np.all(theComponents >= 0.0) - - return inSST - - -@pytest.fixture -def set_of_rodrigues(set_of_quaternions): - return Rotation(set_of_quaternions).as_Rodrigues(vector=True)[:200] - -class TestSymmetry: - - @pytest.mark.parametrize('system',Symmetry.crystal_systems) - def test_in_FZ_vectorize(self,set_of_rodrigues,system): - result = Symmetry(system).in_FZ(set_of_rodrigues.reshape(50,4,3)).reshape(200) - for i,r in enumerate(result): - assert r == in_FZ(system,set_of_rodrigues[i]) - - @pytest.mark.parametrize('system',Symmetry.crystal_systems) - def test_in_disorientation_SST_vectorize(self,set_of_rodrigues,system): - result = Symmetry(system).in_disorientation_SST(set_of_rodrigues.reshape(50,4,3)).reshape(200) - for i,r in enumerate(result): - assert r == in_disorientation_SST(system,set_of_rodrigues[i]) - - @pytest.mark.parametrize('proper',[True,False]) - @pytest.mark.parametrize('system',Symmetry.crystal_systems) - def test_in_SST_vectorize(self,system,proper): - vecs = np.random.rand(20,4,3) - result = Symmetry(system).in_SST(vecs,proper).reshape(20*4) - for i,r in enumerate(result): - assert r == in_SST(system,vecs.reshape(20*4,3)[i],proper) - - @pytest.mark.parametrize('invalid_symmetry',['fcc','bcc','hello']) - def test_invalid_symmetry(self,invalid_symmetry): - with pytest.raises(KeyError): - s = Symmetry(invalid_symmetry) # noqa - - def test_equal(self): - symmetry = random.choice(Symmetry.crystal_systems) - print(symmetry) - assert Symmetry(symmetry) == Symmetry(symmetry) - - def test_not_equal(self): - symmetries = random.sample(Symmetry.crystal_systems,k=2) - assert Symmetry(symmetries[0]) != Symmetry(symmetries[1]) - - @pytest.mark.parametrize('system',Symmetry.crystal_systems) - def test_in_FZ(self,system): - assert Symmetry(system).in_FZ(np.zeros(3)) - - @pytest.mark.parametrize('system',Symmetry.crystal_systems) - def test_in_disorientation_SST(self,system): - assert Symmetry(system).in_disorientation_SST(np.zeros(3)) - - @pytest.mark.parametrize('system',Symmetry.crystal_systems) - @pytest.mark.parametrize('proper',[True,False]) - def test_in_SST(self,system,proper): - assert Symmetry(system).in_SST(np.zeros(3),proper) - - @pytest.mark.parametrize('function',['in_FZ','in_disorientation_SST','in_SST']) - def test_invalid_argument(self,function): - s = Symmetry() # noqa - with pytest.raises(ValueError): - eval(f's.{function}(np.ones(4))') diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 669f73e91..5ab0361a8 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -1,131 +1,536 @@ -import os -from itertools import permutations - import pytest import numpy as np +from itertools import permutations -from damask import Table from damask import Rotation from damask import Orientation -from damask import Lattice - -n = 1000 - -def IPF_color(orientation,direction): - """TSL color of inverse pole figure for given axis (non-vectorized).""" - for o in orientation.equivalent: - pole = o.rotation@direction - inSST,color = orientation.lattice.in_SST(pole,color=True) - if inSST: break - - return color - -def inverse_pole(orientation,axis,proper=False,SST=True): - if SST: - for eq in orientation.equivalent: - pole = eq.rotation @ axis/np.linalg.norm(axis) - if orientation.lattice.in_SST(pole,proper=proper): - return pole - else: - return orientation.rotation @ axis/np.linalg.norm(axis) +from damask import Table +from damask import lattice +from damask import util @pytest.fixture -def reference_dir(reference_dir_base): +def ref_path(ref_path_base): """Directory containing reference results.""" - return reference_dir_base/'Rotation' + return ref_path_base/'Orientation' + +@pytest.fixture +def set_of_rodrigues(set_of_quaternions): + return Rotation(set_of_quaternions).as_Rodrigues_vector() class TestOrientation: - @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch']) - @pytest.mark.parametrize('lattice',['fcc','bcc']) - def test_relationship_vectorize(self,set_of_quaternions,lattice,model): - result = Orientation(set_of_quaternions[:200].reshape(50,4,4),lattice).related(model) - ref_qu = result.rotation.quaternion.reshape(-1,200,4) - for i in range(200): - single = Orientation(set_of_quaternions[i],lattice).related(model).rotation.quaternion - assert np.allclose(ref_qu[:,i,:],single) + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('shape',[None,5,(4,6)]) + def test_equal(self,lattice,shape): + R = Rotation.from_random(shape) + assert Orientation(R,lattice) == Orientation(R,lattice) - @pytest.mark.parametrize('lattice',Lattice.lattices) - def test_IPF_vectorize(self,set_of_quaternions,lattice): - direction = np.random.random(3)*2.0-1 - oris = Orientation(Rotation(set_of_quaternions),lattice)[:200] - for i,color in enumerate(oris.IPF_color(direction)): - assert np.allclose(color,IPF_color(oris[i],direction)) + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('shape',[None,5,(4,6)]) + def test_unequal(self,lattice,shape): + R = Rotation.from_random(shape) + assert not(Orientation(R,lattice) != Orientation(R,lattice)) - @pytest.mark.parametrize('SST',[False,True]) + @pytest.mark.parametrize('a,b',[ + (dict(rotation=[1,0,0,0]), + dict(rotation=[0.5,0.5,0.5,0.5])), + + (dict(rotation=[1,0,0,0],lattice='cubic'), + dict(rotation=[1,0,0,0],lattice='hexagonal')), + + (dict(rotation=[1,0,0,0],lattice='cF',a=1), + dict(rotation=[1,0,0,0],lattice='cF',a=2)), + ]) + def test_nonequal(self,a,b): + assert Orientation(**a) != Orientation(**b) + + @pytest.mark.parametrize('kwargs',[ + dict(lattice='aP', alpha=np.pi/4,beta=np.pi/3, ), + dict(lattice='mP', c=1.2,alpha=np.pi/4, gamma=np.pi/2), + dict(lattice='oP', c=1.2,alpha=np.pi/4, ), + dict(lattice='oS',a=1.0, c=2.0,alpha=np.pi/2,beta=np.pi/3, ), + dict(lattice='tP',a=1.0,b=1.2, ), + dict(lattice='tI', alpha=np.pi/3, ), + dict(lattice='hP', gamma=np.pi/2), + dict(lattice='cI',a=1.0, c=2.0,alpha=np.pi/2,beta=np.pi/2, ), + dict(lattice='cF', beta=np.pi/3, ), + ]) + def test_invalid_init(self,kwargs): + with pytest.raises(ValueError): + Orientation(**kwargs).parameters # noqa + + @pytest.mark.parametrize('kwargs',[ + dict(lattice='aP',a=1.0,b=1.1,c=1.2,alpha=np.pi/4,beta=np.pi/3,gamma=np.pi/2), + dict(lattice='mP',a=1.0,b=1.1,c=1.2, beta=np.pi/3 ), + dict(lattice='oS',a=1.0,b=1.1,c=1.2, ), + dict(lattice='tI',a=1.0, c=1.2, ), + dict(lattice='hP',a=1.0 ), + dict(lattice='cI',a=1.0, ), + ]) + def test_repr(self,kwargs): + o = Orientation.from_random(**kwargs) + assert isinstance(o.__repr__(),str) + + @pytest.mark.parametrize('kwargs',[ + dict(lattice='aP',a=1.0,b=1.1,c=1.2,alpha=np.pi/4,beta=np.pi/3,gamma=np.pi/2), + dict(lattice='mP',a=1.0,b=1.1,c=1.2, beta=np.pi/3 ), + dict(lattice='oS',a=1.0,b=1.1,c=1.2, ), + dict(lattice='tI',a=1.0, c=1.2, ), + dict(lattice='hP',a=1.0 ), + dict(lattice='cI',a=1.0, ), + ]) + def test_copy(self,kwargs): + o = Orientation.from_random(**kwargs) + p = o.copy(rotation=Rotation.from_random()) + assert o != p + + def test_from_quaternion(self): + assert np.all(Orientation.from_quaternion(q=np.array([1,0,0,0]),lattice='triclinic').as_matrix() + == np.eye(3)) + + def test_from_Euler_angles(self): + assert np.all(Orientation.from_Euler_angles(phi=np.zeros(3),lattice='triclinic').as_matrix() + == np.eye(3)) + + def test_from_axis_angle(self): + assert np.all(Orientation.from_axis_angle(axis_angle=[1,0,0,0],lattice='triclinic').as_matrix() + == np.eye(3)) + + def test_from_basis(self): + assert np.all(Orientation.from_basis(basis=np.eye(3),lattice='triclinic').as_matrix() + == np.eye(3)) + + def test_from_matrix(self): + assert np.all(Orientation.from_matrix(R=np.eye(3),lattice='triclinic').as_matrix() + == np.eye(3)) + + def test_from_Rodrigues_vector(self): + assert np.all(Orientation.from_Rodrigues_vector(rho=np.array([0,0,1,0]),lattice='triclinic').as_matrix() + == np.eye(3)) + + def test_from_homochoric(self): + assert np.all(Orientation.from_homochoric(h=np.zeros(3),lattice='triclinic').as_matrix() + == np.eye(3)) + + def test_from_cubochoric(self): + assert np.all(Orientation.from_cubochoric(c=np.zeros(3),lattice='triclinic').as_matrix() + == np.eye(3)) + + def test_from_spherical_component(self): + assert np.all(Orientation.from_spherical_component(center=Rotation(), + sigma=0.0,N=1,lattice='triclinic').as_matrix() + == np.eye(3)) + + def test_from_fiber_component(self): + r = Rotation.from_fiber_component(alpha=np.zeros(2),beta=np.zeros(2), + sigma=0.0,N=1,rng_seed=0) + assert np.all(Orientation.from_fiber_component(alpha=np.zeros(2),beta=np.zeros(2), + sigma=0.0,N=1,rng_seed=0,lattice='triclinic').quaternion + == r.quaternion) + + @pytest.mark.parametrize('kwargs',[ + dict(lattice='aP',a=1.0,b=1.1,c=1.2,alpha=np.pi/4.5,beta=np.pi/3.5,gamma=np.pi/2.5), + dict(lattice='mP',a=1.0,b=1.1,c=1.2, beta=np.pi/3.5), + dict(lattice='oS',a=1.0,b=1.1,c=1.2,), + dict(lattice='tI',a=1.0, c=1.2,), + dict(lattice='hP',a=1.0 ), + dict(lattice='cI',a=1.0, ), + ]) + def test_from_direction(self,kwargs): + for a,b in np.random.random((10,2,3)): + c = np.cross(b,a) + if np.all(np.isclose(c,0)): continue + o = Orientation.from_directions(uvw=a,hkl=c,**kwargs) + x = o.to_pole(uvw=a) + z = o.to_pole(hkl=c) + assert np.isclose(np.dot(x/np.linalg.norm(x),np.array([1,0,0])),1) \ + and np.isclose(np.dot(z/np.linalg.norm(z),np.array([0,0,1])),1) + + + def test_negative_angle(self): + with pytest.raises(ValueError): + Orientation(lattice='aP',a=1,b=2,c=3,alpha=45,beta=45,gamma=-45,degrees=True) # noqa + + def test_excess_angle(self): + with pytest.raises(ValueError): + Orientation(lattice='aP',a=1,b=2,c=3,alpha=45,beta=45,gamma=90.0001,degrees=True) # noqa + + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('angle',[10,20,30,40]) + def test_average(self,angle,lattice): + o = Orientation.from_axis_angle(lattice=lattice,axis_angle=[[0,0,1,10],[0,0,1,angle]],degrees=True) + avg_angle = o.average().as_axis_angle(degrees=True,pair=True)[1] + assert np.isclose(avg_angle,10+(angle-10)/2.) + + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + def test_reduced_equivalent(self,lattice): + i = Orientation(lattice=lattice) + o = Orientation.from_random(lattice=lattice) + eq = o.equivalent + FZ = np.argmin(abs(eq.misorientation(i.broadcast_to(len(eq))).as_axis_angle(pair=True)[1])) + assert o.reduced == eq[FZ] + + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('N',[1,8,32]) + def test_disorientation(self,lattice,N): + o = Orientation.from_random(lattice=lattice,shape=N) + p = Orientation.from_random(lattice=lattice,shape=N) + + d,ops = o.disorientation(p,return_operators=True) + + for n in range(N): + assert np.allclose(d[n].as_quaternion(), + o[n].equivalent[ops[n][0]] + .misorientation(p[n].equivalent[ops[n][1]]) + .as_quaternion()) \ + or np.allclose((~d)[n].as_quaternion(), + o[n].equivalent[ops[n][0]] + .misorientation(p[n].equivalent[ops[n][1]]) + .as_quaternion()) + + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('a,b',[ + ((2,3,2),(2,3,2)), + ((2,2),(4,4)), + ((3,1),(1,3)), + (None,None), + ]) + def test_disorientation_blending(self,lattice,a,b): + o = Orientation.from_random(lattice=lattice,shape=a) + p = Orientation.from_random(lattice=lattice,shape=b) + blend = util.shapeblender(o.shape,p.shape) + for loc in np.random.randint(0,blend,(10,len(blend))): + assert o[tuple(loc[:len(o.shape)])].disorientation(p[tuple(loc[-len(p.shape):])]) \ + == o.disorientation(p)[tuple(loc)] + + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + def test_disorientation360(self,lattice): + o_1 = Orientation(Rotation(),lattice) + o_2 = Orientation.from_Euler_angles(lattice=lattice,phi=[360,0,0],degrees=True) + assert np.allclose((o_1.disorientation(o_2)).as_matrix(),np.eye(3)) + + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) + def test_reduced_vectorization(self,lattice,shape): + o = Orientation.from_random(lattice=lattice,shape=shape) + for r, theO in zip(o.reduced.flatten(),o.flatten()): + assert r == theO.reduced + + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) + @pytest.mark.parametrize('vector',np.array([[1,0,0],[1,2,3],[-1,1,-1]])) @pytest.mark.parametrize('proper',[True,False]) - @pytest.mark.parametrize('lattice',Lattice.lattices) - def test_inverse_pole_vectorize(self,set_of_quaternions,lattice,SST,proper): - axis = np.random.random(3)*2.0-1 - oris = Orientation(Rotation(set_of_quaternions),lattice)[:200] - for i,pole in enumerate(oris.inverse_pole(axis,SST=SST)): - assert np.allclose(pole,inverse_pole(oris[i],axis,SST=SST)) + def test_to_SST_vectorization(self,lattice,shape,vector,proper): + o = Orientation.from_random(lattice=lattice,shape=shape) + for r, theO in zip(o.to_SST(vector=vector,proper=proper).reshape((-1,3)),o.flatten()): + assert np.allclose(r,theO.to_SST(vector=vector,proper=proper)) + + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) + @pytest.mark.parametrize('vector',np.array([[1,0,0],[1,2,3],[-1,1,-1]])) + @pytest.mark.parametrize('proper',[True,False]) + @pytest.mark.parametrize('in_SST',[True,False]) + def test_IPF_color_vectorization(self,lattice,shape,vector,proper,in_SST): + o = Orientation.from_random(lattice=lattice,shape=shape) + for r, theO in zip(o.IPF_color(vector,in_SST=in_SST,proper=proper).reshape((-1,3)),o.flatten()): + assert np.allclose(r,theO.IPF_color(vector,in_SST=in_SST,proper=proper)) + + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('a,b',[ + ((2,3,2),(2,3,2)), + ((2,2),(4,4)), + ((3,1),(1,3)), + (None,(3,)), + ]) + def test_to_SST_blending(self,lattice,a,b): + o = Orientation.from_random(lattice=lattice,shape=a) + v = np.random.random(b+(3,)) + blend = util.shapeblender(o.shape,b) + for loc in np.random.randint(0,blend,(10,len(blend))): + print(f'{a}/{b} @ {loc}') + print(o[tuple(loc[:len(o.shape)])].to_SST(v[tuple(loc[-len(b):])])) + print(o.to_SST(v)[tuple(loc)]) + assert np.allclose(o[tuple(loc[:len(o.shape)])].to_SST(v[tuple(loc[-len(b):])]), + o.to_SST(v)[tuple(loc)]) @pytest.mark.parametrize('color',[{'label':'red', 'RGB':[1,0,0],'direction':[0,0,1]}, {'label':'green','RGB':[0,1,0],'direction':[0,1,1]}, {'label':'blue', 'RGB':[0,0,1],'direction':[1,1,1]}]) - @pytest.mark.parametrize('lattice',['fcc','bcc']) - def test_IPF_cubic(self,color,lattice): - cube = Orientation(Rotation(),lattice) + @pytest.mark.parametrize('proper',[True,False]) + def test_IPF_cubic(self,color,proper): + cube = Orientation(lattice='cubic') for direction in set(permutations(np.array(color['direction']))): - assert np.allclose(cube.IPF_color(np.array(direction)),np.array(color['RGB'])) + assert np.allclose(np.array(color['RGB']), + cube.IPF_color(vector=np.array(direction),proper=proper)) - @pytest.mark.parametrize('lattice',Lattice.lattices) - def test_IPF_equivalent(self,set_of_quaternions,lattice): - direction = np.random.random(3)*2.0-1 - for ori in Orientation(Rotation(set_of_quaternions),lattice)[:200]: - color = ori.IPF_color(direction) - for equivalent in ori.equivalent: - assert np.allclose(color,equivalent.IPF_color(direction)) + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('proper',[True,False]) + def test_IPF_equivalent(self,set_of_quaternions,lattice,proper): + direction = np.random.random(3)*2.0-1.0 + o = Orientation(rotation=set_of_quaternions,lattice=lattice).equivalent + color = o.IPF_color(vector=direction,proper=proper) + assert np.allclose(np.broadcast_to(color[0,...],color.shape),color) - @pytest.mark.parametrize('lattice',Lattice.lattices) - def test_reduced(self,set_of_quaternions,lattice): - oris = Orientation(Rotation(set_of_quaternions),lattice) - reduced = oris.reduced - assert np.all(reduced.in_FZ) and oris.rotation.shape == reduced.rotation.shape + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + def test_in_FZ_vectorization(self,set_of_rodrigues,lattice): + result = Orientation.from_Rodrigues_vector(rho=set_of_rodrigues.reshape((-1,4,4)),lattice=lattice).in_FZ.reshape(-1) + for r,rho in zip(result,set_of_rodrigues[:len(result)]): + assert r == Orientation.from_Rodrigues_vector(rho=rho,lattice=lattice).in_FZ + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + def test_in_disorientation_FZ_vectorization(self,set_of_rodrigues,lattice): + result = Orientation.from_Rodrigues_vector(rho=set_of_rodrigues.reshape((-1,4,4)), + lattice=lattice).in_disorientation_FZ.reshape(-1) + for r,rho in zip(result,set_of_rodrigues[:len(result)]): + assert r == Orientation.from_Rodrigues_vector(rho=rho,lattice=lattice).in_disorientation_FZ + + @pytest.mark.parametrize('proper',[True,False]) + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + def test_in_SST_vectorization(self,lattice,proper): + vecs = np.random.rand(20,4,3) + result = Orientation(lattice=lattice).in_SST(vecs,proper).flatten() + for r,v in zip(result,vecs.reshape((-1,3))): + assert np.all(r == Orientation(lattice=lattice).in_SST(v,proper)) + + @pytest.mark.parametrize('invalid_lattice',['fcc','bcc','hello']) + def test_invalid_lattice_init(self,invalid_lattice): + with pytest.raises(KeyError): + Orientation(lattice=invalid_lattice) # noqa + + @pytest.mark.parametrize('invalid_family',[None,'fcc','bcc','hello']) + def test_invalid_symmetry_family(self,invalid_family): + with pytest.raises(KeyError): + o = Orientation(lattice='cubic') + o.family = invalid_family + o.symmetry_operations # noqa + + def test_missing_symmetry_equivalent(self): + with pytest.raises(ValueError): + Orientation(lattice=None).equivalent # noqa + + def test_missing_symmetry_reduced(self): + with pytest.raises(ValueError): + Orientation(lattice=None).reduced # noqa + + def test_missing_symmetry_in_FZ(self): + with pytest.raises(ValueError): + Orientation(lattice=None).in_FZ # noqa + + def test_missing_symmetry_in_disorientation_FZ(self): + with pytest.raises(ValueError): + Orientation(lattice=None).in_disorientation_FZ # noqa + + def test_missing_symmetry_disorientation(self): + with pytest.raises(ValueError): + Orientation(lattice=None).disorientation(Orientation(lattice=None)) # noqa + + def test_missing_symmetry_average(self): + with pytest.raises(ValueError): + Orientation(lattice=None).average() # noqa + + def test_missing_symmetry_to_SST(self): + with pytest.raises(ValueError): + Orientation(lattice=None).to_SST(np.zeros(3)) # noqa + + def test_missing_symmetry_immutable(self): + with pytest.raises(KeyError): + Orientation(lattice=None).immutable # noqa + + def test_missing_symmetry_basis_real(self): + with pytest.raises(KeyError): + Orientation(lattice=None).basis_real # noqa + + def test_missing_symmetry_basis_reciprocal(self): + with pytest.raises(KeyError): + Orientation(lattice=None).basis_reciprocal # noqa + + def test_double_Bravais_to_Miller(self): + with pytest.raises(KeyError): + Orientation.Bravais_to_Miller(uvtw=np.ones(4),hkil=np.ones(4)) # noqa + + def test_double_Miller_to_Bravais(self): + with pytest.raises(KeyError): + Orientation.Miller_to_Bravais(uvw=np.ones(4),hkl=np.ones(4)) # noqa + + def test_double_to_lattice(self): + with pytest.raises(KeyError): + Orientation().to_lattice(direction=np.ones(3),plane=np.ones(3)) # noqa + + def test_double_to_frame(self): + with pytest.raises(KeyError): + Orientation().to_frame(uvw=np.ones(3),hkl=np.ones(3)) # noqa + + @pytest.mark.parametrize('relation',[None,'Peter','Paul']) + def test_unknown_relation(self,relation): + with pytest.raises(KeyError): + Orientation(lattice='cF').related(relation) # noqa + + @pytest.mark.parametrize('relation,lattice,a,b,c,alpha,beta,gamma', + [ + ('Bain', 'aP',0.5,2.0,3.0,0.8,0.5,1.2), + ('KS', 'mP',1.0,2.0,3.0,np.pi/2,0.5,np.pi/2), + ('Pitsch', 'oI',0.5,1.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('Burgers','tP',0.5,0.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('GT', 'hP',1.0,None,1.6,np.pi/2,np.pi/2,2*np.pi/3), + ('Burgers','cF',1.0,1.0,None,np.pi/2,np.pi/2,np.pi/2), + ]) + def test_unknown_relation_lattice(self,relation,lattice,a,b,c,alpha,beta,gamma): + with pytest.raises(KeyError): + Orientation(lattice=lattice, + a=a,b=b,c=c, + alpha=alpha,beta=beta,gamma=gamma).related(relation) # noqa + + @pytest.mark.parametrize('lattice',Orientation.crystal_families) + @pytest.mark.parametrize('proper',[True,False]) + def test_in_SST(self,lattice,proper): + assert Orientation(lattice=lattice).in_SST(np.zeros(3),proper) + + @pytest.mark.parametrize('function',['in_SST','IPF_color']) + def test_invalid_argument(self,function): + o = Orientation(lattice='cubic') # noqa + with pytest.raises(ValueError): + eval(f'o.{function}(np.ones(4))') + + @pytest.mark.parametrize('model',lattice.relations) + def test_relationship_definition(self,model): + m,o = list(lattice.relations[model]) + assert lattice.relations[model][m].shape[:-1] == lattice.relations[model][o].shape[:-1] @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch']) - @pytest.mark.parametrize('lattice',['fcc','bcc']) + @pytest.mark.parametrize('lattice',['cF','cI']) + def test_relationship_vectorize(self,set_of_quaternions,lattice,model): + r = Orientation(rotation=set_of_quaternions[:200].reshape((50,4,4)),lattice=lattice).related(model) + for i in range(200): + assert r.reshape((-1,200))[:,i] == Orientation(set_of_quaternions[i],lattice).related(model) + + @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch']) + @pytest.mark.parametrize('lattice',['cF','cI']) def test_relationship_forward_backward(self,model,lattice): - ori = Orientation(Rotation.from_random(),lattice) - for i,r in enumerate(ori.related(model)): - ori2 = r.related(model)[i] - misorientation = ori.rotation.misorientation(ori2.rotation) - assert misorientation.as_axis_angle(degrees=True)[3]<1.0e-5 + o = Orientation.from_random(lattice=lattice) + for i,r in enumerate(o.related(model)): + assert o.disorientation(r.related(model)[i]).as_axis_angle(degrees=True,pair=True)[1]<1.0e-5 @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch']) - @pytest.mark.parametrize('lattice',['fcc','bcc']) - def test_relationship_reference(self,update,reference_dir,model,lattice): - reference = os.path.join(reference_dir,f'{lattice}_{model}.txt') - ori = Orientation(Rotation(),lattice) - eu = np.array([o.rotation.as_Eulers(degrees=True) for o in ori.related(model)]) + @pytest.mark.parametrize('lattice',['cF','cI']) + def test_relationship_reference(self,update,ref_path,model,lattice): + reference = ref_path/f'{lattice}_{model}.txt' + o = Orientation(lattice=lattice) + eu = o.related(model).as_Euler_angles(degrees=True) if update: coords = np.array([(1,i+1) for i,x in enumerate(eu)]) - table = Table(eu,{'Eulers':(3,)}) - table = table.add('pos',coords) - table.save(reference) + Table(eu,{'Eulers':(3,)})\ + .add('pos',coords)\ + .save(reference) assert np.allclose(eu,Table.load(reference).get('Eulers')) - @pytest.mark.parametrize('lattice',Lattice.lattices) - def test_disorientation360(self,lattice): - R_1 = Orientation(Rotation(),lattice) - R_2 = Orientation(Rotation.from_Eulers([360,0,0],degrees=True),lattice) - assert np.allclose(R_1.disorientation(R_2).as_matrix(),np.eye(3)) + def test_basis_real(self): + for gamma in np.random.random(2**8)*np.pi: + basis = np.tril(np.random.random((3,3))+1e-6) + basis[1,:2] = basis[1,1]*np.array([np.cos(gamma),np.sin(gamma)]) + basis[2,:2] = basis[2,:2]*2-1 + lengths = np.linalg.norm(basis,axis=-1) + cosines = np.roll(np.einsum('ij,ij->i',basis,np.roll(basis,1,axis=0))/lengths/np.roll(lengths,1),1) + o = Orientation.from_random(lattice='aP', + **dict(zip(['a','b','c'],lengths)), + **dict(zip(['alpha','beta','gamma'],np.arccos(cosines))), + ) + assert np.allclose(o.to_frame(uvw=np.eye(3)),basis), 'Lattice basis disagrees with initialization' - @pytest.mark.parametrize('lattice',Lattice.lattices) - @pytest.mark.parametrize('angle',[10,20,30,40]) - def test_average(self,angle,lattice): - R_1 = Orientation(Rotation.from_axis_angle([0,0,1,10],degrees=True),lattice) - R_2 = Orientation(Rotation.from_axis_angle([0,0,1,angle],degrees=True),lattice) - avg_angle = R_1.average(R_2).rotation.as_axis_angle(degrees=True,pair=True)[1] - assert np.isclose(avg_angle,10+(angle-10)/2.) + @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', + [ + ('aP',0.5,2.0,3.0,0.8,0.5,1.2), + ('mP',1.0,2.0,3.0,np.pi/2,0.5,np.pi/2), + ('oI',0.5,1.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('tP',0.5,0.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('hP',1.0,None,1.6,np.pi/2,np.pi/2,2*np.pi/3), + ('cF',1.0,1.0,None,np.pi/2,np.pi/2,np.pi/2), + ]) + def test_bases_contraction(self,lattice,a,b,c,alpha,beta,gamma): + L = Orientation(lattice=lattice, + a=a,b=b,c=c, + alpha=alpha,beta=beta,gamma=gamma) + assert np.allclose(np.eye(3),np.einsum('ik,jk',L.basis_real,L.basis_reciprocal)) - @pytest.mark.parametrize('lattice',Lattice.lattices) - def test_from_average(self,lattice): - R_1 = Orientation(Rotation.from_random(),lattice) - eqs = [r for r in R_1.equivalent] - R_2 = Orientation.from_average(eqs) - assert np.allclose(R_1.rotation.quaternion,R_2.rotation.quaternion) + @pytest.mark.parametrize('keyFrame,keyLattice',[('uvw','direction'),('hkl','plane'),]) + @pytest.mark.parametrize('vector',np.array([ + [1.,1.,1.], + [-2.,3.,0.5], + [0.,0.,1.], + [1.,1.,1.], + [2.,2.,2.], + [0.,1.,1.], + ])) + @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', + [ + ('aP',0.5,2.0,3.0,0.8,0.5,1.2), + ('mP',1.0,2.0,3.0,np.pi/2,0.5,np.pi/2), + ('oI',0.5,1.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('tP',0.5,0.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('hP',1.0,1.0,1.6,np.pi/2,np.pi/2,2*np.pi/3), + ('cF',1.0,1.0,1.0,np.pi/2,np.pi/2,np.pi/2), + ]) + def test_to_frame_to_lattice(self,lattice,a,b,c,alpha,beta,gamma,vector,keyFrame,keyLattice): + L = Orientation(lattice=lattice, + a=a,b=b,c=c, + alpha=alpha,beta=beta,gamma=gamma) + assert np.allclose(vector, + L.to_frame(**{keyFrame:L.to_lattice(**{keyLattice:vector})})) + + @pytest.mark.parametrize('vector',np.array([ + [1,0,0], + [1,1,0], + [1,1,1], + [1,0,-2], + ])) + @pytest.mark.parametrize('kw_Miller,kw_Bravais',[('uvw','uvtw'),('hkl','hkil')]) + def test_Miller_Bravais_Miller(self,vector,kw_Miller,kw_Bravais): + assert np.all(vector == Orientation.Bravais_to_Miller(**{kw_Bravais:Orientation.Miller_to_Bravais(**{kw_Miller:vector})})) + + @pytest.mark.parametrize('vector',np.array([ + [1,0,-1,2], + [1,-1,0,3], + [1,1,-2,-3], + [0,0,0,1], + ])) + @pytest.mark.parametrize('kw_Miller,kw_Bravais',[('uvw','uvtw'),('hkl','hkil')]) + def test_Bravais_Miller_Bravais(self,vector,kw_Miller,kw_Bravais): + assert np.all(vector == Orientation.Miller_to_Bravais(**{kw_Miller:Orientation.Bravais_to_Miller(**{kw_Bravais:vector})})) + + @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', + [ + ('aP',0.5,2.0,3.0,0.8,0.5,1.2), + ('mP',1.0,2.0,3.0,np.pi/2,0.5,np.pi/2), + ('oI',0.5,1.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('tP',0.5,0.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('hP',1.0,1.0,1.6,np.pi/2,np.pi/2,2*np.pi/3), + ('cF',1.0,1.0,1.0,np.pi/2,np.pi/2,np.pi/2), + ]) + + @pytest.mark.parametrize('kw',['uvw','hkl']) + @pytest.mark.parametrize('with_symmetry',[False,True]) + @pytest.mark.parametrize('shape',[None,1,(12,24)]) + @pytest.mark.parametrize('vector',[ + np.random.random( 3 ), + np.random.random( (4,3)), + np.random.random((4,8,3)), + ]) + def test_to_pole(self,shape,lattice,a,b,c,alpha,beta,gamma,vector,kw,with_symmetry): + o = Orientation.from_random(shape=shape, + lattice=lattice, + a=a,b=b,c=c, + alpha=alpha,beta=beta,gamma=gamma) + assert o.to_pole(**{kw:vector,'with_symmetry':with_symmetry}).shape \ + == o.shape + (o.symmetry_operations.shape if with_symmetry else ()) + vector.shape + + @pytest.mark.parametrize('lattice',['hP','cI','cF']) + def test_Schmid(self,update,ref_path,lattice): + L = Orientation(lattice=lattice) + for mode in L.kinematics: + reference = ref_path/f'{lattice}_{mode}.txt' + P = L.Schmid(mode) + if update: + table = Table(P.reshape(-1,9),{'Schmid':(3,3,)}) + table.save(reference) + assert np.allclose(P,Table.load(reference).get('Schmid')) diff --git a/python/tests/test_Result.py b/python/tests/test_Result.py index 68b72badf..5723f4ffb 100644 --- a/python/tests/test_Result.py +++ b/python/tests/test_Result.py @@ -11,29 +11,30 @@ import h5py from damask import Result from damask import Rotation from damask import Orientation +from damask import tensor from damask import mechanics from damask import grid_filters @pytest.fixture -def default(tmp_path,reference_dir): +def default(tmp_path,ref_path): """Small Result file in temp location for modification.""" fname = '12grains6x7x8_tensionY.hdf5' - shutil.copy(reference_dir/fname,tmp_path) + shutil.copy(ref_path/fname,tmp_path) f = Result(tmp_path/fname) f.pick('times',20.0) return f @pytest.fixture -def single_phase(tmp_path,reference_dir): +def single_phase(tmp_path,ref_path): """Single phase Result file in temp location for modification.""" fname = '6grains6x7x8_single_phase_tensionY.hdf5' - shutil.copy(reference_dir/fname,tmp_path) + shutil.copy(ref_path/fname,tmp_path) return Result(tmp_path/fname) @pytest.fixture -def reference_dir(reference_dir_base): +def ref_path(ref_path_base): """Directory containing reference results.""" - return reference_dir_base/'Result' + return ref_path_base/'Result' class TestResult: @@ -58,7 +59,7 @@ class TestResult: f = default.get_dataset_location('F') assert a == b == c == d == e ==f - @pytest.mark.parametrize('what',['increments','times','constituents']) # ToDo: discuss materialpoints + @pytest.mark.parametrize('what',['increments','times','phases']) # ToDo: discuss homogenizations def test_pick_none(self,default,what): default.pick(what,False) a = default.get_dataset_location('F') @@ -67,7 +68,7 @@ class TestResult: assert a == b == [] - @pytest.mark.parametrize('what',['increments','times','constituents']) # ToDo: discuss materialpoints + @pytest.mark.parametrize('what',['increments','times','phases']) # ToDo: discuss homogenizations def test_pick_more(self,default,what): default.pick(what,False) default.pick_more(what,'*') @@ -78,7 +79,7 @@ class TestResult: assert a == b - @pytest.mark.parametrize('what',['increments','times','constituents']) # ToDo: discuss materialpoints + @pytest.mark.parametrize('what',['increments','times','phases']) # ToDo: discuss homogenizations def test_pick_less(self,default,what): default.pick(what,True) default.pick_less(what,'*') @@ -94,11 +95,11 @@ class TestResult: default.pick('invalid',True) def test_add_absolute(self,default): - default.add_absolute('Fe') - loc = {'Fe': default.get_dataset_location('Fe'), - '|Fe|': default.get_dataset_location('|Fe|')} - in_memory = np.abs(default.read_dataset(loc['Fe'],0)) - in_file = default.read_dataset(loc['|Fe|'],0) + default.add_absolute('F_e') + loc = {'F_e': default.get_dataset_location('F_e'), + '|F_e|': default.get_dataset_location('|F_e|')} + in_memory = np.abs(default.read_dataset(loc['F_e'],0)) + in_file = default.read_dataset(loc['|F_e|'],0) assert np.allclose(in_memory,in_file) @pytest.mark.parametrize('mode',['direct','function']) @@ -120,13 +121,13 @@ class TestResult: in_file = default.read_dataset(loc['x'],0) assert np.allclose(in_memory,in_file) - def test_add_Cauchy(self,default): - default.add_Cauchy('P','F') + def test_add_stress_Cauchy(self,default): + default.add_stress_Cauchy('P','F') loc = {'F': default.get_dataset_location('F'), 'P': default.get_dataset_location('P'), 'sigma':default.get_dataset_location('sigma')} - in_memory = mechanics.Cauchy(default.read_dataset(loc['P'],0), - default.read_dataset(loc['F'],0)) + in_memory = mechanics.stress_Cauchy(default.read_dataset(loc['P'],0), + default.read_dataset(loc['F'],0)) in_file = default.read_dataset(loc['sigma'],0) assert np.allclose(in_memory,in_file) @@ -142,46 +143,44 @@ class TestResult: default.add_deviator('P') loc = {'P' :default.get_dataset_location('P'), 's_P':default.get_dataset_location('s_P')} - in_memory = mechanics.deviatoric_part(default.read_dataset(loc['P'],0)) + in_memory = tensor.deviatoric(default.read_dataset(loc['P'],0)) in_file = default.read_dataset(loc['s_P'],0) assert np.allclose(in_memory,in_file) @pytest.mark.parametrize('eigenvalue,function',[('max',np.amax),('min',np.amin)]) def test_add_eigenvalue(self,default,eigenvalue,function): - default.add_Cauchy('P','F') + default.add_stress_Cauchy('P','F') default.add_eigenvalue('sigma',eigenvalue) loc = {'sigma' :default.get_dataset_location('sigma'), 'lambda':default.get_dataset_location(f'lambda_{eigenvalue}(sigma)')} - in_memory = function(mechanics.eigenvalues(default.read_dataset(loc['sigma'],0)),axis=1,keepdims=True) + in_memory = function(tensor.eigenvalues(default.read_dataset(loc['sigma'],0)),axis=1,keepdims=True) in_file = default.read_dataset(loc['lambda'],0) assert np.allclose(in_memory,in_file) @pytest.mark.parametrize('eigenvalue,idx',[('max',2),('mid',1),('min',0)]) def test_add_eigenvector(self,default,eigenvalue,idx): - default.add_Cauchy('P','F') + default.add_stress_Cauchy('P','F') default.add_eigenvector('sigma',eigenvalue) loc = {'sigma' :default.get_dataset_location('sigma'), 'v(sigma)':default.get_dataset_location(f'v_{eigenvalue}(sigma)')} - in_memory = mechanics.eigenvectors(default.read_dataset(loc['sigma'],0))[:,idx] + in_memory = tensor.eigenvectors(default.read_dataset(loc['sigma'],0))[:,idx] in_file = default.read_dataset(loc['v(sigma)'],0) assert np.allclose(in_memory,in_file) @pytest.mark.parametrize('d',[[1,0,0],[0,1,0],[0,0,1]]) def test_add_IPF_color(self,default,d): - default.add_IPF_color('orientation',d) - loc = {'orientation': default.get_dataset_location('orientation'), - 'color': default.get_dataset_location('IPFcolor_[{} {} {}]'.format(*d))} - qu = default.read_dataset(loc['orientation']).view(np.double).reshape(-1,4) - crystal_structure = default.get_crystal_structure() - in_memory = np.empty((qu.shape[0],3),np.uint8) - for i,q in enumerate(qu): - o = Orientation(q,crystal_structure).reduced - in_memory[i] = np.uint8(o.IPF_color(np.array(d))*255) + default.add_IPF_color('O',np.array(d)) + loc = {'O': default.get_dataset_location('O'), + 'color': default.get_dataset_location('IPFcolor_[{} {} {}]'.format(*d))} + qu = default.read_dataset(loc['O']).view(np.double).squeeze() + crystal_structure = default._get_attribute(default.get_dataset_location('O')[0],'Lattice') + c = Orientation(rotation=qu,lattice=crystal_structure) + in_memory = np.uint8(c.IPF_color(np.array(d))*255) in_file = default.read_dataset(loc['color']) assert np.allclose(in_memory,in_file) def test_add_maximum_shear(self,default): - default.add_Cauchy('P','F') + default.add_stress_Cauchy('P','F') default.add_maximum_shear('sigma') loc = {'sigma' :default.get_dataset_location('sigma'), 'max_shear(sigma)':default.get_dataset_location('max_shear(sigma)')} @@ -192,24 +191,40 @@ class TestResult: def test_add_Mises_strain(self,default): t = ['V','U'][np.random.randint(0,2)] m = np.random.random()*2.0 - 1.0 - default.add_strain_tensor('F',t,m) + default.add_strain('F',t,m) label = f'epsilon_{t}^{m}(F)' - default.add_Mises(label) + default.add_equivalent_Mises(label) loc = {label :default.get_dataset_location(label), label+'_vM':default.get_dataset_location(label+'_vM')} - in_memory = mechanics.Mises_strain(default.read_dataset(loc[label],0)).reshape(-1,1) + in_memory = mechanics.equivalent_strain_Mises(default.read_dataset(loc[label],0)).reshape(-1,1) in_file = default.read_dataset(loc[label+'_vM'],0) assert np.allclose(in_memory,in_file) def test_add_Mises_stress(self,default): - default.add_Cauchy('P','F') - default.add_Mises('sigma') + default.add_stress_Cauchy('P','F') + default.add_equivalent_Mises('sigma') loc = {'sigma' :default.get_dataset_location('sigma'), 'sigma_vM':default.get_dataset_location('sigma_vM')} - in_memory = mechanics.Mises_stress(default.read_dataset(loc['sigma'],0)).reshape(-1,1) + in_memory = mechanics.equivalent_stress_Mises(default.read_dataset(loc['sigma'],0)).reshape(-1,1) in_file = default.read_dataset(loc['sigma_vM'],0) assert np.allclose(in_memory,in_file) + def test_add_Mises_invalid(self,default): + default.add_stress_Cauchy('P','F') + default.add_calculation('sigma_y','#sigma#',unit='y') + default.add_equivalent_Mises('sigma_y') + assert default.get_dataset_location('sigma_y_vM') == [] + + def test_add_Mises_stress_strain(self,default): + default.add_stress_Cauchy('P','F') + default.add_calculation('sigma_y','#sigma#',unit='y') + default.add_calculation('sigma_x','#sigma#',unit='x') + default.add_equivalent_Mises('sigma_y',kind='strain') + default.add_equivalent_Mises('sigma_x',kind='stress') + loc = {'y' :default.get_dataset_location('sigma_y_vM'), + 'x' :default.get_dataset_location('sigma_x_vM')} + assert not np.allclose(default.read_dataset(loc['y'],0),default.read_dataset(loc['x'],0)) + def test_add_norm(self,default): default.add_norm('F',1) loc = {'F': default.get_dataset_location('F'), @@ -218,23 +233,24 @@ class TestResult: in_file = default.read_dataset(loc['|F|_1'],0) assert np.allclose(in_memory,in_file) - def test_add_PK2(self,default): - default.add_PK2('P','F') + def test_add_stress_second_Piola_Kirchhoff(self,default): + default.add_stress_second_Piola_Kirchhoff('P','F') loc = {'F':default.get_dataset_location('F'), 'P':default.get_dataset_location('P'), 'S':default.get_dataset_location('S')} - in_memory = mechanics.PK2(default.read_dataset(loc['P'],0), - default.read_dataset(loc['F'],0)) + in_memory = mechanics.stress_second_Piola_Kirchhoff(default.read_dataset(loc['P'],0), + default.read_dataset(loc['F'],0)) in_file = default.read_dataset(loc['S'],0) assert np.allclose(in_memory,in_file) + @pytest.mark.skip(reason='requires rework of lattice.f90') @pytest.mark.parametrize('polar',[True,False]) def test_add_pole(self,default,polar): pole = np.array([1.,0.,0.]) - default.add_pole('orientation',pole,polar) - loc = {'orientation': default.get_dataset_location('orientation'), - 'pole': default.get_dataset_location('p^{}_[1 0 0)'.format(u'rφ' if polar else 'xy'))} - rot = Rotation(default.read_dataset(loc['orientation']).view(np.double)) + default.add_pole('O',pole,polar) + loc = {'O': default.get_dataset_location('O'), + 'pole': default.get_dataset_location('p^{}_[1 0 0)'.format(u'rφ' if polar else 'xy'))} + rot = Rotation(default.read_dataset(loc['O']).view(np.double)) rotated_pole = rot * np.broadcast_to(pole,rot.shape+(3,)) xy = rotated_pole[:,0:2]/(1.+abs(pole[2])) in_memory = xy if not polar else \ @@ -242,11 +258,11 @@ class TestResult: in_file = default.read_dataset(loc['pole']) assert np.allclose(in_memory,in_file) - def test_add_rotational_part(self,default): - default.add_rotational_part('F') + def test_add_rotation(self,default): + default.add_rotation('F') loc = {'F': default.get_dataset_location('F'), 'R(F)': default.get_dataset_location('R(F)')} - in_memory = mechanics.rotational_part(default.read_dataset(loc['F'],0)) + in_memory = mechanics.rotation(default.read_dataset(loc['F'],0)).as_matrix() in_file = default.read_dataset(loc['R(F)'],0) assert np.allclose(in_memory,in_file) @@ -254,18 +270,18 @@ class TestResult: default.add_spherical('P') loc = {'P': default.get_dataset_location('P'), 'p_P': default.get_dataset_location('p_P')} - in_memory = mechanics.spherical_part(default.read_dataset(loc['P'],0)).reshape(-1,1) + in_memory = tensor.spherical(default.read_dataset(loc['P'],0),False).reshape(-1,1) in_file = default.read_dataset(loc['p_P'],0) assert np.allclose(in_memory,in_file) def test_add_strain(self,default): t = ['V','U'][np.random.randint(0,2)] m = np.random.random()*2.0 - 1.0 - default.add_strain_tensor('F',t,m) + default.add_strain('F',t,m) label = f'epsilon_{t}^{m}(F)' loc = {'F': default.get_dataset_location('F'), label: default.get_dataset_location(label)} - in_memory = mechanics.strain_tensor(default.read_dataset(loc['F'],0),t,m) + in_memory = mechanics.strain(default.read_dataset(loc['F'],0),t,m) in_file = default.read_dataset(loc[label],0) assert np.allclose(in_memory,in_file) @@ -273,7 +289,7 @@ class TestResult: default.add_stretch_tensor('F','U') loc = {'F': default.get_dataset_location('F'), 'U(F)': default.get_dataset_location('U(F)')} - in_memory = mechanics.right_stretch(default.read_dataset(loc['F'],0)) + in_memory = mechanics.stretch_right(default.read_dataset(loc['F'],0)) in_file = default.read_dataset(loc['U(F)'],0) assert np.allclose(in_memory,in_file) @@ -281,7 +297,7 @@ class TestResult: default.add_stretch_tensor('F','V') loc = {'F': default.get_dataset_location('F'), 'V(F)': default.get_dataset_location('V(F)')} - in_memory = mechanics.left_stretch(default.read_dataset(loc['F'],0)) + in_memory = mechanics.stretch_left(default.read_dataset(loc['F'],0)) in_file = default.read_dataset(loc['V(F)'],0) assert np.allclose(in_memory,in_file) @@ -293,10 +309,14 @@ class TestResult: def test_add_overwrite(self,default,overwrite): default.pick('times',default.times_in_range(0,np.inf)[-1]) - default.add_Cauchy() + default.add_stress_Cauchy() loc = default.get_dataset_location('sigma') with h5py.File(default.fname,'r') as f: - created_first = f[loc[0]].attrs['Created'].decode() + # h5py3 compatibility + try: + created_first = f[loc[0]].attrs['Created'].decode() + except AttributeError: + created_first = f[loc[0]].attrs['Created'] created_first = datetime.strptime(created_first,'%Y-%m-%d %H:%M:%S%z') if overwrite == 'on': @@ -305,9 +325,16 @@ class TestResult: default.disallow_modification() time.sleep(2.) - default.add_calculation('sigma','#sigma#*0.0+311.','not the Cauchy stress') + try: + default.add_calculation('sigma','#sigma#*0.0+311.','not the Cauchy stress') + except ValueError: + pass with h5py.File(default.fname,'r') as f: - created_second = f[loc[0]].attrs['Created'].decode() + # h5py3 compatibility + try: + created_second = f[loc[0]].attrs['Created'].decode() + except AttributeError: + created_second = f[loc[0]].attrs['Created'] created_second = datetime.strptime(created_second,'%Y-%m-%d %H:%M:%S%z') if overwrite == 'on': assert created_first < created_second and np.allclose(default.read_dataset(loc),311.) @@ -341,6 +368,11 @@ class TestResult: os.chdir(tmp_path) default.save_vtk(output) + @pytest.mark.parametrize('mode',['point','cell']) + def test_vtk_mode(self,tmp_path,single_phase,mode): + os.chdir(tmp_path) + single_phase.save_vtk(mode=mode) + def test_XDMF(self,tmp_path,single_phase): os.chdir(tmp_path) single_phase.save_XDMF() diff --git a/python/tests/test_Rotation.py b/python/tests/test_Rotation.py index d70d650a0..a827b7a70 100644 --- a/python/tests/test_Rotation.py +++ b/python/tests/test_Rotation.py @@ -11,9 +11,9 @@ n = 1000 atol=1.e-4 @pytest.fixture -def reference_dir(reference_dir_base): +def ref_path(ref_path_base): """Directory containing reference results.""" - return reference_dir_base/'Rotation' + return ref_path_base/'Rotation' @pytest.fixture def set_of_rotations(set_of_quaternions): @@ -522,7 +522,7 @@ class TestRotation: def test_Eulers_internal(self,set_of_rotations,forward,backward): """Ensure invariance of conversion from Euler angles and back.""" for rot in set_of_rotations: - m = rot.as_Eulers() + m = rot.as_Euler_angles() o = backward(forward(m)) u = np.array([np.pi*2,np.pi,np.pi*2]) ok = np.allclose(m,o,atol=atol) @@ -559,7 +559,7 @@ class TestRotation: """Ensure invariance of conversion from Rodrigues-Frank vector and back.""" cutoff = np.tan(np.pi*.5*(1.-1e-4)) for rot in set_of_rotations: - m = rot.as_Rodrigues() + m = rot.as_Rodrigues_vector() o = backward(forward(m)) ok = np.allclose(np.clip(m,None,cutoff),np.clip(o,None,cutoff),atol=atol) ok = ok or np.isclose(m[3],0.0,atol=atol) @@ -626,7 +626,7 @@ class TestRotation: (Rotation._eu2ro,eu2ro)]) def test_Eulers_vectorization(self,set_of_rotations,vectorized,single): """Check vectorized implementation for Euler angles against single point calculation.""" - eu = np.array([rot.as_Eulers() for rot in set_of_rotations]) + eu = np.array([rot.as_Euler_angles() for rot in set_of_rotations]) vectorized(eu.reshape(eu.shape[0]//2,-1,3)) co = vectorized(eu) for e,c in zip(eu,co): @@ -649,7 +649,7 @@ class TestRotation: (Rotation._ro2ho,ro2ho)]) def test_Rodrigues_vectorization(self,set_of_rotations,vectorized,single): """Check vectorized implementation for Rodrigues-Frank vector against single point calculation.""" - ro = np.array([rot.as_Rodrigues() for rot in set_of_rotations]) + ro = np.array([rot.as_Rodrigues_vector() for rot in set_of_rotations]) vectorized(ro.reshape(ro.shape[0]//2,-1,4)) co = vectorized(ro) for r,c in zip(ro,co): @@ -682,12 +682,15 @@ class TestRotation: for v,o in zip(vec,ori): assert np.allclose(func(v,normalize=True).as_quaternion(),o.as_quaternion()) + def test_invalid_init(self): + with pytest.raises(TypeError): + Rotation(np.ones(3)) @pytest.mark.parametrize('degrees',[True,False]) def test_Eulers(self,set_of_rotations,degrees): for rot in set_of_rotations: m = rot.as_quaternion() - o = Rotation.from_Eulers(rot.as_Eulers(degrees),degrees).as_quaternion() + o = Rotation.from_Euler_angles(rot.as_Euler_angles(degrees),degrees).as_quaternion() ok = np.allclose(m,o,atol=atol) if np.isclose(rot.as_quaternion()[0],0.0,atol=atol): ok |= np.allclose(m*-1.,o,atol=atol) @@ -699,8 +702,8 @@ class TestRotation: def test_axis_angle(self,set_of_rotations,degrees,normalize,P): c = np.array([P*-1,P*-1,P*-1,1.]) for rot in set_of_rotations: - m = rot.as_Eulers() - o = Rotation.from_axis_angle(rot.as_axis_angle(degrees)*c,degrees,normalize,P).as_Eulers() + m = rot.as_Euler_angles() + o = Rotation.from_axis_angle(rot.as_axis_angle(degrees)*c,degrees,normalize,P).as_Euler_angles() u = np.array([np.pi*2,np.pi,np.pi*2]) ok = np.allclose(m,o,atol=atol) ok |= np.allclose(np.where(np.isclose(m,u),m-u,m),np.where(np.isclose(o,u),o-u,o),atol=atol) @@ -726,7 +729,7 @@ class TestRotation: c = np.array([P*-1,P*-1,P*-1,1.]) for rot in set_of_rotations: m = rot.as_matrix() - o = Rotation.from_Rodrigues(rot.as_Rodrigues()*c,normalize,P).as_matrix() + o = Rotation.from_Rodrigues_vector(rot.as_Rodrigues_vector()*c,normalize,P).as_matrix() ok = np.allclose(m,o,atol=atol) assert ok and np.isclose(np.linalg.det(o),1.0), f'{m},{o}' @@ -734,8 +737,8 @@ class TestRotation: def test_homochoric(self,set_of_rotations,P): cutoff = np.tan(np.pi*.5*(1.-1e-4)) for rot in set_of_rotations: - m = rot.as_Rodrigues() - o = Rotation.from_homochoric(rot.as_homochoric()*P*-1,P).as_Rodrigues() + m = rot.as_Rodrigues_vector() + o = Rotation.from_homochoric(rot.as_homochoric()*P*-1,P).as_Rodrigues_vector() ok = np.allclose(np.clip(m,None,cutoff),np.clip(o,None,cutoff),atol=atol) ok = ok or np.isclose(m[3],0.0,atol=atol) assert ok and np.isclose(np.linalg.norm(o[:3]),1.0), f'{m},{o},{rot.as_quaternion()}' @@ -769,13 +772,61 @@ class TestRotation: @pytest.mark.parametrize('shape',[None,1,(4,4)]) def test_random(self,shape): - Rotation.from_random(shape) + r = Rotation.from_random(shape) + if shape is None: + assert r.shape == () + elif shape == 1: + assert r.shape == (1,) + else: + assert r.shape == shape + + def test_equal(self): + assert Rotation.from_random(rng_seed=1) == Rotation.from_random(rng_seed=1) + + def test_inversion(self): + r = Rotation.from_random() + assert r == ~~r + + @pytest.mark.parametrize('shape',[None,1,(1,),(4,2),(1,1,1)]) + def test_shape(self,shape): + r = Rotation.from_random(shape=shape) + assert r.shape == (shape if isinstance(shape,tuple) else (shape,) if shape else ()) + + @pytest.mark.parametrize('shape',[None,1,(1,),(4,2),(3,3,2)]) + def test_append(self,shape): + r = Rotation.from_random(shape=shape) + p = Rotation.from_random(shape=shape) + s = r.append(p) + print(f'append 2x {shape} --> {s.shape}') + assert s[0,...] == r[0,...] and s[-1,...] == p[-1,...] + + @pytest.mark.parametrize('quat,standardized',[ + ([-1,0,0,0],[1,0,0,0]), + ([-0.5,-0.5,-0.5,-0.5],[0.5,0.5,0.5,0.5]), + ]) + def test_standardization(self,quat,standardized): + assert Rotation(quat)._standardize() == Rotation(standardized) + + @pytest.mark.parametrize('shape,length',[ + ((2,3,4),2), + (4,4), + ((),0) + ]) + def test_len(self,shape,length): + r = Rotation.from_random(shape=shape) + assert len(r) == length + + @pytest.mark.parametrize('shape',[(4,6),(2,3,4),(3,3,3)]) + @pytest.mark.parametrize('order',['C','F']) + def test_flatten_reshape(self,shape,order): + r = Rotation.from_random(shape=shape) + assert r == r.flatten(order).reshape(shape,order) @pytest.mark.parametrize('function',[Rotation.from_quaternion, - Rotation.from_Eulers, + Rotation.from_Euler_angles, Rotation.from_axis_angle, Rotation.from_matrix, - Rotation.from_Rodrigues, + Rotation.from_Rodrigues_vector, Rotation.from_homochoric, Rotation.from_cubochoric]) def test_invalid_shape(self,function): @@ -783,9 +834,16 @@ class TestRotation: with pytest.raises(ValueError): function(invalid_shape) + def test_invalid_shape_parallel(self): + invalid_a = np.random.random(np.random.randint(8,32,(3))) + invalid_b = np.random.random(np.random.randint(8,32,(3))) + with pytest.raises(ValueError): + Rotation.from_parallel(invalid_a,invalid_b) + + @pytest.mark.parametrize('fr,to',[(Rotation.from_quaternion,'as_quaternion'), (Rotation.from_axis_angle,'as_axis_angle'), - (Rotation.from_Rodrigues, 'as_Rodrigues'), + (Rotation.from_Rodrigues_vector, 'as_Rodrigues_vector'), (Rotation.from_homochoric,'as_homochoric'), (Rotation.from_cubochoric,'as_cubochoric')]) def test_invalid_P(self,fr,to): @@ -804,16 +862,16 @@ class TestRotation: @pytest.mark.parametrize('function,invalid',[(Rotation.from_quaternion, np.array([-1,0,0,0])), - (Rotation.from_quaternion, np.array([1,1,1,0])), - (Rotation.from_Eulers, np.array([1,4,0])), - (Rotation.from_axis_angle, np.array([1,0,0,4])), - (Rotation.from_axis_angle, np.array([1,1,0,1])), - (Rotation.from_matrix, np.random.rand(3,3)), - (Rotation.from_matrix, np.array([[1,1,0],[1,2,0],[0,0,1]])), - (Rotation.from_Rodrigues, np.array([1,0,0,-1])), - (Rotation.from_Rodrigues, np.array([1,1,0,1])), - (Rotation.from_homochoric, np.array([2,2,2])), - (Rotation.from_cubochoric, np.array([1.1,0,0])) ]) + (Rotation.from_quaternion, np.array([1,1,1,0])), + (Rotation.from_Euler_angles, np.array([1,4,0])), + (Rotation.from_axis_angle, np.array([1,0,0,4])), + (Rotation.from_axis_angle, np.array([1,1,0,1])), + (Rotation.from_matrix, np.random.rand(3,3)), + (Rotation.from_matrix, np.array([[1,1,0],[1,2,0],[0,0,1]])), + (Rotation.from_Rodrigues_vector, np.array([1,0,0,-1])), + (Rotation.from_Rodrigues_vector, np.array([1,1,0,1])), + (Rotation.from_homochoric, np.array([2,2,2])), + (Rotation.from_cubochoric, np.array([1.1,0,0])) ]) def test_invalid_value(self,function,invalid): with pytest.raises(ValueError): function(invalid) @@ -848,7 +906,8 @@ class TestRotation: np.random.rand(3,3,3,3)]) def test_rotate_identity(self,data): R = Rotation() - assert np.allclose(data,R*data) + print(R,data) + assert np.allclose(data,R@data) @pytest.mark.parametrize('data',[np.random.rand(3), np.random.rand(3,3), @@ -856,10 +915,20 @@ class TestRotation: def test_rotate_360deg(self,data): phi_1 = np.random.random() * np.pi phi_2 = 2*np.pi - phi_1 - R_1 = Rotation.from_Eulers(np.array([phi_1,0.,0.])) - R_2 = Rotation.from_Eulers(np.array([0.,0.,phi_2])) + R_1 = Rotation.from_Euler_angles(np.array([phi_1,0.,0.])) + R_2 = Rotation.from_Euler_angles(np.array([0.,0.,phi_2])) assert np.allclose(data,R_2@(R_1@data)) + @pytest.mark.parametrize('pwr',[-10,0,1,2.5,np.pi,np.random.random()]) + def test_rotate_power(self,pwr): + R = Rotation.from_random() + axis_angle = R.as_axis_angle() + axis_angle[ 3] = (pwr*axis_angle[-1])%(2.*np.pi) + if axis_angle[3] > np.pi: + axis_angle[3] -= 2.*np.pi + axis_angle *= -1 + assert R**pwr == Rotation.from_axis_angle(axis_angle) + def test_rotate_inverse(self): R = Rotation.from_random() assert np.allclose(np.eye(3),(~R@R).as_matrix()) @@ -877,7 +946,7 @@ class TestRotation: def test_rotate_invalid_shape(self,data): R = Rotation.from_random() with pytest.raises(ValueError): - R*data + R@data @pytest.mark.parametrize('data',['does_not_work', (1,2), @@ -885,7 +954,7 @@ class TestRotation: def test_rotate_invalid_type(self,data): R = Rotation.from_random() with pytest.raises(TypeError): - R*data + R@data def test_misorientation(self): R = Rotation.from_random() @@ -893,14 +962,13 @@ class TestRotation: def test_misorientation360(self): R_1 = Rotation() - R_2 = Rotation.from_Eulers([360,0,0],degrees=True) + R_2 = Rotation.from_Euler_angles([360,0,0],degrees=True) assert np.allclose(R_1.misorientation(R_2).as_matrix(),np.eye(3)) @pytest.mark.parametrize('angle',[10,20,30,40,50,60,70,80,90,100,120]) def test_average(self,angle): - R_1 = Rotation.from_axis_angle([0,0,1,10],degrees=True) - R_2 = Rotation.from_axis_angle([0,0,1,angle],degrees=True) - avg_angle = R_1.average(R_2).as_axis_angle(degrees=True,pair=True)[1] + R = Rotation.from_axis_angle([[0,0,1,10],[0,0,1,angle]],degrees=True) + avg_angle = R.average().as_axis_angle(degrees=True,pair=True)[1] assert np.isclose(avg_angle,10+(angle-10)/2.) @@ -948,34 +1016,34 @@ class TestRotation: @pytest.mark.parametrize('fractions',[True,False]) @pytest.mark.parametrize('degrees',[True,False]) @pytest.mark.parametrize('N',[2**13,2**14,2**15]) - def test_ODF_cell(self,reference_dir,fractions,degrees,N): + def test_ODF_cell(self,ref_path,fractions,degrees,N): steps = np.array([144,36,36]) limits = np.array([360.,90.,90.]) rng = tuple(zip(np.zeros(3),limits)) - weights = Table.load(reference_dir/'ODF_experimental_cell.txt').get('intensity').flatten() + weights = Table.load(ref_path/'ODF_experimental_cell.txt').get('intensity').flatten() Eulers = grid_filters.cell_coord0(steps,limits) Eulers = np.radians(Eulers) if not degrees else Eulers - Eulers_r = Rotation.from_ODF(weights,Eulers.reshape(-1,3,order='F'),N,degrees,fractions).as_Eulers(True) + Eulers_r = Rotation.from_ODF(weights,Eulers.reshape(-1,3,order='F'),N,degrees,fractions).as_Euler_angles(True) weights_r = np.histogramdd(Eulers_r,steps,rng)[0].flatten(order='F')/N * np.sum(weights) if fractions: assert np.sqrt(((weights_r - weights) ** 2).mean()) < 4 @pytest.mark.parametrize('degrees',[True,False]) @pytest.mark.parametrize('N',[2**13,2**14,2**15]) - def test_ODF_node(self,reference_dir,degrees,N): + def test_ODF_node(self,ref_path,degrees,N): steps = np.array([144,36,36]) limits = np.array([360.,90.,90.]) rng = tuple(zip(-limits/steps*.5,limits-limits/steps*.5)) - weights = Table.load(reference_dir/'ODF_experimental.txt').get('intensity') + weights = Table.load(ref_path/'ODF_experimental.txt').get('intensity') weights = weights.reshape(steps+1,order='F')[:-1,:-1,:-1].reshape(-1,order='F') Eulers = grid_filters.node_coord0(steps,limits)[:-1,:-1,:-1] Eulers = np.radians(Eulers) if not degrees else Eulers - Eulers_r = Rotation.from_ODF(weights,Eulers.reshape(-1,3,order='F'),N,degrees).as_Eulers(True) + Eulers_r = Rotation.from_ODF(weights,Eulers.reshape(-1,3,order='F'),N,degrees).as_Euler_angles(True) weights_r = np.histogramdd(Eulers_r,steps,rng)[0].flatten(order='F')/N * np.sum(weights) assert np.sqrt(((weights_r - weights) ** 2).mean()) < 5 diff --git a/python/tests/test_Table.py b/python/tests/test_Table.py index 9dad681bd..b448e201d 100644 --- a/python/tests/test_Table.py +++ b/python/tests/test_Table.py @@ -11,12 +11,18 @@ def default(): return Table(x,{'F':(3,3),'v':(3,),'s':(1,)},['test data','contains only ones']) @pytest.fixture -def reference_dir(reference_dir_base): +def ref_path(ref_path_base): """Directory containing reference results.""" - return reference_dir_base/'Table' + return ref_path_base/'Table' class TestTable: + def test_repr(self,default): + print(default) + + def test_len(self): + len(Table(np.random.rand(7,3),{'X':3})) == 7 + def test_get_scalar(self,default): d = default.get('s') assert np.allclose(d,1.0) and d.shape[1:] == (1,) @@ -61,23 +67,23 @@ class TestTable: default.save(tmp_path/'shouldnotbethere.txt',format='invalid') @pytest.mark.parametrize('mode',['str','path']) - def test_read_ang(self,reference_dir,mode): + def test_read_ang(self,ref_path,mode): if mode == 'path': - new = Table.load_ang(reference_dir/'simple.ang') + new = Table.load_ang(ref_path/'simple.ang') elif mode == 'str': - new = Table.load_ang(str(reference_dir/'simple.ang')) + new = Table.load_ang(str(ref_path/'simple.ang')) assert new.data.shape == (4,10) and \ new.labels == ['eu', 'pos', 'IQ', 'CI', 'ID', 'intensity', 'fit'] - def test_read_ang_file(self,reference_dir): - f = open(reference_dir/'simple.ang') + def test_read_ang_file(self,ref_path): + f = open(ref_path/'simple.ang') new = Table.load_ang(f) assert new.data.shape == (4,10) and \ new.labels == ['eu', 'pos', 'IQ', 'CI', 'ID', 'intensity', 'fit'] @pytest.mark.parametrize('fname',['datatype-mix.txt','whitespace-mix.txt']) - def test_read_strange(self,reference_dir,fname): - with open(reference_dir/fname) as f: + def test_read_strange(self,ref_path,fname): + with open(ref_path/fname) as f: Table.load(f) def test_set(self,default): diff --git a/python/tests/test_VTK.py b/python/tests/test_VTK.py index 81c9eb772..1b90fff88 100644 --- a/python/tests/test_VTK.py +++ b/python/tests/test_VTK.py @@ -9,28 +9,28 @@ from damask import VTK from damask import grid_filters @pytest.fixture -def reference_dir(reference_dir_base): +def ref_path(ref_path_base): """Directory containing reference results.""" - return reference_dir_base/'VTK' + return ref_path_base/'VTK' @pytest.fixture def default(): """Simple VTK.""" grid = np.array([5,6,7],int) size = np.array([.6,1.,.5]) - return VTK.from_rectilinearGrid(grid,size) + return VTK.from_rectilinear_grid(grid,size) class TestVTK: @pytest.fixture(autouse=True) - def _execution_stamp(self, execution_stamp): + def _patch_execution_stamp(self, patch_execution_stamp): print('patched damask.util.execution_stamp') def test_rectilinearGrid(self,tmp_path): grid = np.random.randint(5,10,3)*2 size = np.random.random(3) + 1.0 origin = np.random.random(3) - v = VTK.from_rectilinearGrid(grid,size,origin) + v = VTK.from_rectilinear_grid(grid,size,origin) string = v.__repr__() v.save(tmp_path/'rectilinearGrid',False) vtr = VTK.load(tmp_path/'rectilinearGrid.vtr') @@ -41,7 +41,7 @@ class TestVTK: def test_polyData(self,tmp_path): points = np.random.rand(100,3) - v = VTK.from_polyData(points) + v = VTK.from_poly_data(points) string = v.__repr__() v.save(tmp_path/'polyData',False) vtp = VTK.load(tmp_path/'polyData.vtp') @@ -60,7 +60,7 @@ class TestVTK: def test_unstructuredGrid(self,tmp_path,cell_type,n): nodes = np.random.rand(n,3) connectivity = np.random.choice(np.arange(n),n,False).reshape(-1,n) - v = VTK.from_unstructuredGrid(nodes,connectivity,cell_type) + v = VTK.from_unstructured_grid(nodes,connectivity,cell_type) string = v.__repr__() v.save(tmp_path/'unstructuredGrid',False) vtu = VTK.load(tmp_path/'unstructuredGrid.vtu') @@ -72,7 +72,7 @@ class TestVTK: def test_parallel_out(self,tmp_path): points = np.random.rand(102,3) - v = VTK.from_polyData(points) + v = VTK.from_poly_data(points) fname_s = tmp_path/'single.vtp' fname_p = tmp_path/'parallel.vtp' v.save(fname_s,False) @@ -84,17 +84,38 @@ class TestVTK: time.sleep(.5) assert(False) + def test_compress(self,tmp_path): + points = np.random.rand(102,3) + v = VTK.from_poly_data(points) + fname_c = tmp_path/'compressed.vtp' + fname_p = tmp_path/'plain.vtp' + v.save(fname_c,parallel=False,compress=False) + v.save(fname_p,parallel=False,compress=True) + assert(VTK.load(fname_c).__repr__() == VTK.load(fname_p).__repr__()) - @pytest.mark.parametrize('name,dataset_type',[('this_file_does_not_exist.vtk', None), - ('this_file_does_not_exist.vtk','vtk'), - ('this_file_does_not_exist.vtx', None)]) - def test_invalid_dataset_type(self,name,dataset_type): + + @pytest.mark.parametrize('fname',['a','a.vtp','a.b','a.b.vtp']) + def test_filename_variations(self,tmp_path,fname): + points = np.random.rand(102,3) + v = VTK.from_poly_data(points) + v.save(tmp_path/fname) + + @pytest.mark.parametrize('fname,dataset_type',[('a_file.vtk', None), + ('a_file.vtk','vtk'), + ('a_file.vtx', None)]) + def test_invalid_dataset_type(self,tmp_path,fname,dataset_type): + open(tmp_path/fname,'a').close() with pytest.raises(TypeError): - VTK.load(name,dataset_type) + VTK.load(tmp_path/fname,dataset_type) + + def test_file_not_found(self): + with pytest.raises(FileNotFoundError): + VTK.load('/dev/null') + + def test_add_extension(self,tmp_path,default): + default.save(tmp_path/'default.txt',parallel=False) + assert os.path.isfile(tmp_path/'default.txt.vtr') - def test_invalid_extension_write(self,default): - with pytest.raises(ValueError): - default.save('default.txt') def test_invalid_get(self,default): with pytest.raises(ValueError): @@ -119,28 +140,28 @@ class TestVTK: new = VTK.load(tmp_path/'with_comments.vtr') assert new.get_comments() == ['this is a comment'] - def test_compare_reference_polyData(self,update,reference_dir,tmp_path): + def test_compare_reference_polyData(self,update,ref_path,tmp_path): points=np.dstack((np.linspace(0.,1.,10),np.linspace(0.,2.,10),np.linspace(-1.,1.,10))).squeeze() - polyData = VTK.from_polyData(points) + polyData = VTK.from_poly_data(points) polyData.add(points,'coordinates') if update: - polyData.save(reference_dir/'polyData') + polyData.save(ref_path/'polyData') else: - reference = VTK.load(reference_dir/'polyData.vtp') + reference = VTK.load(ref_path/'polyData.vtp') assert polyData.__repr__() == reference.__repr__() and \ np.allclose(polyData.get('coordinates'),points) - def test_compare_reference_rectilinearGrid(self,update,reference_dir,tmp_path): + def test_compare_reference_rectilinearGrid(self,update,ref_path,tmp_path): grid = np.array([5,6,7],int) size = np.array([.6,1.,.5]) - rectilinearGrid = VTK.from_rectilinearGrid(grid,size) + rectilinearGrid = VTK.from_rectilinear_grid(grid,size) c = grid_filters.cell_coord0(grid,size).reshape(-1,3,order='F') n = grid_filters.node_coord0(grid,size).reshape(-1,3,order='F') rectilinearGrid.add(c,'cell') rectilinearGrid.add(n,'node') if update: - rectilinearGrid.save(reference_dir/'rectilinearGrid') + rectilinearGrid.save(ref_path/'rectilinearGrid') else: - reference = VTK.load(reference_dir/'rectilinearGrid.vtr') + reference = VTK.load(ref_path/'rectilinearGrid.vtr') assert rectilinearGrid.__repr__() == reference.__repr__() and \ np.allclose(rectilinearGrid.get('cell'),c) diff --git a/python/tests/test_mechanics.py b/python/tests/test_mechanics.py index 5fe79de28..667747f8e 100644 --- a/python/tests/test_mechanics.py +++ b/python/tests/test_mechanics.py @@ -1,88 +1,177 @@ import pytest import numpy as np +from damask import tensor from damask import mechanics +from damask import Rotation + +def stress_Cauchy(P,F): + sigma = 1.0/np.linalg.det(F) * np.dot(P,F.T) + return symmetric(sigma) + + +def eigenvalues(T_sym): + return np.linalg.eigvalsh(symmetric(T_sym)) + + +def maximum_shear(T_sym): + w = eigenvalues(T_sym) + return (w[0] - w[2])*0.5 + + +def equivalent_strain_Mises(epsilon): + return equivalent_Mises(epsilon,2.0/3.0) + + +def equivalent_stress_Mises(sigma): + return equivalent_Mises(sigma,3.0/2.0) + + +def stress_second_Piola_Kirchhoff(P,F): + S = np.dot(np.linalg.inv(F),P) + return symmetric(S) + + +def rotation(T): + return polar_decomposition(T,'R')[0] + + +def strain(F,t,m): + + if t == 'V': + B = np.matmul(F,F.T) + w,n = np.linalg.eigh(B) + elif t == 'U': + C = np.matmul(F.T,F) + w,n = np.linalg.eigh(C) + + if m > 0.0: + eps = 1.0/(2.0*abs(m)) * (+ np.matmul(n,np.einsum('j,kj->jk',w**m,n)) + - np.eye(3)) + elif m < 0.0: + eps = 1.0/(2.0*abs(m)) * (- np.matmul(n,np.einsum('j,kj->jk',w**m,n)) + + np.eye(3)) + else: + eps = np.matmul(n,np.einsum('j,kj->jk',0.5*np.log(w),n)) + + return eps + + +def stretch_left(T): + return polar_decomposition(T,'V')[0] + + +def stretch_right(T): + return polar_decomposition(T,'U')[0] + + +def symmetric(T): + return (T+T.T)*0.5 + + +def polar_decomposition(T,requested): + u, s, vh = np.linalg.svd(T) + R = np.dot(u,vh) + + output = [] + if 'R' in requested: + output.append(R) + if 'V' in requested: + output.append(np.dot(T,R.T)) + if 'U' in requested: + output.append(np.dot(R.T,T)) + + return tuple(output) + +def equivalent_Mises(T_sym,s): + return np.sqrt(s*(np.sum(deviatoric(T_sym)**2.0))) + +def deviatoric(T): + return T - np.eye(3)*np.trace(T)/3.0 class TestMechanics: n = 1000 c = np.random.randint(n) + @pytest.mark.parametrize('vectorized,single',[(mechanics.maximum_shear, maximum_shear), + (mechanics.equivalent_stress_Mises, equivalent_stress_Mises), + (mechanics.equivalent_strain_Mises, equivalent_strain_Mises), + (mechanics.stretch_left, stretch_left), + (mechanics.stretch_right, stretch_right), + ]) + def test_vectorize_1_arg(self,vectorized,single): + epsilon = np.random.rand(self.n,3,3) + epsilon_vec = np.reshape(epsilon,(self.n//10,10,3,3)) + for i,v in enumerate(np.reshape(vectorized(epsilon_vec),vectorized(epsilon).shape)): + assert np.allclose(single(epsilon[i]),v) - @pytest.mark.parametrize('function',[mechanics.deviatoric_part, - mechanics.eigenvalues, - mechanics.eigenvectors, - mechanics.left_stretch, - mechanics.maximum_shear, - mechanics.Mises_strain, - mechanics.Mises_stress, - mechanics.right_stretch, - mechanics.rotational_part, - mechanics.spherical_part, - mechanics.symmetric, - mechanics.transpose, - ]) - def test_vectorize_1_arg(self,function): - epsilon = np.random.rand(self.n,3,3) - assert np.allclose(function(epsilon)[self.c],function(epsilon[self.c])) + def test_vectorize_rotation(self): + epsilon = Rotation.from_random(self.n).as_matrix() + epsilon_vec = np.reshape(epsilon,(self.n//10,10,3,3)) + for i,v in enumerate(np.reshape(mechanics.rotation(epsilon_vec).as_matrix(), + mechanics.rotation(epsilon).as_matrix().shape)): + assert np.allclose(rotation(epsilon[i]),v) - @pytest.mark.parametrize('function',[mechanics.Cauchy, - mechanics.PK2, - ]) - def test_vectorize_2_arg(self,function): - P = np.random.rand(self.n,3,3) - F = np.random.rand(self.n,3,3) - assert np.allclose(function(P,F)[self.c],function(P[self.c],F[self.c])) - def test_vectorize_strain_tensor(self): - F = np.random.rand(self.n,3,3) - t = ['V','U'][np.random.randint(0,2)] - m = np.random.random()*10. -5.0 - assert np.allclose(mechanics.strain_tensor(F,t,m)[self.c], - mechanics.strain_tensor(F[self.c],t,m)) + @pytest.mark.parametrize('vectorized,single',[(mechanics.stress_Cauchy, stress_Cauchy), + (mechanics.stress_second_Piola_Kirchhoff, stress_second_Piola_Kirchhoff) + ]) + def test_vectorize_2_arg(self,vectorized,single): + P = np.random.rand(self.n,3,3) + F = np.random.rand(self.n,3,3) + P_vec = np.reshape(P,(self.n//10,10,3,3)) + F_vec = np.reshape(F,(self.n//10,10,3,3)) + for i,v in enumerate(np.reshape(vectorized(P_vec,F_vec),vectorized(P,F).shape)): + assert np.allclose(single(P[i],F[i]),v) - @pytest.mark.parametrize('function',[mechanics.Cauchy, - mechanics.PK2, + + @pytest.mark.parametrize('vectorized,single',[(mechanics.strain,strain)]) + def test_vectorize_strain(self,vectorized,single): + F = np.random.rand(self.n,3,3) + F_vec = np.reshape(F,(self.n//10,10,3,3)) + t = ['V','U'][np.random.randint(0,2)] + m = np.random.random()*10.0 -5.0 + for i,v in enumerate(np.reshape(vectorized(F_vec,t,m),vectorized(F,t,m).shape)): + assert np.allclose(single(F[i],t,m),v) + + @pytest.mark.parametrize('function',[mechanics.stress_Cauchy, + mechanics.stress_second_Piola_Kirchhoff, ]) def test_stress_measures(self,function): """Ensure that all stress measures are equivalent for no deformation.""" P = np.random.rand(self.n,3,3) - assert np.allclose(function(P,np.broadcast_to(np.eye(3),(self.n,3,3))),mechanics.symmetric(P)) - - def test_deviatoric_part(self): - I_n = np.broadcast_to(np.eye(3),(self.n,3,3)) - r = np.logical_not(I_n)*np.random.rand(self.n,3,3) - assert np.allclose(mechanics.deviatoric_part(I_n+r),r) + assert np.allclose(function(P,np.broadcast_to(np.eye(3),(self.n,3,3))),tensor.symmetric(P)) def test_polar_decomposition(self): """F = RU = VR.""" F = np.broadcast_to(np.eye(3),[self.n,3,3])*np.random.rand(self.n,3,3) - R = mechanics.rotational_part(F) - V = mechanics.left_stretch(F) - U = mechanics.right_stretch(F) + R = mechanics.rotation(F).as_matrix() + V = mechanics.stretch_left(F) + U = mechanics.stretch_right(F) assert np.allclose(np.matmul(R,U), np.matmul(V,R)) @pytest.mark.parametrize('m',[0.0,np.random.random()*10.,np.random.random()*-10.]) - def test_strain_tensor_no_rotation(self,m): + def test_strain_no_rotation(self,m): """Ensure that left and right stretch give same results for no rotation.""" F = np.broadcast_to(np.eye(3),[self.n,3,3])*np.random.rand(self.n,3,3) - assert np.allclose(mechanics.strain_tensor(F,'U',m), - mechanics.strain_tensor(F,'V',m)) + assert np.allclose(mechanics.strain(F,'U',m), + mechanics.strain(F,'V',m)) @pytest.mark.parametrize('m',[0.0,np.random.random()*2.5,np.random.random()*-2.5]) - def test_strain_tensor_rotation_equivalence(self,m): + def test_strain_rotation_equivalence(self,m): """Ensure that left and right strain differ only by a rotation.""" F = np.broadcast_to(np.eye(3),[self.n,3,3]) + (np.random.rand(self.n,3,3)*0.5 - 0.25) - assert np.allclose(np.linalg.det(mechanics.strain_tensor(F,'U',m)), - np.linalg.det(mechanics.strain_tensor(F,'V',m))) + assert np.allclose(np.linalg.det(mechanics.strain(F,'U',m)), + np.linalg.det(mechanics.strain(F,'V',m))) @pytest.mark.parametrize('m',[0.0,np.random.random(),np.random.random()*-1.]) @pytest.mark.parametrize('t',['V','U']) - def test_strain_tensor_rotation(self,m,t): + def test_strain_rotation(self,m,t): """Ensure that pure rotation results in no strain.""" - F = mechanics.rotational_part(np.random.rand(self.n,3,3)) - assert np.allclose(mechanics.strain_tensor(F,t,m), + F = Rotation.from_random(self.n).as_matrix() + assert np.allclose(mechanics.strain(F,t,m), 0.0) def test_rotation_determinant(self): @@ -92,82 +181,37 @@ class TestMechanics: Should be +1, but random F might contain a reflection. """ x = np.random.rand(self.n,3,3) - assert np.allclose(np.abs(np.linalg.det(mechanics.rotational_part(x))), + assert np.allclose(np.abs(np.linalg.det(mechanics._polar_decomposition(x,'R')[0])), 1.0) - def test_spherical_deviatoric_part(self): - """Ensure that full tensor is sum of spherical and deviatoric part.""" - x = np.random.rand(self.n,3,3) - sph = mechanics.spherical_part(x,True) - assert np.allclose(sph + mechanics.deviatoric_part(x), - x) - def test_deviatoric_Mises(self): """Ensure that Mises equivalent stress depends only on deviatoric part.""" x = np.random.rand(self.n,3,3) - full = mechanics.Mises_stress(x) - dev = mechanics.Mises_stress(mechanics.deviatoric_part(x)) + full = mechanics.equivalent_stress_Mises(x) + dev = mechanics.equivalent_stress_Mises(tensor.deviatoric(x)) assert np.allclose(full, dev) - def test_spherical_mapping(self): - """Ensure that mapping to tensor is correct.""" + @pytest.mark.parametrize('Mises_equivalent',[mechanics.equivalent_strain_Mises, + mechanics.equivalent_stress_Mises]) + def test_spherical_Mises(self,Mises_equivalent): + """Ensure that Mises equivalent strain/stress of spherical strain is 0.""" x = np.random.rand(self.n,3,3) - tensor = mechanics.spherical_part(x,True) - scalar = mechanics.spherical_part(x) - assert np.allclose(np.linalg.det(tensor), - scalar**3.0) - - def test_spherical_Mises(self): - """Ensure that Mises equivalent strrain of spherical strain is 0.""" - x = np.random.rand(self.n,3,3) - sph = mechanics.spherical_part(x,True) - assert np.allclose(mechanics.Mises_strain(sph), + assert np.allclose(Mises_equivalent(tensor.spherical(x,True)), 0.0) - def test_symmetric(self): - """Ensure that a symmetric tensor is half of the sum of a tensor and its transpose.""" - x = np.random.rand(self.n,3,3) - assert np.allclose(mechanics.symmetric(x)*2.0, - mechanics.transpose(x)+x) - - def test_transpose(self): - """Ensure that a symmetric tensor equals its transpose.""" - x = mechanics.symmetric(np.random.rand(self.n,3,3)) - assert np.allclose(mechanics.transpose(x), - x) def test_Mises(self): """Ensure that equivalent stress is 3/2 of equivalent strain.""" x = np.random.rand(self.n,3,3) - assert np.allclose(mechanics.Mises_stress(x)/mechanics.Mises_strain(x), + assert np.allclose(mechanics.equivalent_stress_Mises(x)/mechanics.equivalent_strain_Mises(x), 1.5) - def test_eigenvalues(self): - """Ensure that the characteristic polynomial can be solved.""" - A = mechanics.symmetric(np.random.rand(self.n,3,3)) - lambd = mechanics.eigenvalues(A) - s = np.random.randint(self.n) - for i in range(3): - assert np.allclose(np.linalg.det(A[s]-lambd[s,i]*np.eye(3)),.0) - - def test_eigenvalues_and_vectors(self): - """Ensure that eigenvalues and -vectors are the solution to the characteristic polynomial.""" - A = mechanics.symmetric(np.random.rand(self.n,3,3)) - lambd = mechanics.eigenvalues(A) - x = mechanics.eigenvectors(A) - s = np.random.randint(self.n) - for i in range(3): - assert np.allclose(np.dot(A[s]-lambd[s,i]*np.eye(3),x[s,:,i]),.0) - - def test_eigenvectors_RHS(self): - """Ensure that RHS coordinate system does only change sign of determinant.""" - A = mechanics.symmetric(np.random.rand(self.n,3,3)) - LRHS = np.linalg.det(mechanics.eigenvectors(A,RHS=False)) - RHS = np.linalg.det(mechanics.eigenvectors(A,RHS=True)) - assert np.allclose(np.abs(LRHS),RHS) - def test_spherical_no_shear(self): """Ensure that sherical stress has max shear of 0.0.""" - A = mechanics.spherical_part(mechanics.symmetric(np.random.rand(self.n,3,3)),True) + A = tensor.spherical(tensor.symmetric(np.random.rand(self.n,3,3)),True) assert np.allclose(mechanics.maximum_shear(A),0.0) + + def test_invalid_decomposition(self): + with pytest.raises(ValueError): + mechanics._polar_decomposition(np.random.rand(10,3,3),'A') diff --git a/python/tests/test_tensor.py b/python/tests/test_tensor.py new file mode 100644 index 000000000..58194130e --- /dev/null +++ b/python/tests/test_tensor.py @@ -0,0 +1,99 @@ +import pytest +import numpy as np + +from damask import tensor + +def deviatoric(T): + return T - spherical(T) + +def eigenvalues(T_sym): + return np.linalg.eigvalsh(symmetric(T_sym)) + +def eigenvectors(T_sym,RHS=False): + (u,v) = np.linalg.eigh(symmetric(T_sym)) + + if RHS: + if np.linalg.det(v) < 0.0: v[:,2] *= -1.0 + return v + +def symmetric(T): + return (T+transpose(T))*0.5 + +def transpose(T): + return T.T + +def spherical(T,tensor=True): + sph = np.trace(T)/3.0 + return sph if not tensor else np.eye(3)*sph + + +class TestTensor: + + n = 1000 + c = np.random.randint(n) + + + @pytest.mark.parametrize('vectorized,single',[(tensor.deviatoric, deviatoric), + (tensor.eigenvalues, eigenvalues), + (tensor.eigenvectors, eigenvectors), + (tensor.symmetric, symmetric), + (tensor.transpose, transpose), + (tensor.spherical, spherical), + ]) + def test_vectorize_1_arg(self,vectorized,single): + epsilon = np.random.rand(self.n,3,3) + epsilon_vec = np.reshape(epsilon,(self.n//10,10,3,3)) + for i,v in enumerate(np.reshape(vectorized(epsilon_vec),vectorized(epsilon).shape)): + assert np.allclose(single(epsilon[i]),v) + + def test_symmetric(self): + """Ensure that a symmetric tensor is half of the sum of a tensor and its transpose.""" + x = np.random.rand(self.n,3,3) + assert np.allclose(tensor.symmetric(x)*2.0,tensor.transpose(x)+x) + + def test_transpose(self): + """Ensure that a symmetric tensor equals its transpose.""" + x = tensor.symmetric(np.random.rand(self.n,3,3)) + assert np.allclose(tensor.transpose(x),x) + + def test_eigenvalues(self): + """Ensure that the characteristic polynomial can be solved.""" + A = tensor.symmetric(np.random.rand(self.n,3,3)) + lambd = tensor.eigenvalues(A) + s = np.random.randint(self.n) + for i in range(3): + assert np.allclose(np.linalg.det(A[s]-lambd[s,i]*np.eye(3)),.0) + + def test_eigenvalues_and_vectors(self): + """Ensure that eigenvalues and -vectors are the solution to the characteristic polynomial.""" + A = tensor.symmetric(np.random.rand(self.n,3,3)) + lambd = tensor.eigenvalues(A) + x = tensor.eigenvectors(A) + s = np.random.randint(self.n) + for i in range(3): + assert np.allclose(np.dot(A[s]-lambd[s,i]*np.eye(3),x[s,:,i]),.0) + + def test_eigenvectors_RHS(self): + """Ensure that RHS coordinate system does only change sign of determinant.""" + A = tensor.symmetric(np.random.rand(self.n,3,3)) + LRHS = np.linalg.det(tensor.eigenvectors(A,RHS=False)) + RHS = np.linalg.det(tensor.eigenvectors(A,RHS=True)) + assert np.allclose(np.abs(LRHS),RHS) + + def test_spherical_deviatoric_part(self): + """Ensure that full tensor is sum of spherical and deviatoric part.""" + x = np.random.rand(self.n,3,3) + assert np.allclose(tensor.spherical(x,True) + tensor.deviatoric(x), + x) + def test_spherical_mapping(self): + """Ensure that mapping to tensor is correct.""" + x = np.random.rand(self.n,3,3) + tnsr = tensor.spherical(x,True) + scalar = tensor.spherical(x,False) + assert np.allclose(np.linalg.det(tnsr), + scalar**3.0) + + def test_deviatoric(self): + I_n = np.broadcast_to(np.eye(3),(self.n,3,3)) + r = np.logical_not(I_n)*np.random.rand(self.n,3,3) + assert np.allclose(tensor.deviatoric(I_n+r),r) diff --git a/python/tests/test_util.py b/python/tests/test_util.py index 0b5593e8e..eb1084b09 100644 --- a/python/tests/test_util.py +++ b/python/tests/test_util.py @@ -15,6 +15,10 @@ class TestUtil: out,err = util.execute('sh -c "echo $test_for_execute"',env={'test_for_execute':'test'}) assert out=='test\n' and err=='' + def test_execute_invalid(self): + with pytest.raises(RuntimeError): + util.execute('/bin/false') + def test_croak(self): util.croak('Burp!') @@ -44,3 +48,56 @@ class TestUtil: selected = util.hybrid_IA(dist,N_samples) dist_sampled = np.histogram(centers[selected],bins)[0]/N_samples*np.sum(dist) assert np.sqrt(((dist - dist_sampled) ** 2).mean()) < .025 and selected.shape[0]==N_samples + + @pytest.mark.parametrize('point,normalize,answer', + [ + ([1,0,0],False,[1,0,0]), + ([1,0,0],True, [1,0,0]), + ([0,1,1],False,[0,0.5,0]), + ([0,1,1],True, [0,0.41421356,0]), + ([1,1,1],False,[0.5,0.5,0]), + ([1,1,1],True, [0.3660254, 0.3660254, 0]), + ]) + def test_project_stereographic(self,point,normalize,answer): + assert np.allclose(util.project_stereographic(np.array(point),normalize=normalize),answer) + + @pytest.mark.parametrize('fro,to,mode,answer', + [ + ((),(1,),'left',(1,)), + ((1,),(7,),'right',(1,)), + ((1,2),(1,1,2,2),'right',(1,1,2,1)), + ((1,2),(1,1,2,2),'left',(1,1,1,2)), + ((1,2,3),(1,1,2,3,4),'right',(1,1,2,3,1)), + ((10,2),(10,3,2,2,),'right',(10,1,2,1)), + ((10,2),(10,3,2,2,),'left',(10,1,1,2)), + ((2,2,3),(2,2,2,3,4),'left',(1,2,2,3,1)), + ((2,2,3),(2,2,2,3,4),'right',(2,2,1,3,1)), + ]) + def test_shapeshifter(self,fro,to,mode,answer): + assert util.shapeshifter(fro,to,mode) == answer + + @pytest.mark.parametrize('fro,to,mode', + [ + ((10,3,4),(10,3,2,2),'left'), + ((2,3),(10,3,2,2),'right'), + ]) + def test_invalid_shapeshifter(self,fro,to,mode): + with pytest.raises(ValueError): + util.shapeshifter(fro,to,mode) + + @pytest.mark.parametrize('a,b,answer', + [ + ((),(1,),(1,)), + ((1,),(),(1,)), + ((1,),(7,),(1,7)), + ((2,),(2,2),(2,2)), + ((1,2),(2,2),(1,2,2)), + ((1,2,3),(2,3,4),(1,2,3,4)), + ((1,2,3),(1,2,3),(1,2,3)), + ]) + def test_shapeblender(self,a,b,answer): + assert util.shapeblender(a,b) == answer + + @pytest.mark.parametrize('style',[util.emph,util.deemph,util.warn,util.strikeout]) + def test_decorate(self,style): + assert 'DAMASK' in style('DAMASK') diff --git a/src/CPFEM.f90 b/src/CPFEM.f90 index fa774de66..c49ecbcb6 100644 --- a/src/CPFEM.f90 +++ b/src/CPFEM.f90 @@ -107,9 +107,9 @@ subroutine CPFEM_init print'(/,a)', ' <<<+- CPFEM init -+>>>'; flush(IO_STDOUT) - allocate(CPFEM_cs( 6,discretization_nIP,discretization_nElem), source= 0.0_pReal) - allocate(CPFEM_dcsdE( 6,6,discretization_nIP,discretization_nElem), source= 0.0_pReal) - allocate(CPFEM_dcsdE_knownGood(6,6,discretization_nIP,discretization_nElem), source= 0.0_pReal) + allocate(CPFEM_cs( 6,discretization_nIPs,discretization_Nelems), source= 0.0_pReal) + allocate(CPFEM_dcsdE( 6,6,discretization_nIPs,discretization_Nelems), source= 0.0_pReal) + allocate(CPFEM_dcsdE_knownGood(6,6,discretization_nIPs,discretization_Nelems), source= 0.0_pReal) !------------------------------------------------------------------------------ ! read debug options @@ -184,8 +184,8 @@ subroutine CPFEM_general(mode, ffn, ffn1, temperature_inp, dt, elFE, ip, cauchyS temperature(material_homogenizationAt(elCP))%p(thermalMapping(material_homogenizationAt(elCP))%p(ip,elCP)) = & temperature_inp end select chosenThermal1 - materialpoint_F0(1:3,1:3,ip,elCP) = ffn - materialpoint_F(1:3,1:3,ip,elCP) = ffn1 + homogenization_F0(1:3,1:3,ip,elCP) = ffn + homogenization_F(1:3,1:3,ip,elCP) = ffn1 if (iand(mode, CPFEM_CALCRESULTS) /= 0_pInt) then @@ -212,17 +212,17 @@ subroutine CPFEM_general(mode, ffn, ffn1, temperature_inp, dt, elFE, ip, cauchyS else terminalIllness ! translate from P to sigma - Kirchhoff = matmul(materialpoint_P(1:3,1:3,ip,elCP), transpose(materialpoint_F(1:3,1:3,ip,elCP))) - J_inverse = 1.0_pReal / math_det33(materialpoint_F(1:3,1:3,ip,elCP)) + Kirchhoff = matmul(homogenization_P(1:3,1:3,ip,elCP), transpose(homogenization_F(1:3,1:3,ip,elCP))) + J_inverse = 1.0_pReal / math_det33(homogenization_F(1:3,1:3,ip,elCP)) CPFEM_cs(1:6,ip,elCP) = math_sym33to6(J_inverse * Kirchhoff,weighted=.false.) ! translate from dP/dF to dCS/dE H = 0.0_pReal do i=1,3; do j=1,3; do k=1,3; do l=1,3; do m=1,3; do n=1,3 H(i,j,k,l) = H(i,j,k,l) & - + materialpoint_F(j,m,ip,elCP) * materialpoint_F(l,n,ip,elCP) & - * materialpoint_dPdF(i,m,k,n,ip,elCP) & - - math_delta(j,l) * materialpoint_F(i,m,ip,elCP) * materialpoint_P(k,m,ip,elCP) & + + homogenization_F(j,m,ip,elCP) * homogenization_F(l,n,ip,elCP) & + * homogenization_dPdF(i,m,k,n,ip,elCP) & + - math_delta(j,l) * homogenization_F(i,m,ip,elCP) * homogenization_P(k,m,ip,elCP) & + 0.5_pReal * ( Kirchhoff(j,l)*math_delta(i,k) + Kirchhoff(i,k)*math_delta(j,l) & + Kirchhoff(j,k)*math_delta(i,l) + Kirchhoff(i,l)*math_delta(j,k)) enddo; enddo; enddo; enddo; enddo; enddo diff --git a/src/C_routines.c b/src/C_routines.c index 98dc25e45..4b07c0ee0 100644 --- a/src/C_routines.c +++ b/src/C_routines.c @@ -4,25 +4,24 @@ #include #include #include +#include #include #include #include "zlib.h" -/* http://stackoverflow.com/questions/30279228/is-there-an-alternative-to-getcwd-in-fortran-2003-2008 */ +#define PATHLEN 4096 +#define STRLEN 256 -int isdirectory_c(const char *dir){ - struct stat statbuf; - if(stat(dir, &statbuf) != 0) /* error */ - return 0; /* return "NO, this is not a directory" */ - return S_ISDIR(statbuf.st_mode); /* 1 => is directory, 0 => this is NOT a directory */ +int setcwd_c(const char *cwd){ + return chdir(cwd); } -void getcurrentworkdir_c(char cwd[], int *stat ){ - char cwd_tmp[4096]; - if(getcwd(cwd_tmp, sizeof(cwd_tmp)) == cwd_tmp){ - strcpy(cwd,cwd_tmp); +void getcwd_c(char cwd[], int *stat ){ + char cwd_tmp[PATHLEN+1]; + if(getcwd(cwd_tmp, sizeof(cwd_tmp))){ + strcpy(cwd,cwd_tmp); // getcwd guarantees a NULL-terminated string *stat = 0; } else{ @@ -32,9 +31,9 @@ void getcurrentworkdir_c(char cwd[], int *stat ){ void gethostname_c(char hostname[], int *stat){ - char hostname_tmp[4096]; + char hostname_tmp[STRLEN]; if(gethostname(hostname_tmp, sizeof(hostname_tmp)) == 0){ - strcpy(hostname,hostname_tmp); + strncpy(hostname,hostname_tmp,sizeof(hostname_tmp)+1); // gethostname does not guarantee a NULL-terminated string *stat = 0; } else{ @@ -43,10 +42,18 @@ void gethostname_c(char hostname[], int *stat){ } -int chdir_c(const char *dir){ - return chdir(dir); +void getusername_c(char username[], int *stat){ + struct passwd *pw = getpwuid(geteuid()); + if(pw && strlen(pw->pw_name) <= STRLEN){ + strncpy(username,pw->pw_name,STRLEN+1); + *stat = 0; + } + else{ + *stat = 1; + } } + void signalterm_c(void (*handler)(int)){ signal(SIGTERM, handler); } @@ -59,6 +66,7 @@ void signalusr2_c(void (*handler)(int)){ signal(SIGUSR2, handler); } + void inflate_c(const uLong *s_deflated, const uLong *s_inflated, const Byte deflated[], Byte inflated[]){ /* make writable copy, uncompress will write to it */ uLong s_inflated_,i; diff --git a/src/DAMASK_interface.f90 b/src/DAMASK_interface.f90 index 52971ae06..41f421eb8 100644 --- a/src/DAMASK_interface.f90 +++ b/src/DAMASK_interface.f90 @@ -69,8 +69,6 @@ subroutine DAMASK_interface_init loadCaseArg = '', & !< -l argument given to the executable geometryArg = '', & !< -g argument given to the executable workingDirArg = '' !< -w argument given to the executable - character(len=pStringLen) :: & - userName !< name of user calling the executable integer :: & stat, & i @@ -117,6 +115,9 @@ subroutine DAMASK_interface_init print'(/,a)', ' Compiled on: '//__DATE__//' at '//__TIME__ + print'(/,a,i0,a,i0,a,i0)', & + ' PETSc version: ',PETSC_VERSION_MAJOR,'.',PETSC_VERSION_MINOR,'.',PETSC_VERSION_SUBMINOR + call date_and_time(values = dateAndTime) print'(/,a,2(i2.2,a),i4.4)', ' Date: ',dateAndTime(3),'/',dateAndTime(2),'/', dateAndTime(1) print'(a,2(i2.2,a),i2.2)', ' Time: ',dateAndTime(5),':', dateAndTime(6),':', dateAndTime(7) @@ -126,9 +127,9 @@ subroutine DAMASK_interface_init if (err /= 0) call quit(1) select case(trim(arg)) ! extract key case ('-h','--help') - print'(a)', ' #######################################################################' + print'(/,a)',' #######################################################################' print'(a)', ' DAMASK Command Line Interface:' - print'(a)', ' For PETSc-based solvers for the Düsseldorf Advanced Material Simulation Kit' + print'(a)', ' Düsseldorf Advanced Material Simulation Kit with PETSc-based solvers' print'(a,/)',' #######################################################################' print'(a,/)',' Valid command line switches:' print'(a)', ' --geom (-g, --geometry)' @@ -189,17 +190,15 @@ subroutine DAMASK_interface_init interface_loadFile = getLoadCaseFile(loadCaseArg) call get_command(commandLine) - call get_environment_variable('USER',userName) - ! ToDo: https://stackoverflow.com/questions/8953424/how-to-get-the-username-in-c-c-in-linux - print'(a)', ' Host name: '//trim(getHostName()) - print'(a)', ' User name: '//trim(userName) + print'(/,a)', ' Host name: '//getHostName() + print'(a)', ' User name: '//getUserName() print'(/a)', ' Command line call: '//trim(commandLine) if (len_trim(workingDirArg) > 0) & print'(a)', ' Working dir argument: '//trim(workingDirArg) print'(a)', ' Geometry argument: '//trim(geometryArg) - print'(a)', ' Load case argument: '//trim(loadcaseArg) - print'(a)', ' Working directory: '//getCWD() + print'(a)', ' Loadcase argument: '//trim(loadcaseArg) + print'(/,a)', ' Working directory: '//getCWD() print'(a)', ' Geometry file: '//interface_geomFile print'(a)', ' Loadcase file: '//interface_loadFile print'(a)', ' Solver job name: '//getSolverJobName() @@ -222,8 +221,8 @@ end subroutine DAMASK_interface_init !-------------------------------------------------------------------------------------------------- subroutine setWorkingDirectory(workingDirectoryArg) - character(len=*), intent(in) :: workingDirectoryArg !< working directory argument - character(len=pPathLen) :: workingDirectory + character(len=*), intent(in) :: workingDirectoryArg !< working directory argument + character(len=:), allocatable :: workingDirectory logical :: error external :: quit @@ -359,12 +358,12 @@ end function rectifyPath !-------------------------------------------------------------------------------------------------- -!> @brief relative path from absolute a to absolute b +!> @brief Determine relative path from absolute a to absolute b !-------------------------------------------------------------------------------------------------- function makeRelativePath(a,b) - character (len=*), intent(in) :: a,b - character (len=pPathLen) :: a_cleaned,b_cleaned + character(len=*), intent(in) :: a,b + character(len=pPathLen) :: a_cleaned,b_cleaned character(len=:), allocatable :: makeRelativePath integer :: i,posLastCommonSlash,remainingSlashes diff --git a/src/HDF5_utilities.f90 b/src/HDF5_utilities.f90 index a13ac1a8a..47f4243e7 100644 --- a/src/HDF5_utilities.f90 +++ b/src/HDF5_utilities.f90 @@ -12,11 +12,10 @@ module HDF5_utilities use prec use parallelization - use IO use rotations - implicit none - public + implicit none + public !-------------------------------------------------------------------------------------------------- !> @brief reads integer or float data of defined shape from file ! ToDo: order of arguments wrong @@ -93,15 +92,15 @@ subroutine HDF5_utilities_init !-------------------------------------------------------------------------------------------------- !initialize HDF5 library and check if integer and float type size match call h5open_f(hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_Utilities_init: h5open_f') + if(hdferr < 0) error stop 'HDF5 error' call h5tget_size_f(H5T_NATIVE_INTEGER,typeSize, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_Utilities_init: h5tget_size_f (int)') + if(hdferr < 0) error stop 'HDF5 error' if (int(bit_size(0),SIZE_T)/=typeSize*8) & error stop 'Default integer size does not match H5T_NATIVE_INTEGER' call h5tget_size_f(H5T_NATIVE_DOUBLE,typeSize, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_Utilities_init: h5tget_size_f (double)') + if(hdferr < 0) error stop 'HDF5 error' if (int(storage_size(0.0_pReal),SIZE_T)/=typeSize*8) & error stop 'pReal does not match H5T_NATIVE_DOUBLE' @@ -128,30 +127,30 @@ integer(HID_T) function HDF5_openFile(fileName,mode,parallel) endif call h5pcreate_f(H5P_FILE_ACCESS_F, plist_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_openFile: h5pcreate_f') + if(hdferr < 0) error stop 'HDF5 error' #ifdef PETSc if (present(parallel)) then; if (parallel) then call h5pset_fapl_mpio_f(plist_id, PETSC_COMM_WORLD, MPI_INFO_NULL, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_openFile: h5pset_fapl_mpio_f') + if(hdferr < 0) error stop 'HDF5 error' endif; endif #endif if (m == 'w') then call h5fcreate_f(fileName,H5F_ACC_TRUNC_F,HDF5_openFile,hdferr,access_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_openFile: h5fcreate_f (w)') + if(hdferr < 0) error stop 'HDF5 error' elseif(m == 'a') then call h5fopen_f(fileName,H5F_ACC_RDWR_F,HDF5_openFile,hdferr,access_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_openFile: h5fopen_f (a)') + if(hdferr < 0) error stop 'HDF5 error' elseif(m == 'r') then call h5fopen_f(fileName,H5F_ACC_RDONLY_F,HDF5_openFile,hdferr,access_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_openFile: h5fopen_f (r)') + if(hdferr < 0) error stop 'HDF5 error' else - call IO_error(1,ext_msg='HDF5_openFile: h5fopen_f unknown access mode: '//trim(m)) + error stop 'unknown access mode' endif call h5pclose_f(plist_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_openFile: h5pclose_f') + if(hdferr < 0) error stop 'HDF5 error' end function HDF5_openFile @@ -166,7 +165,7 @@ subroutine HDF5_closeFile(fileHandle) integer :: hdferr call h5fclose_f(fileHandle,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_closeFile: h5fclose_f') + if(hdferr < 0) error stop 'HDF5 error' end subroutine HDF5_closeFile @@ -185,19 +184,19 @@ integer(HID_T) function HDF5_addGroup(fileHandle,groupName) !------------------------------------------------------------------------------------------------- ! creating a property list for data access properties call h5pcreate_f(H5P_GROUP_ACCESS_F, aplist_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg = 'HDF5_addGroup: h5pcreate_f ('//trim(groupName)//')') + if(hdferr < 0) error stop 'HDF5 error' !------------------------------------------------------------------------------------------------- ! setting I/O mode to collective #ifdef PETSc call h5pset_all_coll_metadata_ops_f(aplist_id, .true., hdferr) - if (hdferr < 0) call IO_error(1,ext_msg = 'HDF5_addGroup: h5pset_all_coll_metadata_ops_f ('//trim(groupName)//')') + if(hdferr < 0) error stop 'HDF5 error' #endif !------------------------------------------------------------------------------------------------- ! Create group call h5gcreate_f(fileHandle, trim(groupName), HDF5_addGroup, hdferr, OBJECT_NAMELEN_DEFAULT_F,gapl_id = aplist_id) - if (hdferr < 0) call IO_error(1,ext_msg = 'HDF5_addGroup: h5gcreate_f ('//trim(groupName)//')') + if(hdferr < 0) error stop 'HDF5 error' call h5pclose_f(aplist_id,hdferr) @@ -221,19 +220,19 @@ integer(HID_T) function HDF5_openGroup(fileHandle,groupName) !------------------------------------------------------------------------------------------------- ! creating a property list for data access properties call h5pcreate_f(H5P_GROUP_ACCESS_F, aplist_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg = 'HDF5_openGroup: h5pcreate_f ('//trim(groupName)//')') + if(hdferr < 0) error stop 'HDF5 error' !------------------------------------------------------------------------------------------------- ! setting I/O mode to collective #ifdef PETSc call h5pget_all_coll_metadata_ops_f(aplist_id, is_collective, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg = 'HDF5_openGroup: h5pset_all_coll_metadata_ops_f ('//trim(groupName)//')') + if(hdferr < 0) error stop 'HDF5 error' #endif !------------------------------------------------------------------------------------------------- ! opening the group call h5gopen_f(fileHandle, trim(groupName), HDF5_openGroup, hdferr, gapl_id = aplist_id) - if (hdferr < 0) call IO_error(1,ext_msg = 'HDF5_openGroup: h5gopen_f ('//trim(groupName)//')') + if(hdferr < 0) error stop 'HDF5 error' call h5pclose_f(aplist_id,hdferr) @@ -250,7 +249,7 @@ subroutine HDF5_closeGroup(group_id) integer :: hdferr call h5gclose_f(group_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg = 'HDF5_closeGroup: h5gclose_f (el is ID)', el = int(group_id)) + if(hdferr < 0) error stop 'HDF5 error' end subroutine HDF5_closeGroup @@ -273,11 +272,11 @@ logical function HDF5_objectExists(loc_id,path) endif call h5lexists_f(loc_id, p, HDF5_objectExists, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg = 'HDF5_objectExists: h5oexists_by_name_f') + if(hdferr < 0) error stop 'HDF5 error' if(HDF5_objectExists) then call h5oexists_by_name_f(loc_id, p, HDF5_objectExists, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg = 'HDF5_objectExists: h5oexists_by_name_f') + if(hdferr < 0) error stop 'HDF5 error' endif end function HDF5_objectExists @@ -296,6 +295,8 @@ subroutine HDF5_addAttribute_str(loc_id,attrLabel,attrValue,path) logical :: attrExists integer :: hdferr character(len=:), allocatable :: p + character(len=:,kind=C_CHAR), allocatable,target :: attrValue_ + type(c_ptr), target, dimension(1) :: ptr if (present(path)) then p = trim(path) @@ -303,28 +304,29 @@ subroutine HDF5_addAttribute_str(loc_id,attrLabel,attrValue,path) p = '.' endif + attrValue_ = trim(attrValue)//C_NULL_CHAR + ptr(1) = c_loc(attrValue_) + call h5screate_f(H5S_SCALAR_F,space_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_str: h5screate_f') - call h5tcopy_f(H5T_NATIVE_CHARACTER, type_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_str: h5tcopy_f') - call h5tset_size_f(type_id, int(len_trim(attrValue),HSIZE_T), hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_str: h5tset_size_f') + if(hdferr < 0) error stop 'HDF5 error' + call h5tcopy_f(H5T_STRING, type_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' call h5aexists_by_name_f(loc_id,trim(p),attrLabel,attrExists,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_str: h5aexists_by_name_f') + if(hdferr < 0) error stop 'HDF5 error' if (attrExists) then - call h5adelete_by_name_f(loc_id, trim(p), attrLabel, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_str: h5adelete_by_name_f') + call h5adelete_by_name_f(loc_id, trim(p), attrLabel, hdferr) + if(hdferr < 0) error stop 'HDF5 error' endif call h5acreate_by_name_f(loc_id,trim(p),trim(attrLabel),type_id,space_id,attr_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_str: h5acreate_f') - call h5awrite_f(attr_id, type_id, trim(attrValue), int([1],HSIZE_T), hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_str: h5awrite_f') + if(hdferr < 0) error stop 'HDF5 error' + call h5awrite_f(attr_id, type_id, c_loc(ptr(1)), hdferr) + if(hdferr < 0) error stop 'HDF5 error' call h5aclose_f(attr_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_str: h5aclose_f') + if(hdferr < 0) error stop 'HDF5 error' call h5tclose_f(type_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_str: h5tclose_f') + if(hdferr < 0) error stop 'HDF5 error' call h5sclose_f(space_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_str: h5sclose_f') + if(hdferr < 0) error stop 'HDF5 error' end subroutine HDF5_addAttribute_str @@ -351,21 +353,21 @@ subroutine HDF5_addAttribute_int(loc_id,attrLabel,attrValue,path) endif call h5screate_f(H5S_SCALAR_F,space_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int: h5screate_f') + if(hdferr < 0) error stop 'HDF5 error' call h5aexists_by_name_f(loc_id,trim(p),attrLabel,attrExists,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int: h5aexists_by_name_f') + if(hdferr < 0) error stop 'HDF5 error' if (attrExists) then call h5adelete_by_name_f(loc_id, trim(p), attrLabel, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int: h5adelete_by_name_f') + if(hdferr < 0) error stop 'HDF5 error' endif call h5acreate_by_name_f(loc_id,trim(p),trim(attrLabel),H5T_NATIVE_INTEGER,space_id,attr_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int: h5acreate_f') + if(hdferr < 0) error stop 'HDF5 error' call h5awrite_f(attr_id, H5T_NATIVE_INTEGER, attrValue, int([1],HSIZE_T), hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int: h5awrite_f') + if(hdferr < 0) error stop 'HDF5 error' call h5aclose_f(attr_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int: h5tclose_f') + if(hdferr < 0) error stop 'HDF5 error' call h5sclose_f(space_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int: h5sclose_f') + if(hdferr < 0) error stop 'HDF5 error' end subroutine HDF5_addAttribute_int @@ -392,21 +394,21 @@ subroutine HDF5_addAttribute_real(loc_id,attrLabel,attrValue,path) endif call h5screate_f(H5S_SCALAR_F,space_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_real: h5screate_f') + if(hdferr < 0) error stop 'HDF5 error' call h5aexists_by_name_f(loc_id,trim(p),attrLabel,attrExists,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_real: h5aexists_by_name_f') + if(hdferr < 0) error stop 'HDF5 error' if (attrExists) then call h5adelete_by_name_f(loc_id, trim(p), attrLabel, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_real: h5adelete_by_name_f') + if(hdferr < 0) error stop 'HDF5 error' endif call h5acreate_by_name_f(loc_id,trim(p),trim(attrLabel),H5T_NATIVE_DOUBLE,space_id,attr_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_real: h5acreate_f') + if(hdferr < 0) error stop 'HDF5 error' call h5awrite_f(attr_id, H5T_NATIVE_DOUBLE, attrValue, int([1],HSIZE_T), hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_real: h5awrite_f') + if(hdferr < 0) error stop 'HDF5 error' call h5aclose_f(attr_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_real: h5tclose_f') + if(hdferr < 0) error stop 'HDF5 error' call h5sclose_f(space_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_real: h5sclose_f') + if(hdferr < 0) error stop 'HDF5 error' end subroutine HDF5_addAttribute_real @@ -436,21 +438,21 @@ subroutine HDF5_addAttribute_int_array(loc_id,attrLabel,attrValue,path) array_size = size(attrValue,kind=HSIZE_T) call h5screate_simple_f(1, array_size, space_id, hdferr, array_size) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5screate_f') + if(hdferr < 0) error stop 'HDF5 error' call h5aexists_by_name_f(loc_id,trim(p),attrLabel,attrExists,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5aexists_by_name_f') + if(hdferr < 0) error stop 'HDF5 error' if (attrExists) then call h5adelete_by_name_f(loc_id, trim(p), attrLabel, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5adelete_by_name_f') + if(hdferr < 0) error stop 'HDF5 error' endif call h5acreate_by_name_f(loc_id,trim(p),trim(attrLabel),H5T_NATIVE_INTEGER,space_id,attr_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5acreate_f') + if(hdferr < 0) error stop 'HDF5 error' call h5awrite_f(attr_id, H5T_NATIVE_INTEGER, attrValue, array_size, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5awrite_f') + if(hdferr < 0) error stop 'HDF5 error' call h5aclose_f(attr_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5tclose_f') + if(hdferr < 0) error stop 'HDF5 error' call h5sclose_f(space_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5sclose_f') + if(hdferr < 0) error stop 'HDF5 error' end subroutine HDF5_addAttribute_int_array @@ -480,21 +482,21 @@ subroutine HDF5_addAttribute_real_array(loc_id,attrLabel,attrValue,path) array_size = size(attrValue,kind=HSIZE_T) call h5screate_simple_f(1, array_size, space_id, hdferr, array_size) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5screate_f') + if(hdferr < 0) error stop 'HDF5 error' call h5aexists_by_name_f(loc_id,trim(p),attrLabel,attrExists,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5aexists_by_name_f') + if(hdferr < 0) error stop 'HDF5 error' if (attrExists) then call h5adelete_by_name_f(loc_id, trim(p), attrLabel, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5adelete_by_name_f') + if(hdferr < 0) error stop 'HDF5 error' endif call h5acreate_by_name_f(loc_id,trim(p),trim(attrLabel),H5T_NATIVE_DOUBLE,space_id,attr_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5acreate_f') + if(hdferr < 0) error stop 'HDF5 error' call h5awrite_f(attr_id, H5T_NATIVE_DOUBLE, attrValue, array_size, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5awrite_f') + if(hdferr < 0) error stop 'HDF5 error' call h5aclose_f(attr_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5tclose_f') + if(hdferr < 0) error stop 'HDF5 error' call h5sclose_f(space_id,hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_addAttribute_int_array: h5sclose_f') + if(hdferr < 0) error stop 'HDF5 error' end subroutine HDF5_addAttribute_real_array @@ -510,13 +512,13 @@ subroutine HDF5_setLink(loc_id,target_name,link_name) logical :: linkExists call h5lexists_f(loc_id, link_name,linkExists, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg = 'HDF5_setLink: h5lexists_soft_f ('//trim(link_name)//')') + if(hdferr < 0) error stop 'HDF5 error' if (linkExists) then call h5ldelete_f(loc_id,link_name, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg = 'HDF5_setLink: h5ldelete_soft_f ('//trim(link_name)//')') + if(hdferr < 0) error stop 'HDF5 error' endif call h5lcreate_soft_f(target_name, loc_id, link_name, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg = 'HDF5_setLink: h5lcreate_soft_f ('//trim(target_name)//' '//trim(link_name)//')') + if(hdferr < 0) error stop 'HDF5 error' end subroutine HDF5_setLink @@ -555,7 +557,7 @@ subroutine HDF5_read_real1(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_DOUBLE,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_real1: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -595,7 +597,7 @@ subroutine HDF5_read_real2(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_DOUBLE,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_real2: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -635,7 +637,7 @@ subroutine HDF5_read_real3(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_DOUBLE,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_real3: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -675,7 +677,7 @@ subroutine HDF5_read_real4(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_DOUBLE,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_real4: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -715,7 +717,7 @@ subroutine HDF5_read_real5(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_DOUBLE,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_real5: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -755,7 +757,7 @@ subroutine HDF5_read_real6(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_DOUBLE,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_real6: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -795,7 +797,7 @@ subroutine HDF5_read_real7(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_DOUBLE,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_real7: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -837,7 +839,7 @@ subroutine HDF5_read_int1(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_INTEGER,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_int1: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -877,7 +879,7 @@ subroutine HDF5_read_int2(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_INTEGER,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_int2: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -917,7 +919,7 @@ subroutine HDF5_read_int3(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_INTEGER,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_int3: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -957,7 +959,7 @@ subroutine HDF5_read_int4(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_INTEGER,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_int4: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -997,7 +999,7 @@ subroutine HDF5_read_int5(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_INTEGER,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_int5: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -1037,7 +1039,7 @@ subroutine HDF5_read_int6(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_INTEGER,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_int6: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -1077,7 +1079,7 @@ subroutine HDF5_read_int7(loc_id,dataset,datasetName,parallel) call h5dread_f(dset_id, H5T_NATIVE_INTEGER,dataset,totalShape, hdferr,& file_space_id = filespace_id, xfer_prp = plist_id, mem_space_id = memspace_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_read_int7: h5dread_f') + if(hdferr < 0) error stop 'HDF5 error' call finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id) @@ -1118,7 +1120,7 @@ subroutine HDF5_write_real1(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_DOUBLE,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_real1: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1159,7 +1161,7 @@ subroutine HDF5_write_real2(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_DOUBLE,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_real2: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1200,7 +1202,7 @@ subroutine HDF5_write_real3(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_DOUBLE,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_real3: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1241,7 +1243,7 @@ subroutine HDF5_write_real4(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_DOUBLE,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_real4: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1283,7 +1285,7 @@ subroutine HDF5_write_real5(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_DOUBLE,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_real5: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1324,7 +1326,7 @@ subroutine HDF5_write_real6(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_DOUBLE,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_real6: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1365,7 +1367,7 @@ subroutine HDF5_write_real7(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_DOUBLE,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_real7: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1407,7 +1409,7 @@ subroutine HDF5_write_int1(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_INTEGER,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_int1: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1448,7 +1450,7 @@ subroutine HDF5_write_int2(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_INTEGER,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_int2: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1489,7 +1491,7 @@ subroutine HDF5_write_int3(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_INTEGER,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_int3: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1530,7 +1532,7 @@ subroutine HDF5_write_int4(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_INTEGER,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_int4: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1571,7 +1573,7 @@ subroutine HDF5_write_int5(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_INTEGER,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_int5: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1612,7 +1614,7 @@ subroutine HDF5_write_int6(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_INTEGER,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_int6: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1653,7 +1655,7 @@ subroutine HDF5_write_int7(loc_id,dataset,datasetName,parallel) if (product(totalShape) /= 0) then call h5dwrite_f(dset_id, H5T_NATIVE_INTEGER,dataset,int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_int7: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1729,7 +1731,7 @@ subroutine HDF5_write_rotation(loc_id,dataset,datasetName,parallel) file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) call h5dwrite_f(dset_id, z_id,dataset_asArray(4,:),int(totalShape,HSIZE_T), hdferr,& file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (hdferr < 0) call IO_error(1,ext_msg='HDF5_write_rotation: h5dwrite_f') + if(hdferr < 0) error stop 'HDF5 error' endif call finalize_write(plist_id, dset_id, filespace_id, memspace_id) @@ -1762,7 +1764,7 @@ subroutine initialize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_ !------------------------------------------------------------------------------------------------- ! creating a property list for transfer properties (is collective for MPI) call h5pcreate_f(H5P_DATASET_XFER_F, plist_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_read: h5pcreate_f') + if(hdferr < 0) error stop 'HDF5 error' !-------------------------------------------------------------------------------------------------- readSize = 0 @@ -1770,9 +1772,9 @@ subroutine initialize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_ #ifdef PETSc if (parallel) then call h5pset_dxpl_mpio_f(plist_id, H5FD_MPIO_COLLECTIVE_F, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_read: h5pset_dxpl_mpio_f') + if(hdferr < 0) error stop 'HDF5 error' call MPI_allreduce(MPI_IN_PLACE,readSize,worldsize,MPI_INT,MPI_SUM,PETSC_COMM_WORLD,ierr) ! get total output size over each process - if (ierr /= 0) call IO_error(894,ext_msg='initialize_read: MPI_allreduce') + if (ierr /= 0) error stop 'MPI error' endif #endif myStart = int(0,HSIZE_T) @@ -1782,28 +1784,28 @@ subroutine initialize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_ !-------------------------------------------------------------------------------------------------- ! create dataspace in memory (local shape) call h5screate_simple_f(size(localShape), localShape, memspace_id, hdferr, localShape) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_read: h5screate_simple_f/memspace_id') + if(hdferr < 0) error stop 'HDF5 error' !-------------------------------------------------------------------------------------------------- ! creating a property list for IO and set it to collective call h5pcreate_f(H5P_DATASET_ACCESS_F, aplist_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_read: h5pcreate_f') + if(hdferr < 0) error stop 'HDF5 error' #ifdef PETSc call h5pset_all_coll_metadata_ops_f(aplist_id, .true., hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_read: h5pset_all_coll_metadata_ops_f') + if(hdferr < 0) error stop 'HDF5 error' #endif !-------------------------------------------------------------------------------------------------- ! open the dataset in the file and get the space ID call h5dopen_f(loc_id,datasetName,dset_id,hdferr, dapl_id = aplist_id) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_read: h5dopen_f') + if(hdferr < 0) error stop 'HDF5 error' call h5dget_space_f(dset_id, filespace_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_read: h5dget_space_f') + if(hdferr < 0) error stop 'HDF5 error' !-------------------------------------------------------------------------------------------------- ! select a hyperslab (the portion of the current process) in the file call h5sselect_hyperslab_f(filespace_id, H5S_SELECT_SET_F, myStart, localShape, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_read: h5sselect_hyperslab_f') + if(hdferr < 0) error stop 'HDF5 error' end subroutine initialize_read @@ -1817,15 +1819,15 @@ subroutine finalize_read(dset_id, filespace_id, memspace_id, plist_id, aplist_id integer :: hdferr call h5pclose_f(plist_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='finalize_read: plist_id') + if(hdferr < 0) error stop 'HDF5 error' call h5pclose_f(aplist_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='finalize_read: aplist_id') + if(hdferr < 0) error stop 'HDF5 error' call h5dclose_f(dset_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='finalize_read: h5dclose_f') + if(hdferr < 0) error stop 'HDF5 error' call h5sclose_f(filespace_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='finalize_read: h5sclose_f/filespace_id') + if(hdferr < 0) error stop 'HDF5 error' call h5sclose_f(memspace_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='finalize_read: h5sclose_f/memspace_id') + if(hdferr < 0) error stop 'HDF5 error' end subroutine finalize_read @@ -1856,11 +1858,11 @@ subroutine initialize_write(dset_id, filespace_id, memspace_id, plist_id, & !------------------------------------------------------------------------------------------------- ! creating a property list for transfer properties (is collective when reading in parallel) call h5pcreate_f(H5P_DATASET_XFER_F, plist_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_write: h5pcreate_f') + if(hdferr < 0) error stop 'HDF5 error' #ifdef PETSc if (parallel) then call h5pset_dxpl_mpio_f(plist_id, H5FD_MPIO_COLLECTIVE_F, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_write: h5pset_dxpl_mpio_f') + if(hdferr < 0) error stop 'HDF5 error' endif #endif @@ -1871,7 +1873,7 @@ subroutine initialize_write(dset_id, filespace_id, memspace_id, plist_id, & #ifdef PETSc if (parallel) then call MPI_allreduce(MPI_IN_PLACE,writeSize,worldsize,MPI_INT,MPI_SUM,PETSC_COMM_WORLD,ierr) ! get total output size over each process - if (ierr /= 0) call IO_error(894,ext_msg='initialize_write: MPI_allreduce') + if (ierr /= 0) error stop 'MPI error' endif #endif myStart = int(0,HSIZE_T) @@ -1881,16 +1883,16 @@ subroutine initialize_write(dset_id, filespace_id, memspace_id, plist_id, & !-------------------------------------------------------------------------------------------------- ! create dataspace in memory (local shape) and in file (global shape) call h5screate_simple_f(size(myShape), myShape, memspace_id, hdferr, myShape) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_write: h5dopen_f') + if(hdferr < 0) error stop 'HDF5 error' call h5screate_simple_f(size(totalShape), totalShape, filespace_id, hdferr, totalShape) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_write: h5dget_space_f') + if(hdferr < 0) error stop 'HDF5 error' !-------------------------------------------------------------------------------------------------- ! create dataset in the file and select a hyperslab from it (the portion of the current process) call h5dcreate_f(loc_id, trim(datasetName), datatype, filespace_id, dset_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_write: h5dcreate_f') + if(hdferr < 0) error stop 'HDF5 error' call h5sselect_hyperslab_f(filespace_id, H5S_SELECT_SET_F, myStart, myShape, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='initialize_write: h5sselect_hyperslab_f') + if(hdferr < 0) error stop 'HDF5 error' end subroutine initialize_write @@ -1904,13 +1906,13 @@ subroutine finalize_write(plist_id, dset_id, filespace_id, memspace_id) integer :: hdferr call h5pclose_f(plist_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='finalize_write: plist_id') + if(hdferr < 0) error stop 'HDF5 error' call h5dclose_f(dset_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='finalize_write: h5dclose_f') + if(hdferr < 0) error stop 'HDF5 error' call h5sclose_f(filespace_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='finalize_write: h5sclose_f/filespace_id') + if(hdferr < 0) error stop 'HDF5 error' call h5sclose_f(memspace_id, hdferr) - if (hdferr < 0) call IO_error(1,ext_msg='finalize_write: h5sclose_f/memspace_id') + if(hdferr < 0) error stop 'HDF5 error' end subroutine finalize_write diff --git a/src/IO.f90 b/src/IO.f90 index 260bd94b8..fd87907aa 100644 --- a/src/IO.f90 +++ b/src/IO.f90 @@ -367,12 +367,8 @@ subroutine IO_error(error_ID,el,ip,g,instance,ext_msg) ! file handling errors case (100) msg = 'could not open file:' - case (101) - msg = 'write error for file:' case (102) msg = 'could not read file:' - case (106) - msg = 'working directory does not exist:' !-------------------------------------------------------------------------------------------------- ! file parsing errors @@ -395,14 +391,10 @@ subroutine IO_error(error_ID,el,ip,g,instance,ext_msg) msg = 'hex lattice structure with invalid c/a ratio' case (132) msg = 'trans_lattice_structure not possible' - case (133) - msg = 'transformed hex lattice structure with invalid c/a ratio' case (134) msg = 'negative lattice parameter' case (135) msg = 'zero entry on stiffness diagonal' - case (136) - msg = 'zero entry on stiffness diagonal for transformed phase' case (137) msg = 'not defined for lattice structure' case (138) @@ -431,8 +423,6 @@ subroutine IO_error(error_ID,el,ip,g,instance,ext_msg) ! material error messages and related messages in mesh case (150) msg = 'index out of bounds' - case (151) - msg = 'material has no constituents' case (153) msg = 'sum of phase fractions differs from 1' case (155) @@ -463,10 +453,6 @@ subroutine IO_error(error_ID,el,ip,g,instance,ext_msg) !-------------------------------------------------------------------------------------------------- ! math errors - case (400) - msg = 'matrix inversion error' - case (401) - msg = 'error in Eigenvalue calculation' case (402) msg = 'invalid orientation specified' @@ -499,14 +485,12 @@ subroutine IO_error(error_ID,el,ip,g,instance,ext_msg) !------------------------------------------------------------------------------------------------- ! errors related to the grid solver - case (809) - msg = 'initializing FFTW' - case (810) - msg = 'FFTW plan creation' case (831) msg = 'mask consistency violated in grid load case' case (832) msg = 'ill-defined L (line partly defined) in grid load case' + case (833) + msg = 'non-positive ratio for geometric progression' case (834) msg = 'negative time increment in grid load case' case (835) @@ -587,45 +571,20 @@ subroutine IO_warning(warning_ID,el,ip,g,ext_msg) character(len=pStringLen) :: formatString select case (warning_ID) - case (1) - msg = 'unknown key' - case (34) - msg = 'invalid restart increment given' - case (35) - msg = 'could not get $DAMASK_NUM_THREADS' - case (40) - msg = 'found spectral solver parameter' case (42) msg = 'parameter has no effect' - case (43) - msg = 'main diagonal of C66 close to zero' case (47) msg = 'no valid parameter for FFTW, using FFTW_PATIENT' - case (50) - msg = 'not all available slip system families are defined' - case (51) - msg = 'not all available twin system families are defined' - case (52) - msg = 'not all available parameters are defined' - case (53) - msg = 'not all available transformation system families are defined' - case (101) - msg = 'crystallite debugging off' - case (201) - msg = 'position not found when parsing line' case (207) msg = 'line truncated' case (600) msg = 'crystallite responds elastically' case (601) msg = 'stiffness close to zero' - case (650) - msg = 'polar decomposition failed' case (700) msg = 'unknown crystal symmetry' case (709) msg = 'read only the first document' - case (850) msg = 'max number of cut back exceeded, terminating' case default diff --git a/src/YAML_types.f90 b/src/YAML_types.f90 index f11faf54b..b71261d9c 100644 --- a/src/YAML_types.f90 +++ b/src/YAML_types.f90 @@ -583,9 +583,9 @@ function tNode_get_byIndex_asStrings(self,i) result(nodeAsStrings) end function tNode_get_byIndex_asStrings -!------------------------------------------------------------------------------------------------------- +!-------------------------------------------------------------------------------------------------- !> @brief Returns the key in a dictionary as a string -!------------------------------------------------------------------------------------------------------- +!-------------------------------------------------------------------------------------------------- function tNode_getKey_byIndex(self,i) result(key) class(tNode), intent(in), target :: self @@ -1169,17 +1169,21 @@ function tList_asStrings(self) type(tScalar), pointer :: scalar len_max = 0 - allocate(character(len=pStringLen) :: tList_asStrings(self%length)) + item => self%first + do i = 1, self%length + scalar => item%node%asScalar() + len_max = max(len_max, len_trim(scalar%asString())) + item => item%next + enddo + + allocate(character(len=len_max) :: tList_asStrings(self%length)) item => self%first do i = 1, self%length scalar => item%node%asScalar() tList_asStrings(i) = scalar%asString() - len_max = max(len_max, len_trim(tList_asStrings(i))) item => item%next enddo - !ToDo: trim to len_max - end function tList_asStrings diff --git a/src/commercialFEM_fileList.f90 b/src/commercialFEM_fileList.f90 index f4eb5d78c..d161b36eb 100644 --- a/src/commercialFEM_fileList.f90 +++ b/src/commercialFEM_fileList.f90 @@ -25,7 +25,7 @@ #include "material.f90" #include "lattice.f90" #include "constitutive.f90" -#include "constitutive_plastic.f90" +#include "constitutive_mech.f90" #include "constitutive_plastic_none.f90" #include "constitutive_plastic_isotropic.f90" #include "constitutive_plastic_phenopowerlaw.f90" diff --git a/src/constitutive.f90 b/src/constitutive.f90 index 64d3f2d31..470ac4dc7 100644 --- a/src/constitutive.f90 +++ b/src/constitutive.f90 @@ -19,7 +19,7 @@ module constitutive implicit none private - integer(kind(ELASTICITY_undefined_ID)), dimension(:), allocatable, protected :: & + integer(kind(ELASTICITY_undefined_ID)), dimension(:), allocatable :: & !ToDo: old intel compiler complains about protected phase_elasticity !< elasticity of each phase integer(kind(PLASTICITY_undefined_ID)), dimension(:), allocatable :: & !ToDo: old intel compiler complains about protected @@ -52,8 +52,8 @@ module constitutive interface - module subroutine plastic_init - end subroutine plastic_init + module subroutine mech_init + end subroutine mech_init module subroutine damage_init end subroutine damage_init @@ -127,7 +127,7 @@ module constitutive instance,of,ip,el) real(pReal), dimension(3,3), intent(in) :: & Mp !< MandelStress - real(pReal), dimension(3,3,homogenization_maxNgrains,discretization_nIP,discretization_nElem), intent(in) :: & + real(pReal), dimension(3,3,homogenization_maxNconstituents,discretization_nIPs,discretization_Nelems), intent(in) :: & F, & !< deformation gradient Fp !< plastic deformation gradient real(pReal), intent(in) :: & @@ -218,7 +218,7 @@ module constitutive instance, & i, & e - type(rotation), dimension(1,discretization_nIP,discretization_nElem), intent(in) :: & + type(rotation), dimension(1,discretization_nIPs,discretization_Nelems), intent(in) :: & orientation !< crystal orientation end subroutine plastic_nonlocal_updateCompatibility @@ -343,6 +343,25 @@ module constitutive end interface constitutive_dependentState + interface constitutive_SandItsTangents + + module subroutine constitutive_hooke_SandItsTangents(S, dS_dFe, dS_dFi, Fe, Fi, ipc, ip, el) + integer, intent(in) :: & + ipc, & !< component-ID of integration point + ip, & !< integration point + el !< element + real(pReal), intent(in), dimension(3,3) :: & + Fe, & !< elastic deformation gradient + Fi !< intermediate deformation gradient + real(pReal), intent(out), dimension(3,3) :: & + S !< 2nd Piola-Kirchhoff stress tensor + real(pReal), intent(out), dimension(3,3,3,3) :: & + dS_dFe, & !< derivative of 2nd P-K stress with respect to elastic deformation gradient + dS_dFi !< derivative of 2nd P-K stress with respect to intermediate deformation gradient + end subroutine constitutive_hooke_SandItsTangents + + end interface constitutive_SandItsTangents + type :: tDebugOptions logical :: & @@ -385,14 +404,10 @@ subroutine constitutive_init integer :: & p, & !< counter in phase loop - s, & !< counter in source loop - stiffDegradationCtr + s !< counter in source loop class (tNode), pointer :: & debug_constitutive, & - phases, & - phase, & - elastic, & - stiffDegradation + phases debug_constitutive => config_debug%get('constitutive', defaultVal=emptyList) debugConstitutive%basic = debug_constitutive%contains('basic') @@ -402,52 +417,15 @@ subroutine constitutive_init debugConstitutive%ip = config_debug%get_asInt('integrationpoint',defaultVal = 1) debugConstitutive%grain = config_debug%get_asInt('grain',defaultVal = 1) -!------------------------------------------------------------------------------------------------- -! initialize elasticity (hooke) !ToDO: Maybe move to elastic submodule along with function homogenizedC? - phases => config_material%get('phase') - allocate(phase_elasticity(phases%length), source = ELASTICITY_undefined_ID) - allocate(phase_elasticityInstance(phases%length), source = 0) - allocate(phase_NstiffnessDegradations(phases%length),source=0) - - do p = 1, phases%length - phase => phases%get(p) - elastic => phase%get('elasticity') - if(elastic%get_asString('type') == 'hooke') then - phase_elasticity(p) = ELASTICITY_HOOKE_ID - else - call IO_error(200,ext_msg=elastic%get_asString('type')) - endif - stiffDegradation => phase%get('stiffness_degradation',defaultVal=emptyList) ! check for stiffness degradation mechanisms - phase_NstiffnessDegradations(p) = stiffDegradation%length - enddo - - allocate(phase_stiffnessDegradation(maxval(phase_NstiffnessDegradations),phases%length), & - source=STIFFNESS_DEGRADATION_undefined_ID) - - if(maxVal(phase_NstiffnessDegradations)/=0) then - do p = 1, phases%length - phase => phases%get(p) - stiffDegradation => phase%get('stiffness_degradation',defaultVal=emptyList) - do stiffDegradationCtr = 1, stiffDegradation%length - if(stiffDegradation%get_asString(stiffDegradationCtr) == 'damage') & - phase_stiffnessDegradation(stiffDegradationCtr,p) = STIFFNESS_DEGRADATION_damage_ID - enddo - enddo - endif - - do p = 1, phases%length - phase_elasticityInstance(p) = count(phase_elasticity(1:p) == phase_elasticity(p)) - enddo - - !-------------------------------------------------------------------------------------------------- ! initialize constitutive laws - call plastic_init + call mech_init call damage_init call thermal_init print'(/,a)', ' <<<+- constitutive init -+>>>'; flush(IO_STDOUT) + phases => config_material%get('phase') constitutive_source_maxSizeDotState = 0 PhaseLoop2:do p = 1,phases%length !-------------------------------------------------------------------------------------------------- @@ -666,80 +644,6 @@ pure function constitutive_initialFi(ipc, ip, el) end function constitutive_initialFi -!-------------------------------------------------------------------------------------------------- -!> @brief returns the 2nd Piola-Kirchhoff stress tensor and its tangent with respect to -!> the elastic/intermediate deformation gradients depending on the selected elastic law -!! (so far no case switch because only Hooke is implemented) -!-------------------------------------------------------------------------------------------------- -subroutine constitutive_SandItsTangents(S, dS_dFe, dS_dFi, Fe, Fi, ipc, ip, el) - - integer, intent(in) :: & - ipc, & !< component-ID of integration point - ip, & !< integration point - el !< element - real(pReal), intent(in), dimension(3,3) :: & - Fe, & !< elastic deformation gradient - Fi !< intermediate deformation gradient - real(pReal), intent(out), dimension(3,3) :: & - S !< 2nd Piola-Kirchhoff stress tensor - real(pReal), intent(out), dimension(3,3,3,3) :: & - dS_dFe, & !< derivative of 2nd P-K stress with respect to elastic deformation gradient - dS_dFi !< derivative of 2nd P-K stress with respect to intermediate deformation gradient - - call constitutive_hooke_SandItsTangents(S, dS_dFe, dS_dFi, Fe, Fi, ipc, ip, el) - - -end subroutine constitutive_SandItsTangents - - -!-------------------------------------------------------------------------------------------------- -!> @brief returns the 2nd Piola-Kirchhoff stress tensor and its tangent with respect to -!> the elastic and intermediate deformation gradients using Hooke's law -!-------------------------------------------------------------------------------------------------- -subroutine constitutive_hooke_SandItsTangents(S, dS_dFe, dS_dFi, & - Fe, Fi, ipc, ip, el) - - integer, intent(in) :: & - ipc, & !< component-ID of integration point - ip, & !< integration point - el !< element - real(pReal), intent(in), dimension(3,3) :: & - Fe, & !< elastic deformation gradient - Fi !< intermediate deformation gradient - real(pReal), intent(out), dimension(3,3) :: & - S !< 2nd Piola-Kirchhoff stress tensor in lattice configuration - real(pReal), intent(out), dimension(3,3,3,3) :: & - dS_dFe, & !< derivative of 2nd P-K stress with respect to elastic deformation gradient - dS_dFi !< derivative of 2nd P-K stress with respect to intermediate deformation gradient - real(pReal), dimension(3,3) :: E - real(pReal), dimension(3,3,3,3) :: C - integer :: & - ho, & !< homogenization - d !< counter in degradation loop - integer :: & - i, j - - ho = material_homogenizationAt(el) - C = math_66toSym3333(constitutive_homogenizedC(ipc,ip,el)) - - DegradationLoop: do d = 1, phase_NstiffnessDegradations(material_phaseAt(ipc,el)) - degradationType: select case(phase_stiffnessDegradation(d,material_phaseAt(ipc,el))) - case (STIFFNESS_DEGRADATION_damage_ID) degradationType - C = C * damage(ho)%p(damageMapping(ho)%p(ip,el))**2 - end select degradationType - enddo DegradationLoop - - E = 0.5_pReal*(matmul(transpose(Fe),Fe)-math_I3) !< Green-Lagrange strain in unloaded configuration - S = math_mul3333xx33(C,matmul(matmul(transpose(Fi),E),Fi)) !< 2PK stress in lattice configuration in work conjugate with GL strain pulled back to lattice configuration - - do i =1, 3;do j=1,3 - dS_dFe(i,j,1:3,1:3) = matmul(Fe,matmul(matmul(Fi,C(i,j,1:3,1:3)),transpose(Fi))) !< dS_ij/dFe_kl = C_ijmn * Fi_lm * Fi_on * Fe_ko - dS_dFi(i,j,1:3,1:3) = 2.0_pReal*matmul(matmul(E,Fi),C(i,j,1:3,1:3)) !< dS_ij/dFi_kl = C_ijln * E_km * Fe_mn - enddo; enddo - -end subroutine constitutive_hooke_SandItsTangents - - !-------------------------------------------------------------------------------------------------- !> @brief contains the constitutive equation for calculating the rate of change of microstructure !-------------------------------------------------------------------------------------------------- @@ -753,7 +657,7 @@ function constitutive_collectDotState(S, FArray, Fi, FpArray, subdt, ipc, ip, el of real(pReal), intent(in) :: & subdt !< timestep - real(pReal), intent(in), dimension(3,3,homogenization_maxNgrains,discretization_nIP,discretization_nElem) :: & + real(pReal), intent(in), dimension(3,3,homogenization_maxNconstituents,discretization_nIPs,discretization_Nelems) :: & FArray, & !< elastic deformation gradient FpArray !< plastic deformation gradient real(pReal), intent(in), dimension(3,3) :: & @@ -905,12 +809,12 @@ end function constitutive_deltaState !> @brief Allocate the components of the state structure for a given phase !-------------------------------------------------------------------------------------------------- subroutine constitutive_allocateState(state, & - NipcMyPhase,sizeState,sizeDotState,sizeDeltaState) + Nconstituents,sizeState,sizeDotState,sizeDeltaState) class(tState), intent(out) :: & state integer, intent(in) :: & - NipcMyPhase, & + Nconstituents, & sizeState, & sizeDotState, & sizeDeltaState @@ -921,14 +825,14 @@ subroutine constitutive_allocateState(state, & state%offsetDeltaState = sizeState-sizeDeltaState ! deltaState occupies latter part of state by definition allocate(state%atol (sizeState), source=0.0_pReal) - allocate(state%state0 (sizeState,NipcMyPhase), source=0.0_pReal) - allocate(state%partitionedState0(sizeState,NipcMyPhase), source=0.0_pReal) - allocate(state%subState0 (sizeState,NipcMyPhase), source=0.0_pReal) - allocate(state%state (sizeState,NipcMyPhase), source=0.0_pReal) + allocate(state%state0 (sizeState,Nconstituents), source=0.0_pReal) + allocate(state%partitionedState0(sizeState,Nconstituents), source=0.0_pReal) + allocate(state%subState0 (sizeState,Nconstituents), source=0.0_pReal) + allocate(state%state (sizeState,Nconstituents), source=0.0_pReal) - allocate(state%dotState (sizeDotState,NipcMyPhase), source=0.0_pReal) + allocate(state%dotState (sizeDotState,Nconstituents), source=0.0_pReal) - allocate(state%deltaState(sizeDeltaState,NipcMyPhase), source=0.0_pReal) + allocate(state%deltaState(sizeDeltaState,Nconstituents), source=0.0_pReal) end subroutine constitutive_allocateState diff --git a/src/constitutive_damage.f90 b/src/constitutive_damage.f90 index 5572192f7..a864ca1b8 100644 --- a/src/constitutive_damage.f90 +++ b/src/constitutive_damage.f90 @@ -184,7 +184,7 @@ module subroutine constitutive_damage_getRateAndItsTangents(phiDot, dPhiDot_dPhi phiDot = 0.0_pReal dPhiDot_dPhi = 0.0_pReal - do grain = 1, homogenization_Ngrains(material_homogenizationAt(el)) + do grain = 1, homogenization_Nconstituents(material_homogenizationAt(el)) phase = material_phaseAt(grain,el) constituent = material_phasememberAt(grain,ip,el) do source = 1, phase_Nsources(phase) @@ -225,7 +225,7 @@ module subroutine damage_results do p = 1, size(material_name_phase) sourceLoop: do i = 1, phase_Nsources(p) - group = trim('current/constituent')//'/'//trim(material_name_phase(p)) + group = trim('current/phase')//'/'//trim(material_name_phase(p)) group = trim(group)//'/sources' call results_closeGroup(results_addGroup(group)) diff --git a/src/constitutive_plastic.f90 b/src/constitutive_mech.f90 similarity index 73% rename from src/constitutive_plastic.f90 rename to src/constitutive_mech.f90 index bf6bc079e..2a6bf97eb 100644 --- a/src/constitutive_plastic.f90 +++ b/src/constitutive_mech.f90 @@ -1,7 +1,7 @@ !---------------------------------------------------------------------------------------------------- !> @brief internal microstructure state for all plasticity constitutive models !---------------------------------------------------------------------------------------------------- -submodule(constitutive) constitutive_plastic +submodule(constitutive) constitutive_mech interface @@ -30,10 +30,10 @@ submodule(constitutive) constitutive_plastic myPlasticity end function plastic_dislotwin_init - module function plastic_disloTungsten_init() result(myPlasticity) + module function plastic_dislotungsten_init() result(myPlasticity) logical, dimension(:), allocatable :: & myPlasticity - end function plastic_disloTungsten_init + end function plastic_dislotungsten_init module function plastic_nonlocal_init() result(myPlasticity) logical, dimension(:), allocatable :: & @@ -94,7 +94,7 @@ submodule(constitutive) constitutive_plastic of end subroutine plastic_dislotwin_LpAndItsTangent - pure module subroutine plastic_disloTungsten_LpAndItsTangent(Lp,dLp_dMp,Mp,T,instance,of) + pure module subroutine plastic_dislotungsten_LpAndItsTangent(Lp,dLp_dMp,Mp,T,instance,of) real(pReal), dimension(3,3), intent(out) :: & Lp !< plastic velocity gradient real(pReal), dimension(3,3,3,3), intent(out) :: & @@ -107,7 +107,7 @@ submodule(constitutive) constitutive_plastic integer, intent(in) :: & instance, & of - end subroutine plastic_disloTungsten_LpAndItsTangent + end subroutine plastic_dislotungsten_LpAndItsTangent module subroutine plastic_nonlocal_LpAndItsTangent(Lp,dLp_dMp, & Mp,Temperature,instance,of,ip,el) @@ -136,11 +136,11 @@ submodule(constitutive) constitutive_plastic T end subroutine plastic_dislotwin_dependentState - module subroutine plastic_disloTungsten_dependentState(instance,of) + module subroutine plastic_dislotungsten_dependentState(instance,of) integer, intent(in) :: & instance, & of - end subroutine plastic_disloTungsten_dependentState + end subroutine plastic_dislotungsten_dependentState module subroutine plastic_nonlocal_dependentState(F, Fp, instance, of, ip, el) real(pReal), dimension(3,3), intent(in) :: & @@ -173,10 +173,10 @@ submodule(constitutive) constitutive_plastic character(len=*), intent(in) :: group end subroutine plastic_dislotwin_results - module subroutine plastic_disloTungsten_results(instance,group) + module subroutine plastic_dislotungsten_results(instance,group) integer, intent(in) :: instance character(len=*), intent(in) :: group - end subroutine plastic_disloTungsten_results + end subroutine plastic_dislotungsten_results module subroutine plastic_nonlocal_results(instance,group) integer, intent(in) :: instance @@ -191,17 +191,60 @@ contains !-------------------------------------------------------------------------------------------------- -!> @brief Initialize constitutive models for plasticity +!> @brief Initialize mechanical field related constitutive models +!> @details Initialize elasticity, plasticity and stiffness degradation models. !-------------------------------------------------------------------------------------------------- -module subroutine plastic_init +module subroutine mech_init - integer :: p - class(tNode), pointer :: phases + integer :: & + p, & + stiffDegradationCtr + class(tNode), pointer :: & + phases, & + phase, & + mech, & + elastic, & + stiffDegradation - print'(/,a)', ' <<<+- constitutive_plastic init -+>>>' + print'(/,a)', ' <<<+- constitutive_mech init -+>>>' +!------------------------------------------------------------------------------------------------- +! initialize elasticity (hooke) !ToDO: Maybe move to elastic submodule along with function homogenizedC? phases => config_material%get('phase') + allocate(phase_elasticity(phases%length), source = ELASTICITY_undefined_ID) + allocate(phase_elasticityInstance(phases%length), source = 0) + allocate(phase_NstiffnessDegradations(phases%length),source=0) + do p = 1, phases%length + phase => phases%get(p) + mech => phase%get('mechanics') + elastic => mech%get('elasticity') + if(elastic%get_asString('type') == 'hooke') then + phase_elasticity(p) = ELASTICITY_HOOKE_ID + else + call IO_error(200,ext_msg=elastic%get_asString('type')) + endif + stiffDegradation => mech%get('stiffness_degradation',defaultVal=emptyList) ! check for stiffness degradation mechanisms + phase_NstiffnessDegradations(p) = stiffDegradation%length + enddo + + allocate(phase_stiffnessDegradation(maxval(phase_NstiffnessDegradations),phases%length), & + source=STIFFNESS_DEGRADATION_undefined_ID) + + if(maxVal(phase_NstiffnessDegradations)/=0) then + do p = 1, phases%length + phase => phases%get(p) + mech => phase%get('mechanics') + stiffDegradation => mech%get('stiffness_degradation',defaultVal=emptyList) + do stiffDegradationCtr = 1, stiffDegradation%length + if(stiffDegradation%get_asString(stiffDegradationCtr) == 'damage') & + phase_stiffnessDegradation(stiffDegradationCtr,p) = STIFFNESS_DEGRADATION_damage_ID + enddo + enddo + endif + + +! initialize plasticity allocate(plasticState(phases%length)) allocate(phase_plasticity(phases%length),source = PLASTICITY_undefined_ID) allocate(phase_plasticityInstance(phases%length),source = 0) @@ -212,15 +255,15 @@ module subroutine plastic_init where(plastic_phenopowerlaw_init()) phase_plasticity = PLASTICITY_PHENOPOWERLAW_ID where(plastic_kinehardening_init()) phase_plasticity = PLASTICITY_KINEHARDENING_ID where(plastic_dislotwin_init()) phase_plasticity = PLASTICITY_DISLOTWIN_ID - where(plastic_disloTungsten_init()) phase_plasticity = PLASTICITY_DISLOTUNGSTEN_ID + where(plastic_dislotungsten_init()) phase_plasticity = PLASTICITY_DISLOTUNGSTEN_ID where(plastic_nonlocal_init()) phase_plasticity = PLASTICITY_NONLOCAL_ID do p = 1, phases%length + phase_elasticityInstance(p) = count(phase_elasticity(1:p) == phase_elasticity(p)) phase_plasticityInstance(p) = count(phase_plasticity(1:p) == phase_plasticity(p)) enddo - -end subroutine plastic_init +end subroutine mech_init !-------------------------------------------------------------------------------------------------- @@ -234,6 +277,7 @@ module function plastic_active(plastic_label) result(active_plastic) class(tNode), pointer :: & phases, & phase, & + mech, & pl integer :: p @@ -241,13 +285,62 @@ module function plastic_active(plastic_label) result(active_plastic) allocate(active_plastic(phases%length), source = .false. ) do p = 1, phases%length phase => phases%get(p) - pl => phase%get('plasticity') + mech => phase%get('mechanics') + pl => mech%get('plasticity') if(pl%get_asString('type') == plastic_label) active_plastic(p) = .true. enddo end function plastic_active +!-------------------------------------------------------------------------------------------------- +!> @brief returns the 2nd Piola-Kirchhoff stress tensor and its tangent with respect to +!> the elastic and intermediate deformation gradients using Hooke's law +!-------------------------------------------------------------------------------------------------- +module subroutine constitutive_hooke_SandItsTangents(S, dS_dFe, dS_dFi, & + Fe, Fi, ipc, ip, el) + + integer, intent(in) :: & + ipc, & !< component-ID of integration point + ip, & !< integration point + el !< element + real(pReal), intent(in), dimension(3,3) :: & + Fe, & !< elastic deformation gradient + Fi !< intermediate deformation gradient + real(pReal), intent(out), dimension(3,3) :: & + S !< 2nd Piola-Kirchhoff stress tensor in lattice configuration + real(pReal), intent(out), dimension(3,3,3,3) :: & + dS_dFe, & !< derivative of 2nd P-K stress with respect to elastic deformation gradient + dS_dFi !< derivative of 2nd P-K stress with respect to intermediate deformation gradient + real(pReal), dimension(3,3) :: E + real(pReal), dimension(3,3,3,3) :: C + integer :: & + ho, & !< homogenization + d !< counter in degradation loop + integer :: & + i, j + + ho = material_homogenizationAt(el) + C = math_66toSym3333(constitutive_homogenizedC(ipc,ip,el)) + + DegradationLoop: do d = 1, phase_NstiffnessDegradations(material_phaseAt(ipc,el)) + degradationType: select case(phase_stiffnessDegradation(d,material_phaseAt(ipc,el))) + case (STIFFNESS_DEGRADATION_damage_ID) degradationType + C = C * damage(ho)%p(damageMapping(ho)%p(ip,el))**2 + end select degradationType + enddo DegradationLoop + + E = 0.5_pReal*(matmul(transpose(Fe),Fe)-math_I3) !< Green-Lagrange strain in unloaded configuration + S = math_mul3333xx33(C,matmul(matmul(transpose(Fi),E),Fi)) !< 2PK stress in lattice configuration in work conjugate with GL strain pulled back to lattice configuration + + do i =1, 3;do j=1,3 + dS_dFe(i,j,1:3,1:3) = matmul(Fe,matmul(matmul(Fi,C(i,j,1:3,1:3)),transpose(Fi))) !< dS_ij/dFe_kl = C_ijmn * Fi_lm * Fi_on * Fe_ko + dS_dFi(i,j,1:3,1:3) = 2.0_pReal*matmul(matmul(E,Fi),C(i,j,1:3,1:3)) !< dS_ij/dFi_kl = C_ijln * E_km * Fe_mn + enddo; enddo + +end subroutine constitutive_hooke_SandItsTangents + + !-------------------------------------------------------------------------------------------------- !> @brief calls microstructure function of the different plasticity constitutive models !-------------------------------------------------------------------------------------------------- @@ -275,7 +368,7 @@ module subroutine constitutive_plastic_dependentState(F, Fp, ipc, ip, el) case (PLASTICITY_DISLOTWIN_ID) plasticityType call plastic_dislotwin_dependentState(temperature(ho)%p(tme),instance,of) case (PLASTICITY_DISLOTUNGSTEN_ID) plasticityType - call plastic_disloTungsten_dependentState(instance,of) + call plastic_dislotungsten_dependentState(instance,of) case (PLASTICITY_NONLOCAL_ID) plasticityType call plastic_nonlocal_dependentState (F,Fp,instance,of,ip,el) end select plasticityType @@ -342,7 +435,7 @@ module subroutine constitutive_plastic_LpAndItsTangents(Lp, dLp_dS, dLp_dFi, & call plastic_dislotwin_LpAndItsTangent(Lp,dLp_dMp,Mp,temperature(ho)%p(tme),instance,of) case (PLASTICITY_DISLOTUNGSTEN_ID) plasticityType - call plastic_disloTungsten_LpAndItsTangent(Lp,dLp_dMp,Mp,temperature(ho)%p(tme),instance,of) + call plastic_dislotungsten_LpAndItsTangent(Lp,dLp_dMp,Mp,temperature(ho)%p(tme),instance,of) end select plasticityType @@ -364,7 +457,7 @@ module subroutine plastic_results character(len=pStringLen) :: group plasticityLoop: do p=1,size(material_name_phase) - group = trim('current/constituent')//'/'//trim(material_name_phase(p)) + group = trim('current/phase')//'/'//trim(material_name_phase(p)) call results_closeGroup(results_addGroup(group)) group = trim(group)//'/plastic' @@ -385,7 +478,7 @@ module subroutine plastic_results call plastic_dislotwin_results(phase_plasticityInstance(p),group) case(PLASTICITY_DISLOTUNGSTEN_ID) - call plastic_disloTungsten_results(phase_plasticityInstance(p),group) + call plastic_dislotungsten_results(phase_plasticityInstance(p),group) case(PLASTICITY_NONLOCAL_ID) call plastic_nonlocal_results(phase_plasticityInstance(p),group) @@ -395,6 +488,5 @@ module subroutine plastic_results end subroutine plastic_results - -end submodule constitutive_plastic +end submodule constitutive_mech diff --git a/src/constitutive_plastic_disloTungsten.f90 b/src/constitutive_plastic_disloTungsten.f90 index 54c01b912..c39ae5c2b 100644 --- a/src/constitutive_plastic_disloTungsten.f90 +++ b/src/constitutive_plastic_disloTungsten.f90 @@ -5,7 +5,7 @@ !> @author Martin Diehl, Max-Planck-Institut für Eisenforschung GmbH !> @brief crystal plasticity model for bcc metals, especially Tungsten !-------------------------------------------------------------------------------------------------- -submodule(constitutive:constitutive_plastic) plastic_disloTungsten +submodule(constitutive:constitutive_mech) plastic_dislotungsten real(pReal), parameter :: & kB = 1.38e-23_pReal !< Boltzmann constant in J/Kelvin @@ -74,13 +74,13 @@ contains !> @brief Perform module initialization. !> @details reads in material parameters, allocates arrays, and does sanity checks !-------------------------------------------------------------------------------------------------- -module function plastic_disloTungsten_init() result(myPlasticity) +module function plastic_dislotungsten_init() result(myPlasticity) logical, dimension(:), allocatable :: myPlasticity integer :: & - Ninstance, & + Ninstances, & p, i, & - NipcMyPhase, & + Nconstituents, & sizeState, sizeDotState, & startIndex, endIndex integer, dimension(:), allocatable :: & @@ -94,35 +94,36 @@ module function plastic_disloTungsten_init() result(myPlasticity) class(tNode), pointer :: & phases, & phase, & + mech, & pl print'(/,a)', ' <<<+- plastic_dislotungsten init -+>>>' - myPlasticity = plastic_active('disloTungsten') - Ninstance = count(myPlasticity) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + myPlasticity = plastic_active('dislotungsten') + Ninstances = count(myPlasticity) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return print*, 'Cereceda et al., International Journal of Plasticity 78:242–256, 2016' print*, 'https://dx.doi.org/10.1016/j.ijplas.2015.09.002' - allocate(param(Ninstance)) - allocate(state(Ninstance)) - allocate(dotState(Ninstance)) - allocate(dependentState(Ninstance)) + allocate(param(Ninstances)) + allocate(state(Ninstances)) + allocate(dotState(Ninstances)) + allocate(dependentState(Ninstances)) phases => config_material%get('phase') i = 0 do p = 1, phases%length phase => phases%get(p) - + mech => phase%get('mechanics') if(.not. myPlasticity(p)) cycle i = i + 1 associate(prm => param(i), & dot => dotState(i), & stt => state(i), & dst => dependentState(i)) - pl => phase%get('plasticity') + pl => mech%get('plasticity') #if defined (__GFORTRAN__) prm%output = output_asStrings(pl) @@ -141,7 +142,7 @@ module function plastic_disloTungsten_init() result(myPlasticity) prm%P_sl = lattice_SchmidMatrix_slip(N_sl,phase%get_asString('lattice'),& phase%get_asFloat('c/a',defaultVal=0.0_pReal)) - if(trim(phase%get_asString('lattice')) == 'bcc') then + if(trim(phase%get_asString('lattice')) == 'cI') then a = pl%get_asFloats('a_nonSchmid',defaultVal = emptyRealArray) prm%nonSchmid_pos = lattice_nonSchmidMatrix(N_sl,a,+1) prm%nonSchmid_neg = lattice_nonSchmidMatrix(N_sl,a,-1) @@ -179,7 +180,7 @@ module function plastic_disloTungsten_init() result(myPlasticity) prm%f_at = pl%get_asFloat('f_at') * prm%b_sl**3.0_pReal prm%D_a = pl%get_asFloat('D_a') * prm%b_sl - prm%dipoleformation = pl%get_asBool('dipole_formation_factor', defaultVal = .true.) + prm%dipoleformation = .not. pl%get_asBool('no_dipole_formation', defaultVal = .false.) ! expand: family => system rho_mob_0 = math_expand(rho_mob_0, N_sl) @@ -221,18 +222,18 @@ module function plastic_disloTungsten_init() result(myPlasticity) !-------------------------------------------------------------------------------------------------- ! allocate state arrays - NipcMyPhase = count(material_phaseAt == p) * discretization_nIP + Nconstituents = count(material_phaseAt == p) * discretization_nIPs sizeDotState = size(['rho_mob ','rho_dip ','gamma_sl']) * prm%sum_N_sl sizeState = sizeDotState - call constitutive_allocateState(plasticState(p),NipcMyPhase,sizeState,sizeDotState,0) + call constitutive_allocateState(plasticState(p),Nconstituents,sizeState,sizeDotState,0) !-------------------------------------------------------------------------------------------------- ! state aliases and initialization startIndex = 1 endIndex = prm%sum_N_sl stt%rho_mob => plasticState(p)%state(startIndex:endIndex,:) - stt%rho_mob = spread(rho_mob_0,2,NipcMyPhase) + stt%rho_mob = spread(rho_mob_0,2,Nconstituents) dot%rho_mob => plasticState(p)%dotState(startIndex:endIndex,:) plasticState(p)%atol(startIndex:endIndex) = pl%get_asFloat('atol_rho',defaultVal=1.0_pReal) if (any(plasticState(p)%atol(startIndex:endIndex) < 0.0_pReal)) extmsg = trim(extmsg)//' atol_rho' @@ -240,7 +241,7 @@ module function plastic_disloTungsten_init() result(myPlasticity) startIndex = endIndex + 1 endIndex = endIndex + prm%sum_N_sl stt%rho_dip => plasticState(p)%state(startIndex:endIndex,:) - stt%rho_dip = spread(rho_dip_0,2,NipcMyPhase) + stt%rho_dip = spread(rho_dip_0,2,Nconstituents) dot%rho_dip => plasticState(p)%dotState(startIndex:endIndex,:) plasticState(p)%atol(startIndex:endIndex) = pl%get_asFloat('atol_rho',defaultVal=1.0_pReal) @@ -252,8 +253,8 @@ module function plastic_disloTungsten_init() result(myPlasticity) ! global alias plasticState(p)%slipRate => plasticState(p)%dotState(startIndex:endIndex,:) - allocate(dst%Lambda_sl(prm%sum_N_sl,NipcMyPhase), source=0.0_pReal) - allocate(dst%threshold_stress(prm%sum_N_sl,NipcMyPhase), source=0.0_pReal) + allocate(dst%Lambda_sl(prm%sum_N_sl,Nconstituents), source=0.0_pReal) + allocate(dst%threshold_stress(prm%sum_N_sl,Nconstituents), source=0.0_pReal) plasticState(p)%state0 = plasticState(p)%state ! ToDo: this could be done centrally @@ -261,17 +262,17 @@ module function plastic_disloTungsten_init() result(myPlasticity) !-------------------------------------------------------------------------------------------------- ! exit if any parameter is out of range - if (extmsg /= '') call IO_error(211,ext_msg=trim(extmsg)//'(disloTungsten)') + if (extmsg /= '') call IO_error(211,ext_msg=trim(extmsg)//'(dislotungsten)') enddo -end function plastic_disloTungsten_init +end function plastic_dislotungsten_init !-------------------------------------------------------------------------------------------------- !> @brief Calculate plastic velocity gradient and its tangent. !-------------------------------------------------------------------------------------------------- -pure module subroutine plastic_disloTungsten_LpAndItsTangent(Lp,dLp_dMp, & +pure module subroutine plastic_dislotungsten_LpAndItsTangent(Lp,dLp_dMp, & Mp,T,instance,of) real(pReal), dimension(3,3), intent(out) :: & Lp !< plastic velocity gradient @@ -308,13 +309,13 @@ pure module subroutine plastic_disloTungsten_LpAndItsTangent(Lp,dLp_dMp, & end associate -end subroutine plastic_disloTungsten_LpAndItsTangent +end subroutine plastic_dislotungsten_LpAndItsTangent !-------------------------------------------------------------------------------------------------- !> @brief Calculate the rate of change of microstructure. !-------------------------------------------------------------------------------------------------- -module subroutine plastic_disloTungsten_dotState(Mp,T,instance,of) +module subroutine plastic_dislotungsten_dotState(Mp,T,instance,of) real(pReal), dimension(3,3), intent(in) :: & Mp !< Mandel stress @@ -368,13 +369,13 @@ module subroutine plastic_disloTungsten_dotState(Mp,T,instance,of) end associate -end subroutine plastic_disloTungsten_dotState +end subroutine plastic_dislotungsten_dotState !-------------------------------------------------------------------------------------------------- !> @brief Calculate derived quantities from state. !-------------------------------------------------------------------------------------------------- -module subroutine plastic_disloTungsten_dependentState(instance,of) +module subroutine plastic_dislotungsten_dependentState(instance,of) integer, intent(in) :: & instance, & @@ -393,13 +394,13 @@ module subroutine plastic_disloTungsten_dependentState(instance,of) end associate -end subroutine plastic_disloTungsten_dependentState +end subroutine plastic_dislotungsten_dependentState !-------------------------------------------------------------------------------------------------- !> @brief Write results to HDF5 output file. !-------------------------------------------------------------------------------------------------- -module subroutine plastic_disloTungsten_results(instance,group) +module subroutine plastic_dislotungsten_results(instance,group) integer, intent(in) :: instance character(len=*), intent(in) :: group @@ -428,7 +429,7 @@ module subroutine plastic_disloTungsten_results(instance,group) enddo outputsLoop end associate -end subroutine plastic_disloTungsten_results +end subroutine plastic_dislotungsten_results !-------------------------------------------------------------------------------------------------- @@ -546,4 +547,4 @@ pure subroutine kinetics(Mp,T,instance,of, & end subroutine kinetics -end submodule plastic_disloTungsten +end submodule plastic_dislotungsten diff --git a/src/constitutive_plastic_dislotwin.f90 b/src/constitutive_plastic_dislotwin.f90 index 62dcdd83e..4234a55b8 100644 --- a/src/constitutive_plastic_dislotwin.f90 +++ b/src/constitutive_plastic_dislotwin.f90 @@ -7,7 +7,7 @@ !> @brief material subroutine incoprorating dislocation and twinning physics !> @details to be done !-------------------------------------------------------------------------------------------------- -submodule(constitutive:constitutive_plastic) plastic_dislotwin +submodule(constitutive:constitutive_mech) plastic_dislotwin real(pReal), parameter :: & kB = 1.38e-23_pReal !< Boltzmann constant in J/Kelvin @@ -126,9 +126,9 @@ module function plastic_dislotwin_init() result(myPlasticity) logical, dimension(:), allocatable :: myPlasticity integer :: & - Ninstance, & + Ninstances, & p, i, & - NipcMyPhase, & + Nconstituents, & sizeState, sizeDotState, & startIndex, endIndex integer, dimension(:), allocatable :: & @@ -141,14 +141,15 @@ module function plastic_dislotwin_init() result(myPlasticity) class(tNode), pointer :: & phases, & phase, & + mech, & pl print'(/,a)', ' <<<+- plastic_dislotwin init -+>>>' myPlasticity = plastic_active('dislotwin') - Ninstance = count(myPlasticity) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(myPlasticity) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return print*, 'Ma and Roters, Acta Materialia 52(12):3603–3612, 2004' print*, 'https://doi.org/10.1016/j.actamat.2004.04.012'//IO_EOL @@ -159,23 +160,23 @@ module function plastic_dislotwin_init() result(myPlasticity) print*, 'Wong et al., Acta Materialia 118:140–151, 2016' print*, 'https://doi.org/10.1016/j.actamat.2016.07.032' - allocate(param(Ninstance)) - allocate(state(Ninstance)) - allocate(dotState(Ninstance)) - allocate(dependentState(Ninstance)) + allocate(param(Ninstances)) + allocate(state(Ninstances)) + allocate(dotState(Ninstances)) + allocate(dependentState(Ninstances)) phases => config_material%get('phase') i = 0 do p = 1, phases%length phase => phases%get(p) - + mech => phase%get('mechanics') if(.not. myPlasticity(p)) cycle i = i + 1 associate(prm => param(i), & dot => dotState(i), & stt => state(i), & dst => dependentState(i)) - pl => phase%get('plasticity') + pl => mech%get('plasticity') #if defined (__GFORTRAN__) prm%output = output_asStrings(pl) @@ -300,7 +301,7 @@ module function plastic_dislotwin_init() result(myPlasticity) prm%r = math_expand(prm%r,N_tw) ! sanity checks - if ( prm%x_c_tw < 0.0_pReal) extmsg = trim(extmsg)//' x_c_twin' + if ( prm%x_c_tw < 0.0_pReal) extmsg = trim(extmsg)//' x_c_tw' if ( prm%L_tw < 0.0_pReal) extmsg = trim(extmsg)//' L_tw' if ( prm%i_tw < 0.0_pReal) extmsg = trim(extmsg)//' i_tw' if (any(prm%b_tw < 0.0_pReal)) extmsg = trim(extmsg)//' b_tw' @@ -331,15 +332,15 @@ module function plastic_dislotwin_init() result(myPlasticity) prm%h_tr_tr = lattice_interaction_TransByTrans(N_tr,pl%get_asFloats('h_tr_tr'), & phase%get_asString('lattice')) - prm%C66_tr = lattice_C66_trans(N_tr,prm%C66,pl%get_asString('trans_lattice_structure'), & + prm%C66_tr = lattice_C66_trans(N_tr,prm%C66,pl%get_asString('lattice_tr'), & 0.0_pReal, & - pl%get_asFloat('a_bcc', defaultVal=0.0_pReal), & - pl%get_asFloat('a_fcc', defaultVal=0.0_pReal)) + pl%get_asFloat('a_cI', defaultVal=0.0_pReal), & + pl%get_asFloat('a_cF', defaultVal=0.0_pReal)) - prm%P_tr = lattice_SchmidMatrix_trans(N_tr,pl%get_asString('trans_lattice_structure'), & + prm%P_tr = lattice_SchmidMatrix_trans(N_tr,pl%get_asString('lattice_tr'), & 0.0_pReal, & - pl%get_asFloat('a_bcc', defaultVal=0.0_pReal), & - pl%get_asFloat('a_fcc', defaultVal=0.0_pReal)) + pl%get_asFloat('a_cI', defaultVal=0.0_pReal), & + pl%get_asFloat('a_cF', defaultVal=0.0_pReal)) if (lattice_structure(p) /= lattice_FCC_ID) then prm%dot_N_0_tr = pl%get_asFloats('dot_N_0_tr') @@ -351,7 +352,7 @@ module function plastic_dislotwin_init() result(myPlasticity) prm%s = math_expand(prm%s,N_tr) ! sanity checks - if ( prm%x_c_tr < 0.0_pReal) extmsg = trim(extmsg)//' x_c_trans' + if ( prm%x_c_tr < 0.0_pReal) extmsg = trim(extmsg)//' x_c_tr' if ( prm%L_tr < 0.0_pReal) extmsg = trim(extmsg)//' L_tr' if ( prm%i_tr < 0.0_pReal) extmsg = trim(extmsg)//' i_tr' if (any(prm%t_tr < 0.0_pReal)) extmsg = trim(extmsg)//' t_tr' @@ -407,21 +408,21 @@ module function plastic_dislotwin_init() result(myPlasticity) !-------------------------------------------------------------------------------------------------- ! allocate state arrays - NipcMyPhase = count(material_phaseAt == p) * discretization_nIP + Nconstituents = count(material_phaseAt == p) * discretization_nIPs sizeDotState = size(['rho_mob ','rho_dip ','gamma_sl']) * prm%sum_N_sl & + size(['f_tw']) * prm%sum_N_tw & + size(['f_tr']) * prm%sum_N_tr sizeState = sizeDotState - call constitutive_allocateState(plasticState(p),NipcMyPhase,sizeState,sizeDotState,0) + call constitutive_allocateState(plasticState(p),Nconstituents,sizeState,sizeDotState,0) !-------------------------------------------------------------------------------------------------- ! locally defined state aliases and initialization of state0 and atol startIndex = 1 endIndex = prm%sum_N_sl stt%rho_mob=>plasticState(p)%state(startIndex:endIndex,:) - stt%rho_mob= spread(rho_mob_0,2,NipcMyPhase) + stt%rho_mob= spread(rho_mob_0,2,Nconstituents) dot%rho_mob=>plasticState(p)%dotState(startIndex:endIndex,:) plasticState(p)%atol(startIndex:endIndex) = pl%get_asFloat('atol_rho',defaultVal=1.0_pReal) if (any(plasticState(p)%atol(startIndex:endIndex) < 0.0_pReal)) extmsg = trim(extmsg)//' atol_rho' @@ -429,7 +430,7 @@ module function plastic_dislotwin_init() result(myPlasticity) startIndex = endIndex + 1 endIndex = endIndex + prm%sum_N_sl stt%rho_dip=>plasticState(p)%state(startIndex:endIndex,:) - stt%rho_dip= spread(rho_dip_0,2,NipcMyPhase) + stt%rho_dip= spread(rho_dip_0,2,Nconstituents) dot%rho_dip=>plasticState(p)%dotState(startIndex:endIndex,:) plasticState(p)%atol(startIndex:endIndex) = pl%get_asFloat('atol_rho',defaultVal=1.0_pReal) @@ -455,18 +456,18 @@ module function plastic_dislotwin_init() result(myPlasticity) plasticState(p)%atol(startIndex:endIndex) = pl%get_asFloat('f_trans',defaultVal=1.0e-6_pReal) if (any(plasticState(p)%atol(startIndex:endIndex) < 0.0_pReal)) extmsg = trim(extmsg)//' f_trans' - allocate(dst%Lambda_sl (prm%sum_N_sl,NipcMyPhase),source=0.0_pReal) - allocate(dst%tau_pass (prm%sum_N_sl,NipcMyPhase),source=0.0_pReal) + allocate(dst%Lambda_sl (prm%sum_N_sl,Nconstituents),source=0.0_pReal) + allocate(dst%tau_pass (prm%sum_N_sl,Nconstituents),source=0.0_pReal) - allocate(dst%Lambda_tw (prm%sum_N_tw,NipcMyPhase),source=0.0_pReal) - allocate(dst%tau_hat_tw (prm%sum_N_tw,NipcMyPhase),source=0.0_pReal) - allocate(dst%tau_r_tw (prm%sum_N_tw,NipcMyPhase),source=0.0_pReal) - allocate(dst%V_tw (prm%sum_N_tw,NipcMyPhase),source=0.0_pReal) + allocate(dst%Lambda_tw (prm%sum_N_tw,Nconstituents),source=0.0_pReal) + allocate(dst%tau_hat_tw (prm%sum_N_tw,Nconstituents),source=0.0_pReal) + allocate(dst%tau_r_tw (prm%sum_N_tw,Nconstituents),source=0.0_pReal) + allocate(dst%V_tw (prm%sum_N_tw,Nconstituents),source=0.0_pReal) - allocate(dst%Lambda_tr (prm%sum_N_tr,NipcMyPhase),source=0.0_pReal) - allocate(dst%tau_hat_tr (prm%sum_N_tr,NipcMyPhase),source=0.0_pReal) - allocate(dst%tau_r_tr (prm%sum_N_tr,NipcMyPhase),source=0.0_pReal) - allocate(dst%V_tr (prm%sum_N_tr,NipcMyPhase),source=0.0_pReal) + allocate(dst%Lambda_tr (prm%sum_N_tr,Nconstituents),source=0.0_pReal) + allocate(dst%tau_hat_tr (prm%sum_N_tr,Nconstituents),source=0.0_pReal) + allocate(dst%tau_r_tr (prm%sum_N_tr,Nconstituents),source=0.0_pReal) + allocate(dst%V_tr (prm%sum_N_tr,Nconstituents),source=0.0_pReal) plasticState(p)%state0 = plasticState(p)%state ! ToDo: this could be done centrally diff --git a/src/constitutive_plastic_isotropic.f90 b/src/constitutive_plastic_isotropic.f90 index 2c9028671..b7c5f67c1 100644 --- a/src/constitutive_plastic_isotropic.f90 +++ b/src/constitutive_plastic_isotropic.f90 @@ -7,7 +7,7 @@ !! resolving the stress on the slip systems. Will give the response of phenopowerlaw for an !! untextured polycrystal !-------------------------------------------------------------------------------------------------- -submodule(constitutive:constitutive_plastic) plastic_isotropic +submodule(constitutive:constitutive_mech) plastic_isotropic type :: tParameters real(pReal) :: & @@ -53,10 +53,10 @@ module function plastic_isotropic_init() result(myPlasticity) logical, dimension(:), allocatable :: myPlasticity integer :: & - Ninstance, & + Ninstances, & p, & i, & - NipcMyPhase, & + Nconstituents, & sizeState, sizeDotState real(pReal) :: & xi_0 !< initial critical stress @@ -65,33 +65,34 @@ module function plastic_isotropic_init() result(myPlasticity) class(tNode), pointer :: & phases, & phase, & + mech, & pl print'(/,a)', ' <<<+- plastic_isotropic init -+>>>' myPlasticity = plastic_active('isotropic') - Ninstance = count(myPlasticity) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(myPlasticity) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return print*, 'Maiti and Eisenlohr, Scripta Materialia 145:37–40, 2018' print*, 'https://doi.org/10.1016/j.scriptamat.2017.09.047' - allocate(param(Ninstance)) - allocate(state(Ninstance)) - allocate(dotState(Ninstance)) + allocate(param(Ninstances)) + allocate(state(Ninstances)) + allocate(dotState(Ninstances)) phases => config_material%get('phase') i = 0 do p = 1, phases%length phase => phases%get(p) - + mech => phase%get('mechanics') if(.not. myPlasticity(p)) cycle i = i + 1 associate(prm => param(i), & dot => dotState(i), & stt => state(i)) - pl => phase%get('plasticity') + pl => mech%get('plasticity') #if defined (__GFORTRAN__) @@ -130,11 +131,11 @@ module function plastic_isotropic_init() result(myPlasticity) !-------------------------------------------------------------------------------------------------- ! allocate state arrays - NipcMyPhase = count(material_phaseAt == p) * discretization_nIP + Nconstituents = count(material_phaseAt == p) * discretization_nIPs sizeDotState = size(['xi ','gamma']) sizeState = sizeDotState - call constitutive_allocateState(plasticState(p),NipcMyPhase,sizeState,sizeDotState,0) + call constitutive_allocateState(plasticState(p),Nconstituents,sizeState,sizeDotState,0) !-------------------------------------------------------------------------------------------------- ! state aliases and initialization diff --git a/src/constitutive_plastic_kinehardening.f90 b/src/constitutive_plastic_kinehardening.f90 index 777691242..8454b28f8 100644 --- a/src/constitutive_plastic_kinehardening.f90 +++ b/src/constitutive_plastic_kinehardening.f90 @@ -5,7 +5,7 @@ !> @brief Phenomenological crystal plasticity using a power law formulation for the shear rates !! and a Voce-type kinematic hardening rule !-------------------------------------------------------------------------------------------------- -submodule(constitutive:constitutive_plastic) plastic_kinehardening +submodule(constitutive:constitutive_mech) plastic_kinehardening type :: tParameters real(pReal) :: & @@ -62,9 +62,9 @@ module function plastic_kinehardening_init() result(myPlasticity) logical, dimension(:), allocatable :: myPlasticity integer :: & - Ninstance, & + Ninstances, & p, i, o, & - NipcMyPhase, & + Nconstituents, & sizeState, sizeDeltaState, sizeDotState, & startIndex, endIndex integer, dimension(:), allocatable :: & @@ -77,32 +77,33 @@ module function plastic_kinehardening_init() result(myPlasticity) class(tNode), pointer :: & phases, & phase, & + mech, & pl print'(/,a)', ' <<<+- plastic_kinehardening init -+>>>' myPlasticity = plastic_active('kinehardening') - Ninstance = count(myPlasticity) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(myPlasticity) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return - allocate(param(Ninstance)) - allocate(state(Ninstance)) - allocate(dotState(Ninstance)) - allocate(deltaState(Ninstance)) + allocate(param(Ninstances)) + allocate(state(Ninstances)) + allocate(dotState(Ninstances)) + allocate(deltaState(Ninstances)) phases => config_material%get('phase') i = 0 do p = 1, phases%length phase => phases%get(p) - + mech => phase%get('mechanics') if(.not. myPlasticity(p)) cycle i = i + 1 associate(prm => param(i), & dot => dotState(i), & dlt => deltaState(i), & stt => state(i)) - pl => phase%get('plasticity') + pl => mech%get('plasticity') #if defined (__GFORTRAN__) prm%output = output_asStrings(pl) @@ -124,7 +125,7 @@ module function plastic_kinehardening_init() result(myPlasticity) prm%P = lattice_SchmidMatrix_slip(N_sl,phase%get_asString('lattice'),& phase%get_asFloat('c/a',defaultVal=0.0_pReal)) - if(trim(phase%get_asString('lattice')) == 'bcc') then + if(trim(phase%get_asString('lattice')) == 'cI') then a = pl%get_asFloats('a_nonSchmid',defaultVal = emptyRealArray) if(size(a) > 0) prm%nonSchmidActive = .true. prm%nonSchmid_pos = lattice_nonSchmidMatrix(N_sl,a,+1) @@ -174,19 +175,19 @@ module function plastic_kinehardening_init() result(myPlasticity) !-------------------------------------------------------------------------------------------------- ! allocate state arrays - NipcMyPhase = count(material_phaseAt == p) * discretization_nIP + Nconstituents = count(material_phaseAt == p) * discretization_nIPs sizeDotState = size(['crss ','crss_back', 'accshear ']) * prm%sum_N_sl!ToDo: adjust names, ask Philip sizeDeltaState = size(['sense ', 'chi0 ', 'gamma0' ]) * prm%sum_N_sl !ToDo: adjust names sizeState = sizeDotState + sizeDeltaState - call constitutive_allocateState(plasticState(p),NipcMyPhase,sizeState,sizeDotState,sizeDeltaState) + call constitutive_allocateState(plasticState(p),Nconstituents,sizeState,sizeDotState,sizeDeltaState) !-------------------------------------------------------------------------------------------------- ! state aliases and initialization startIndex = 1 endIndex = prm%sum_N_sl stt%crss => plasticState(p)%state (startIndex:endIndex,:) - stt%crss = spread(xi_0, 2, NipcMyPhase) + stt%crss = spread(xi_0, 2, Nconstituents) dot%crss => plasticState(p)%dotState(startIndex:endIndex,:) plasticState(p)%atol(startIndex:endIndex) = pl%get_asFloat('atol_xi',defaultVal=1.0_pReal) if(any(plasticState(p)%atol(startIndex:endIndex) < 0.0_pReal)) extmsg = trim(extmsg)//' atol_xi' diff --git a/src/constitutive_plastic_none.f90 b/src/constitutive_plastic_none.f90 index ab5f32d3f..27a01fb93 100644 --- a/src/constitutive_plastic_none.f90 +++ b/src/constitutive_plastic_none.f90 @@ -4,7 +4,7 @@ !> @author Martin Diehl, Max-Planck-Institut für Eisenforschung GmbH !> @brief Dummy plasticity for purely elastic material !-------------------------------------------------------------------------------------------------- -submodule(constitutive:constitutive_plastic) plastic_none +submodule(constitutive:constitutive_mech) plastic_none contains @@ -16,12 +16,13 @@ module function plastic_none_init() result(myPlasticity) logical, dimension(:), allocatable :: myPlasticity integer :: & - Ninstance, & + Ninstances, & p, & - NipcMyPhase + Nconstituents class(tNode), pointer :: & phases, & phase, & + mech, & pl print'(/,a)', ' <<<+- plastic_none init -+>>>' @@ -30,19 +31,20 @@ module function plastic_none_init() result(myPlasticity) allocate(myPlasticity(phases%length), source = .false.) do p = 1, phases%length phase => phases%get(p) - pl => phase%get('plasticity') + mech => phase%get('mechanics') + pl => mech%get ('plasticity') if(pl%get_asString('type') == 'none') myPlasticity(p) = .true. enddo - Ninstance = count(myPlasticity) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(myPlasticity) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return do p = 1, phases%length phase => phases%get(p) if(.not. myPlasticity(p)) cycle - NipcMyPhase = count(material_phaseAt == p) * discretization_nIP - call constitutive_allocateState(plasticState(p),NipcMyPhase,0,0,0) + Nconstituents = count(material_phaseAt == p) * discretization_nIPs + call constitutive_allocateState(plasticState(p),Nconstituents,0,0,0) enddo end function plastic_none_init diff --git a/src/constitutive_plastic_nonlocal.f90 b/src/constitutive_plastic_nonlocal.f90 index 8238f17c9..ce9e4e391 100644 --- a/src/constitutive_plastic_nonlocal.f90 +++ b/src/constitutive_plastic_nonlocal.f90 @@ -4,7 +4,7 @@ !> @author Philip Eisenlohr, Max-Planck-Institut für Eisenforschung GmbH !> @brief material subroutine for plasticity including dislocation flux !-------------------------------------------------------------------------------------------------- -submodule(constitutive:constitutive_plastic) plastic_nonlocal +submodule(constitutive:constitutive_mech) plastic_nonlocal use geometry_plastic_nonlocal, only: & nIPneighbors => geometry_plastic_nonlocal_nIPneighbors, & IPneighborhood => geometry_plastic_nonlocal_IPneighborhood, & @@ -153,7 +153,7 @@ submodule(constitutive:constitutive_plastic) plastic_nonlocal state, & state0 - type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstance) + type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstances) type(tNonlocalMicrostructure), dimension(:), allocatable :: microstructure @@ -168,9 +168,9 @@ module function plastic_nonlocal_init() result(myPlasticity) logical, dimension(:), allocatable :: myPlasticity integer :: & - Ninstance, & + Ninstances, & p, i, & - NipcMyPhase, & + Nconstituents, & sizeState, sizeDotState, sizeDependentState, sizeDeltaState, & s1, s2, & s, t, l @@ -183,14 +183,15 @@ module function plastic_nonlocal_init() result(myPlasticity) class(tNode), pointer :: & phases, & phase, & + mech, & pl print'(/,a)', ' <<<+- plastic_nonlocal init -+>>>' myPlasticity = plastic_active('nonlocal') - Ninstance = count(myPlasticity) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) then + Ninstances = count(myPlasticity) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) then call geometry_plastic_nonlocal_disable return endif @@ -201,18 +202,18 @@ module function plastic_nonlocal_init() result(myPlasticity) print*, 'Kords, Dissertation RWTH Aachen, 2014' print*, 'http://publications.rwth-aachen.de/record/229993' - allocate(param(Ninstance)) - allocate(state(Ninstance)) - allocate(state0(Ninstance)) - allocate(dotState(Ninstance)) - allocate(deltaState(Ninstance)) - allocate(microstructure(Ninstance)) + allocate(param(Ninstances)) + allocate(state(Ninstances)) + allocate(state0(Ninstances)) + allocate(dotState(Ninstances)) + allocate(deltaState(Ninstances)) + allocate(microstructure(Ninstances)) phases => config_material%get('phase') i = 0 do p = 1, phases%length phase => phases%get(p) - + mech => phase%get('mechanics') if(.not. myPlasticity(p)) cycle i = i + 1 associate(prm => param(i), & @@ -221,7 +222,7 @@ module function plastic_nonlocal_init() result(myPlasticity) st0 => state0(i), & del => deltaState(i), & dst => microstructure(i)) - pl => phase%get('plasticity') + pl => mech%get('plasticity') phase_localPlasticity(p) = .not. pl%contains('nonlocal') @@ -243,7 +244,7 @@ module function plastic_nonlocal_init() result(myPlasticity) prm%Schmid = lattice_SchmidMatrix_slip(ini%N_sl,phase%get_asString('lattice'),& phase%get_asFloat('c/a',defaultVal=0.0_pReal)) - if(trim(phase%get_asString('lattice')) == 'bcc') then + if(trim(phase%get_asString('lattice')) == 'cI') then a = pl%get_asFloats('a_nonSchmid',defaultVal = emptyRealArray) if(size(a) > 0) prm%nonSchmidActive = .true. prm%nonSchmid_pos = lattice_nonSchmidMatrix(ini%N_sl,a,+1) @@ -361,7 +362,7 @@ module function plastic_nonlocal_init() result(myPlasticity) if (prm%nu_a <= 0.0_pReal) extmsg = trim(extmsg)//' nu_a' if (prm%w <= 0.0_pReal) extmsg = trim(extmsg)//' w' if (prm%D_0 < 0.0_pReal) extmsg = trim(extmsg)//' D_0' - if (prm%V_at <= 0.0_pReal) extmsg = trim(extmsg)//' V_at' ! ToDo: in disloTungsten, the atomic volume is given as a factor + if (prm%V_at <= 0.0_pReal) extmsg = trim(extmsg)//' V_at' ! ToDo: in dislotungsten, the atomic volume is given as a factor if (prm%rho_min < 0.0_pReal) extmsg = trim(extmsg)//' rho_min' if (prm%rho_significant < 0.0_pReal) extmsg = trim(extmsg)//' rho_significant' @@ -391,7 +392,7 @@ module function plastic_nonlocal_init() result(myPlasticity) !-------------------------------------------------------------------------------------------------- ! allocate state arrays - NipcMyPhase = count(material_phaseAt==p) * discretization_nIP + Nconstituents = count(material_phaseAt==p) * discretization_nIPs sizeDotState = size([ 'rhoSglEdgePosMobile ','rhoSglEdgeNegMobile ', & 'rhoSglScrewPosMobile ','rhoSglScrewNegMobile ', & 'rhoSglEdgePosImmobile ','rhoSglEdgeNegImmobile ', & @@ -405,7 +406,7 @@ module function plastic_nonlocal_init() result(myPlasticity) 'maxDipoleHeightEdge ','maxDipoleHeightScrew' ]) * prm%sum_N_sl !< other dependent state variables that are not updated by microstructure sizeDeltaState = sizeDotState - call constitutive_allocateState(plasticState(p),NipcMyPhase,sizeState,sizeDotState,sizeDeltaState) + call constitutive_allocateState(plasticState(p),Nconstituents,sizeState,sizeDotState,sizeDeltaState) plasticState(p)%nonlocal = pl%get_asBool('nonlocal') if(plasticState(p)%nonlocal .and. .not. allocated(IPneighborhood)) & @@ -476,26 +477,26 @@ module function plastic_nonlocal_init() result(myPlasticity) dot%rho_dip_scr => plasticState(p)%dotState (9*prm%sum_N_sl+1:10*prm%sum_N_sl,:) del%rho_dip_scr => plasticState(p)%deltaState (9*prm%sum_N_sl+1:10*prm%sum_N_sl,:) - stt%gamma => plasticState(p)%state (10*prm%sum_N_sl + 1:11*prm%sum_N_sl,1:NipcMyPhase) - dot%gamma => plasticState(p)%dotState (10*prm%sum_N_sl + 1:11*prm%sum_N_sl,1:NipcMyPhase) - del%gamma => plasticState(p)%deltaState (10*prm%sum_N_sl + 1:11*prm%sum_N_sl,1:NipcMyPhase) + stt%gamma => plasticState(p)%state (10*prm%sum_N_sl + 1:11*prm%sum_N_sl,1:Nconstituents) + dot%gamma => plasticState(p)%dotState (10*prm%sum_N_sl + 1:11*prm%sum_N_sl,1:Nconstituents) + del%gamma => plasticState(p)%deltaState (10*prm%sum_N_sl + 1:11*prm%sum_N_sl,1:Nconstituents) plasticState(p)%atol(10*prm%sum_N_sl+1:11*prm%sum_N_sl ) = pl%get_asFloat('atol_gamma', defaultVal = 1.0e-2_pReal) if(any(plasticState(p)%atol(10*prm%sum_N_sl+1:11*prm%sum_N_sl) < 0.0_pReal)) & extmsg = trim(extmsg)//' atol_gamma' - plasticState(p)%slipRate => plasticState(p)%dotState (10*prm%sum_N_sl + 1:11*prm%sum_N_sl,1:NipcMyPhase) + plasticState(p)%slipRate => plasticState(p)%dotState (10*prm%sum_N_sl + 1:11*prm%sum_N_sl,1:Nconstituents) - stt%rho_forest => plasticState(p)%state (11*prm%sum_N_sl + 1:12*prm%sum_N_sl,1:NipcMyPhase) - stt%v => plasticState(p)%state (12*prm%sum_N_sl + 1:16*prm%sum_N_sl,1:NipcMyPhase) - stt%v_edg_pos => plasticState(p)%state (12*prm%sum_N_sl + 1:13*prm%sum_N_sl,1:NipcMyPhase) - stt%v_edg_neg => plasticState(p)%state (13*prm%sum_N_sl + 1:14*prm%sum_N_sl,1:NipcMyPhase) - stt%v_scr_pos => plasticState(p)%state (14*prm%sum_N_sl + 1:15*prm%sum_N_sl,1:NipcMyPhase) - stt%v_scr_neg => plasticState(p)%state (15*prm%sum_N_sl + 1:16*prm%sum_N_sl,1:NipcMyPhase) + stt%rho_forest => plasticState(p)%state (11*prm%sum_N_sl + 1:12*prm%sum_N_sl,1:Nconstituents) + stt%v => plasticState(p)%state (12*prm%sum_N_sl + 1:16*prm%sum_N_sl,1:Nconstituents) + stt%v_edg_pos => plasticState(p)%state (12*prm%sum_N_sl + 1:13*prm%sum_N_sl,1:Nconstituents) + stt%v_edg_neg => plasticState(p)%state (13*prm%sum_N_sl + 1:14*prm%sum_N_sl,1:Nconstituents) + stt%v_scr_pos => plasticState(p)%state (14*prm%sum_N_sl + 1:15*prm%sum_N_sl,1:Nconstituents) + stt%v_scr_neg => plasticState(p)%state (15*prm%sum_N_sl + 1:16*prm%sum_N_sl,1:Nconstituents) - allocate(dst%tau_pass(prm%sum_N_sl,NipcMyPhase),source=0.0_pReal) - allocate(dst%tau_back(prm%sum_N_sl,NipcMyPhase),source=0.0_pReal) + allocate(dst%tau_pass(prm%sum_N_sl,Nconstituents),source=0.0_pReal) + allocate(dst%tau_back(prm%sum_N_sl,Nconstituents),source=0.0_pReal) end associate - if (NipcMyPhase > 0) call stateInit(ini,p,NipcMyPhase,i) + if (Nconstituents > 0) call stateInit(ini,p,Nconstituents,i) plasticState(p)%state0 = plasticState(p)%state !-------------------------------------------------------------------------------------------------- @@ -505,12 +506,12 @@ module function plastic_nonlocal_init() result(myPlasticity) enddo allocate(compatibility(2,maxval(param%sum_N_sl),maxval(param%sum_N_sl),nIPneighbors,& - discretization_nIP,discretization_nElem), source=0.0_pReal) + discretization_nIPs,discretization_Nelems), source=0.0_pReal) ! BEGIN DEPRECATED---------------------------------------------------------------------------------- - allocate(iRhoU(maxval(param%sum_N_sl),4,Ninstance), source=0) - allocate(iV(maxval(param%sum_N_sl),4,Ninstance), source=0) - allocate(iD(maxval(param%sum_N_sl),2,Ninstance), source=0) + allocate(iRhoU(maxval(param%sum_N_sl),4,Ninstances), source=0) + allocate(iV(maxval(param%sum_N_sl),4,Ninstances), source=0) + allocate(iD(maxval(param%sum_N_sl),2,Ninstances), source=0) i = 0 do p = 1, phases%length @@ -519,7 +520,7 @@ module function plastic_nonlocal_init() result(myPlasticity) if(.not. myPlasticity(p)) cycle i = i + 1 - NipcMyPhase = count(material_phaseAt==p) * discretization_nIP + Nconstituents = count(material_phaseAt==p) * discretization_nIPs l = 0 do t = 1,4 do s = 1,param(i)%sum_N_sl @@ -976,7 +977,7 @@ module subroutine plastic_nonlocal_dotState(Mp, F, Fp, Temperature,timestep, & real(pReal), dimension(3,3), intent(in) :: & Mp !< MandelStress - real(pReal), dimension(3,3,homogenization_maxNgrains,discretization_nIP,discretization_nElem), intent(in) :: & + real(pReal), dimension(3,3,homogenization_maxNconstituents,discretization_nIPs,discretization_Nelems), intent(in) :: & F, & !< elastic deformation gradient Fp !< plastic deformation gradient real(pReal), intent(in) :: & @@ -1176,7 +1177,7 @@ end subroutine plastic_nonlocal_dotState !--------------------------------------------------------------------------------------------------- function rhoDotFlux(F,Fp,timestep, instance,of,ip,el) - real(pReal), dimension(3,3,homogenization_maxNgrains,discretization_nIP,discretization_nElem), intent(in) :: & + real(pReal), dimension(3,3,homogenization_maxNconstituents,discretization_nIPs,discretization_Nelems), intent(in) :: & F, & !< elastic deformation gradient Fp !< plastic deformation gradient real(pReal), intent(in) :: & @@ -1416,7 +1417,7 @@ end function rhoDotFlux !-------------------------------------------------------------------------------------------------- module subroutine plastic_nonlocal_updateCompatibility(orientation,instance,i,e) - type(rotation), dimension(1,discretization_nIP,discretization_nElem), intent(in) :: & + type(rotation), dimension(1,discretization_nIPs,discretization_Nelems), intent(in) :: & orientation ! crystal orientation integer, intent(in) :: & instance, & @@ -1601,13 +1602,13 @@ end subroutine plastic_nonlocal_results !-------------------------------------------------------------------------------------------------- !> @brief populates the initial dislocation density !-------------------------------------------------------------------------------------------------- -subroutine stateInit(ini,phase,NipcMyPhase,instance) +subroutine stateInit(ini,phase,Nconstituents,instance) type(tInitialParameters) :: & ini integer,intent(in) :: & phase, & - NipcMyPhase, & + Nconstituents, & instance integer :: & e, & @@ -1625,15 +1626,15 @@ subroutine stateInit(ini,phase,NipcMyPhase,instance) totalVolume, & densityBinning, & minimumIpVolume - real(pReal), dimension(NipcMyPhase) :: & + real(pReal), dimension(Nconstituents) :: & volume associate(stt => state(instance)) if (ini%random_rho_u > 0.0_pReal) then ! randomly distribute dislocation segments on random slip system and of random type in the volume - do e = 1,discretization_nElem - do i = 1,discretization_nIP + do e = 1,discretization_Nelems + do i = 1,discretization_nIPs if (material_phaseAt(1,e) == phase) volume(material_phasememberAt(1,i,e)) = IPvolume(i,e) enddo enddo @@ -1645,13 +1646,13 @@ subroutine stateInit(ini,phase,NipcMyPhase,instance) meanDensity = 0.0_pReal do while(meanDensity < ini%random_rho_u) call random_number(rnd) - phasemember = nint(rnd(1)*real(NipcMyPhase,pReal) + 0.5_pReal) + phasemember = nint(rnd(1)*real(Nconstituents,pReal) + 0.5_pReal) s = nint(rnd(2)*real(sum(ini%N_sl),pReal)*4.0_pReal + 0.5_pReal) meanDensity = meanDensity + densityBinning * volume(phasemember) / totalVolume stt%rhoSglMobile(s,phasemember) = densityBinning enddo else ! homogeneous distribution with noise - do e = 1, NipcMyPhase + do e = 1, Nconstituents do f = 1,size(ini%N_sl,1) from = 1 + sum(ini%N_sl(1:f-1)) upto = sum(ini%N_sl(1:f)) diff --git a/src/constitutive_plastic_phenopowerlaw.f90 b/src/constitutive_plastic_phenopowerlaw.f90 index fd1b5fbbb..678acad27 100644 --- a/src/constitutive_plastic_phenopowerlaw.f90 +++ b/src/constitutive_plastic_phenopowerlaw.f90 @@ -4,7 +4,7 @@ !> @author Martin Diehl, Max-Planck-Institut für Eisenforschung GmbH !> @brief phenomenological crystal plasticity formulation using a powerlaw fitting !-------------------------------------------------------------------------------------------------- -submodule(constitutive:constitutive_plastic) plastic_phenopowerlaw +submodule(constitutive:constitutive_mech) plastic_phenopowerlaw type :: tParameters real(pReal) :: & @@ -70,9 +70,9 @@ module function plastic_phenopowerlaw_init() result(myPlasticity) logical, dimension(:), allocatable :: myPlasticity integer :: & - Ninstance, & + Ninstances, & p, i, & - NipcMyPhase, & + Nconstituents, & sizeState, sizeDotState, & startIndex, endIndex integer, dimension(:), allocatable :: & @@ -86,30 +86,31 @@ module function plastic_phenopowerlaw_init() result(myPlasticity) class(tNode), pointer :: & phases, & phase, & + mech, & pl print'(/,a)', ' <<<+- plastic_phenopowerlaw init -+>>>' myPlasticity = plastic_active('phenopowerlaw') - Ninstance = count(myPlasticity) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(myPlasticity) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return - allocate(param(Ninstance)) - allocate(state(Ninstance)) - allocate(dotState(Ninstance)) + allocate(param(Ninstances)) + allocate(state(Ninstances)) + allocate(dotState(Ninstances)) phases => config_material%get('phase') i = 0 do p = 1, phases%length phase => phases%get(p) - + mech => phase%get('mechanics') if(.not. myPlasticity(p)) cycle i = i + 1 associate(prm => param(i), & dot => dotState(i), & stt => state(i)) - pl => phase%get('plasticity') + pl => mech%get('plasticity') !-------------------------------------------------------------------------------------------------- ! slip related parameters @@ -119,7 +120,7 @@ module function plastic_phenopowerlaw_init() result(myPlasticity) prm%P_sl = lattice_SchmidMatrix_slip(N_sl,phase%get_asString('lattice'),& phase%get_asFloat('c/a',defaultVal=0.0_pReal)) - if(phase%get_asString('lattice') == 'bcc') then + if(phase%get_asString('lattice') == 'cI') then a = pl%get_asFloats('a_nonSchmid',defaultVal=emptyRealArray) if(size(a) > 0) prm%nonSchmidActive = .true. prm%nonSchmid_pos = lattice_nonSchmidMatrix(N_sl,a,+1) @@ -224,20 +225,20 @@ module function plastic_phenopowerlaw_init() result(myPlasticity) !-------------------------------------------------------------------------------------------------- ! allocate state arrays - NipcMyPhase = count(material_phaseAt == p) * discretization_nIP + Nconstituents = count(material_phaseAt == p) * discretization_nIPs sizeDotState = size(['xi_sl ','gamma_sl']) * prm%sum_N_sl & + size(['xi_tw ','gamma_tw']) * prm%sum_N_tw sizeState = sizeDotState - call constitutive_allocateState(plasticState(p),NipcMyPhase,sizeState,sizeDotState,0) + call constitutive_allocateState(plasticState(p),Nconstituents,sizeState,sizeDotState,0) !-------------------------------------------------------------------------------------------------- ! state aliases and initialization startIndex = 1 endIndex = prm%sum_N_sl stt%xi_slip => plasticState(p)%state (startIndex:endIndex,:) - stt%xi_slip = spread(xi_0_sl, 2, NipcMyPhase) + stt%xi_slip = spread(xi_0_sl, 2, Nconstituents) dot%xi_slip => plasticState(p)%dotState(startIndex:endIndex,:) plasticState(p)%atol(startIndex:endIndex) = pl%get_asFloat('atol_xi',defaultVal=1.0_pReal) if(any(plasticState(p)%atol(startIndex:endIndex) < 0.0_pReal)) extmsg = trim(extmsg)//' atol_xi' @@ -245,7 +246,7 @@ module function plastic_phenopowerlaw_init() result(myPlasticity) startIndex = endIndex + 1 endIndex = endIndex + prm%sum_N_tw stt%xi_twin => plasticState(p)%state (startIndex:endIndex,:) - stt%xi_twin = spread(xi_0_tw, 2, NipcMyPhase) + stt%xi_twin = spread(xi_0_tw, 2, Nconstituents) dot%xi_twin => plasticState(p)%dotState(startIndex:endIndex,:) plasticState(p)%atol(startIndex:endIndex) = pl%get_asFloat('atol_xi',defaultVal=1.0_pReal) if(any(plasticState(p)%atol(startIndex:endIndex) < 0.0_pReal)) extmsg = trim(extmsg)//' atol_xi' diff --git a/src/constitutive_thermal.f90 b/src/constitutive_thermal.f90 index 3aefb99a7..a7d5d3259 100644 --- a/src/constitutive_thermal.f90 +++ b/src/constitutive_thermal.f90 @@ -95,7 +95,7 @@ module subroutine constitutive_thermal_getRateAndItsTangents(TDot, dTDot_dT, T, homog = material_homogenizationAt(el) instance = thermal_typeInstance(homog) - do grain = 1, homogenization_Ngrains(homog) + do grain = 1, homogenization_Nconstituents(homog) phase = material_phaseAt(grain,el) constituent = material_phasememberAt(grain,ip,el) do source = 1, phase_Nsources(phase) diff --git a/src/crystallite.f90 b/src/crystallite.f90 index dc6b9f7da..da349bbcd 100644 --- a/src/crystallite.f90 +++ b/src/crystallite.f90 @@ -135,7 +135,7 @@ contains !-------------------------------------------------------------------------------------------------- subroutine crystallite_init - logical, dimension(discretization_nIP,discretization_nElem) :: devNull + logical, dimension(discretization_nIPs,discretization_Nelems) :: devNull integer :: & c, & !< counter in integration point component loop i, & !< counter in integration point loop @@ -150,7 +150,7 @@ subroutine crystallite_init debug_crystallite, & ! pointer to debug options for crystallite phases, & phase, & - generic_param + mech print'(/,a)', ' <<<+- crystallite init -+>>>' @@ -162,9 +162,9 @@ subroutine crystallite_init debugCrystallite%ip = config_debug%get_asInt('integrationpoint', defaultVal=1) debugCrystallite%grain = config_debug%get_asInt('grain', defaultVal=1) - cMax = homogenization_maxNgrains - iMax = discretization_nIP - eMax = discretization_nElem + cMax = homogenization_maxNconstituents + iMax = discretization_nIPs + eMax = discretization_Nelems allocate(crystallite_partitionedF(3,3,cMax,iMax,eMax),source=0.0_pReal) @@ -240,11 +240,11 @@ subroutine crystallite_init allocate(output_constituent(phases%length)) do c = 1, phases%length phase => phases%get(c) - generic_param => phase%get('generic',defaultVal = emptyDict) + mech => phase%get('mechanics',defaultVal = emptyDict) #if defined(__GFORTRAN__) - output_constituent(c)%label = output_asStrings(generic_param) + output_constituent(c)%label = output_asStrings(mech) #else - output_constituent(c)%label = generic_param%get_asStrings('output',defaultVal=emptyStringArray) + output_constituent(c)%label = mech%get_asStrings('output',defaultVal=emptyStringArray) #endif enddo @@ -253,7 +253,7 @@ subroutine crystallite_init ! initialize !$OMP PARALLEL DO PRIVATE(i,c) do e = FEsolving_execElem(1),FEsolving_execElem(2) - do i = FEsolving_execIP(1), FEsolving_execIP(2); do c = 1, homogenization_Ngrains(material_homogenizationAt(e)) + do i = FEsolving_execIP(1), FEsolving_execIP(2); do c = 1, homogenization_Nconstituents(material_homogenizationAt(e)) crystallite_Fp0(1:3,1:3,c,i,e) = material_orientation0(c,i,e)%asMatrix() ! Fp reflects initial orientation (see 10.1016/j.actamat.2006.01.005) crystallite_Fp0(1:3,1:3,c,i,e) = crystallite_Fp0(1:3,1:3,c,i,e) & / math_det33(crystallite_Fp0(1:3,1:3,c,i,e))**(1.0_pReal/3.0_pReal) @@ -279,7 +279,7 @@ subroutine crystallite_init !$OMP PARALLEL DO do e = FEsolving_execElem(1),FEsolving_execElem(2) do i = FEsolving_execIP(1),FEsolving_execIP(2) - do c = 1,homogenization_Ngrains(material_homogenizationAt(e)) + do c = 1,homogenization_Nconstituents(material_homogenizationAt(e)) call constitutive_dependentState(crystallite_partitionedF0(1:3,1:3,c,i,e), & crystallite_partitionedFp0(1:3,1:3,c,i,e), & c,i,e) ! update dependent state variables to be consistent with basic states @@ -307,7 +307,7 @@ end subroutine crystallite_init !-------------------------------------------------------------------------------------------------- function crystallite_stress() - logical, dimension(discretization_nIP,discretization_nElem) :: crystallite_stress + logical, dimension(discretization_nIPs,discretization_Nelems) :: crystallite_stress real(pReal) :: & formerSubStep integer :: & @@ -315,9 +315,8 @@ function crystallite_stress() c, & !< counter in integration point component loop i, & !< counter in integration point loop e, & !< counter in element loop - startIP, endIP, & s - logical, dimension(homogenization_maxNgrains,discretization_nIP,discretization_nElem) :: todo !ToDo: need to set some values to false for different Ngrains + logical, dimension(homogenization_maxNconstituents,discretization_nIPs,discretization_Nelems) :: todo !ToDo: need to set some values to false for different Ngrains real(pReal), dimension(:,:,:,:,:), allocatable :: & subLp0,& !< plastic velocity grad at start of crystallite inc subLi0 !< intermediate velocity grad at start of crystallite inc @@ -335,7 +334,7 @@ function crystallite_stress() crystallite_subStep = 0.0_pReal !$OMP PARALLEL DO elementLooping1: do e = FEsolving_execElem(1),FEsolving_execElem(2) - do i = FEsolving_execIP(1),FEsolving_execIP(2); do c = 1,homogenization_Ngrains(material_homogenizationAt(e)) + do i = FEsolving_execIP(1),FEsolving_execIP(2); do c = 1,homogenization_Nconstituents(material_homogenizationAt(e)) homogenizationRequestsCalculation: if (crystallite_requested(c,i,e)) then plasticState (material_phaseAt(c,e))%subState0( :,material_phaseMemberAt(c,i,e)) = & plasticState (material_phaseAt(c,e))%partitionedState0(:,material_phaseMemberAt(c,i,e)) @@ -356,17 +355,8 @@ function crystallite_stress() enddo elementLooping1 !$OMP END PARALLEL DO - singleRun: if (FEsolving_execELem(1) == FEsolving_execElem(2) .and. & - FEsolving_execIP (1) == FEsolving_execIP (2)) then - startIP = FEsolving_execIP(1) - endIP = startIP - else singleRun - startIP = 1 - endIP = discretization_nIP - endif singleRun - NiterationCrystallite = 0 - cutbackLooping: do while (any(todo(:,startIP:endIP,FEsolving_execELem(1):FEsolving_execElem(2)))) + cutbackLooping: do while (any(todo(:,FEsolving_execIP(1):FEsolving_execIP(2),FEsolving_execELem(1):FEsolving_execElem(2)))) NiterationCrystallite = NiterationCrystallite + 1 #ifdef DEBUG @@ -376,7 +366,7 @@ function crystallite_stress() !$OMP PARALLEL DO PRIVATE(formerSubStep) elementLooping3: do e = FEsolving_execElem(1),FEsolving_execElem(2) do i = FEsolving_execIP(1),FEsolving_execIP(2) - do c = 1,homogenization_Ngrains(material_homogenizationAt(e)) + do c = 1,homogenization_Nconstituents(material_homogenizationAt(e)) !-------------------------------------------------------------------------------------------------- ! wind forward if (crystallite_converged(c,i,e)) then @@ -428,9 +418,9 @@ function crystallite_stress() crystallite_subF(1:3,1:3,c,i,e) = crystallite_subF0(1:3,1:3,c,i,e) & + crystallite_subStep(c,i,e) *( crystallite_partitionedF (1:3,1:3,c,i,e) & -crystallite_partitionedF0(1:3,1:3,c,i,e)) - crystallite_Fe(1:3,1:3,c,i,e) = matmul(matmul(crystallite_subF(1:3,1:3,c,i,e), & - math_inv33(crystallite_Fp(1:3,1:3,c,i,e))), & - math_inv33(crystallite_Fi(1:3,1:3,c,i,e))) + crystallite_Fe(1:3,1:3,c,i,e) = matmul(crystallite_subF(1:3,1:3,c,i,e), & + math_inv33(matmul(crystallite_Fi(1:3,1:3,c,i,e), & + crystallite_Fp(1:3,1:3,c,i,e)))) crystallite_subdt(c,i,e) = crystallite_subStep(c,i,e) * crystallite_dt(c,i,e) crystallite_converged(c,i,e) = .false. call integrateState(c,i,e) @@ -472,7 +462,7 @@ subroutine crystallite_initializeRestorationPoints(i,e) c, & !< constituent number s - do c = 1,homogenization_Ngrains(material_homogenizationAt(e)) + do c = 1,homogenization_Nconstituents(material_homogenizationAt(e)) crystallite_partitionedFp0(1:3,1:3,c,i,e) = crystallite_Fp0(1:3,1:3,c,i,e) crystallite_partitionedLp0(1:3,1:3,c,i,e) = crystallite_Lp0(1:3,1:3,c,i,e) crystallite_partitionedFi0(1:3,1:3,c,i,e) = crystallite_Fi0(1:3,1:3,c,i,e) @@ -503,7 +493,7 @@ subroutine crystallite_windForward(i,e) c, & !< constituent number s - do c = 1,homogenization_Ngrains(material_homogenizationAt(e)) + do c = 1,homogenization_Nconstituents(material_homogenizationAt(e)) crystallite_partitionedF0 (1:3,1:3,c,i,e) = crystallite_partitionedF(1:3,1:3,c,i,e) crystallite_partitionedFp0(1:3,1:3,c,i,e) = crystallite_Fp (1:3,1:3,c,i,e) crystallite_partitionedLp0(1:3,1:3,c,i,e) = crystallite_Lp (1:3,1:3,c,i,e) @@ -536,7 +526,7 @@ subroutine crystallite_restore(i,e,includeL) c, & !< constituent number s - do c = 1,homogenization_Ngrains(material_homogenizationAt(e)) + do c = 1,homogenization_Nconstituents(material_homogenizationAt(e)) if (includeL) then crystallite_Lp(1:3,1:3,c,i,e) = crystallite_partitionedLp0(1:3,1:3,c,i,e) crystallite_Li(1:3,1:3,c,i,e) = crystallite_partitionedLi0(1:3,1:3,c,i,e) @@ -697,7 +687,7 @@ subroutine crystallite_orientations !$OMP PARALLEL DO do e = FEsolving_execElem(1),FEsolving_execElem(2) do i = FEsolving_execIP(1),FEsolving_execIP(2) - do c = 1,homogenization_Ngrains(material_homogenizationAt(e)) + do c = 1,homogenization_Nconstituents(material_homogenizationAt(e)) call crystallite_orientation(c,i,e)%fromMatrix(transpose(math_rotationalPart(crystallite_Fe(1:3,1:3,c,i,e)))) enddo; enddo; enddo !$OMP END PARALLEL DO @@ -746,10 +736,10 @@ subroutine crystallite_results integer :: p,o real(pReal), allocatable, dimension(:,:,:) :: selected_tensors type(rotation), allocatable, dimension(:) :: selected_rotations - character(len=pStringLen) :: group,structureLabel + character(len=:), allocatable :: group,structureLabel do p=1,size(material_name_phase) - group = trim('current/constituent')//'/'//trim(material_name_phase(p))//'/generic' + group = trim('current/phase')//'/'//trim(material_name_phase(p))//'/mechanics' call results_closeGroup(results_addGroup(group)) @@ -759,48 +749,48 @@ subroutine crystallite_results selected_tensors = select_tensors(crystallite_partitionedF,p) call results_writeDataset(group,selected_tensors,output_constituent(p)%label(o),& 'deformation gradient','1') - case('Fe') + case('F_e') selected_tensors = select_tensors(crystallite_Fe,p) call results_writeDataset(group,selected_tensors,output_constituent(p)%label(o),& 'elastic deformation gradient','1') - case('Fp') + case('F_p') selected_tensors = select_tensors(crystallite_Fp,p) call results_writeDataset(group,selected_tensors,output_constituent(p)%label(o),& 'plastic deformation gradient','1') - case('Fi') + case('F_i') selected_tensors = select_tensors(crystallite_Fi,p) call results_writeDataset(group,selected_tensors,output_constituent(p)%label(o),& 'inelastic deformation gradient','1') - case('Lp') + case('L_p') selected_tensors = select_tensors(crystallite_Lp,p) call results_writeDataset(group,selected_tensors,output_constituent(p)%label(o),& 'plastic velocity gradient','1/s') - case('Li') + case('L_i') selected_tensors = select_tensors(crystallite_Li,p) call results_writeDataset(group,selected_tensors,output_constituent(p)%label(o),& 'inelastic velocity gradient','1/s') case('P') selected_tensors = select_tensors(crystallite_P,p) call results_writeDataset(group,selected_tensors,output_constituent(p)%label(o),& - 'First Piola-Kirchoff stress','Pa') + 'First Piola-Kirchhoff stress','Pa') case('S') selected_tensors = select_tensors(crystallite_S,p) call results_writeDataset(group,selected_tensors,output_constituent(p)%label(o),& - 'Second Piola-Kirchoff stress','Pa') + 'Second Piola-Kirchhoff stress','Pa') case('O') select case(lattice_structure(p)) case(lattice_ISO_ID) - structureLabel = 'iso' + structureLabel = 'aP' case(lattice_FCC_ID) - structureLabel = 'fcc' + structureLabel = 'cF' case(lattice_BCC_ID) - structureLabel = 'bcc' + structureLabel = 'cI' case(lattice_BCT_ID) - structureLabel = 'bct' + structureLabel = 'tI' case(lattice_HEX_ID) - structureLabel = 'hex' + structureLabel = 'hP' case(lattice_ORT_ID) - structureLabel = 'ort' + structureLabel = 'oP' end select selected_rotations = select_rotations(crystallite_orientation,p) call results_writeDataset(group,selected_rotations,output_constituent(p)%label(o),& @@ -821,11 +811,11 @@ subroutine crystallite_results real(pReal), allocatable, dimension(:,:,:) :: select_tensors integer :: e,i,c,j - allocate(select_tensors(3,3,count(material_phaseAt==instance)*discretization_nIP)) + allocate(select_tensors(3,3,count(material_phaseAt==instance)*discretization_nIPs)) j=0 do e = 1, size(material_phaseAt,2) - do i = 1, discretization_nIP + do i = 1, discretization_nIPs do c = 1, size(material_phaseAt,1) !ToDo: this needs to be changed for varying Ngrains if (material_phaseAt(c,e) == instance) then j = j + 1 @@ -848,11 +838,11 @@ subroutine crystallite_results type(rotation), allocatable, dimension(:) :: select_rotations integer :: e,i,c,j - allocate(select_rotations(count(material_phaseAt==instance)*homogenization_maxNgrains*discretization_nIP)) + allocate(select_rotations(count(material_phaseAt==instance)*homogenization_maxNconstituents*discretization_nIPs)) j=0 do e = 1, size(material_phaseAt,2) - do i = 1, discretization_nIP + do i = 1, discretization_nIPs do c = 1, size(material_phaseAt,1) !ToDo: this needs to be changed for varying Ngrains if (material_phaseAt(c,e) == instance) then j = j + 1 @@ -1557,22 +1547,22 @@ subroutine crystallite_restartWrite fileHandle = HDF5_openFile(fileName,'a') call HDF5_write(fileHandle,crystallite_partitionedF,'F') - call HDF5_write(fileHandle,crystallite_Fp, 'Fp') - call HDF5_write(fileHandle,crystallite_Fi, 'Fi') - call HDF5_write(fileHandle,crystallite_Lp, 'Lp') - call HDF5_write(fileHandle,crystallite_Li, 'Li') - call HDF5_write(fileHandle,crystallite_S, 'S') + call HDF5_write(fileHandle,crystallite_Fp, 'F_p') + call HDF5_write(fileHandle,crystallite_Fi, 'F_i') + call HDF5_write(fileHandle,crystallite_Lp, 'L_p') + call HDF5_write(fileHandle,crystallite_Li, 'L_i') + call HDF5_write(fileHandle,crystallite_S, 'S') - groupHandle = HDF5_addGroup(fileHandle,'constituent') + groupHandle = HDF5_addGroup(fileHandle,'phase') do i = 1,size(material_name_phase) - write(datasetName,'(i0,a)') i,'_omega_plastic' + write(datasetName,'(i0,a)') i,'_omega' call HDF5_write(groupHandle,plasticState(i)%state,datasetName) enddo call HDF5_closeGroup(groupHandle) - groupHandle = HDF5_addGroup(fileHandle,'materialpoint') - do i = 1, material_Nhomogenization - write(datasetName,'(i0,a)') i,'_omega_homogenization' + groupHandle = HDF5_addGroup(fileHandle,'homogenization') + do i = 1, size(material_name_homogenization) + write(datasetName,'(i0,a)') i,'_omega' call HDF5_write(groupHandle,homogState(i)%state,datasetName) enddo call HDF5_closeGroup(groupHandle) @@ -1598,22 +1588,22 @@ subroutine crystallite_restartRead fileHandle = HDF5_openFile(fileName) call HDF5_read(fileHandle,crystallite_F0, 'F') - call HDF5_read(fileHandle,crystallite_Fp0,'Fp') - call HDF5_read(fileHandle,crystallite_Fi0,'Fi') - call HDF5_read(fileHandle,crystallite_Lp0,'Lp') - call HDF5_read(fileHandle,crystallite_Li0,'Li') + call HDF5_read(fileHandle,crystallite_Fp0,'F_p') + call HDF5_read(fileHandle,crystallite_Fi0,'F_i') + call HDF5_read(fileHandle,crystallite_Lp0,'L_p') + call HDF5_read(fileHandle,crystallite_Li0,'L_i') call HDF5_read(fileHandle,crystallite_S0, 'S') - groupHandle = HDF5_openGroup(fileHandle,'constituent') + groupHandle = HDF5_openGroup(fileHandle,'phase') do i = 1,size(material_name_phase) - write(datasetName,'(i0,a)') i,'_omega_plastic' + write(datasetName,'(i0,a)') i,'_omega' call HDF5_read(groupHandle,plasticState(i)%state0,datasetName) enddo call HDF5_closeGroup(groupHandle) - groupHandle = HDF5_openGroup(fileHandle,'materialpoint') - do i = 1, material_Nhomogenization - write(datasetName,'(i0,a)') i,'_omega_homogenization' + groupHandle = HDF5_openGroup(fileHandle,'homogenization') + do i = 1,size(material_name_homogenization) + write(datasetName,'(i0,a)') i,'_omega' call HDF5_read(groupHandle,homogState(i)%state0,datasetName) enddo call HDF5_closeGroup(groupHandle) @@ -1645,7 +1635,7 @@ subroutine crystallite_forward do j = 1,phase_Nsources(i) sourceState(i)%p(j)%state0 = sourceState(i)%p(j)%state enddo; enddo - do i = 1, material_Nhomogenization + do i = 1,size(material_name_homogenization) homogState (i)%state0 = homogState (i)%state thermalState(i)%state0 = thermalState(i)%state damageState (i)%state0 = damageState (i)%state diff --git a/src/damage_local.f90 b/src/damage_local.f90 index af2532184..e63db90b0 100644 --- a/src/damage_local.f90 +++ b/src/damage_local.f90 @@ -42,7 +42,7 @@ contains !-------------------------------------------------------------------------------------------------- subroutine damage_local_init - integer :: Ninstance,NofMyHomog,h + integer :: Ninstances,Nmaterialpoints,h class(tNode), pointer :: & num_generic, & material_homogenization, & @@ -57,8 +57,8 @@ subroutine damage_local_init num%residualStiffness = num_generic%get_asFloat('residualStiffness', defaultVal=1.0e-6_pReal) if (num%residualStiffness < 0.0_pReal) call IO_error(301,ext_msg='residualStiffness') - Ninstance = count(damage_type == DAMAGE_local_ID) - allocate(param(Ninstance)) + Ninstances = count(damage_type == DAMAGE_local_ID) + allocate(param(Ninstances)) material_homogenization => config_material%get('homogenization') do h = 1, material_homogenization%length @@ -73,11 +73,11 @@ subroutine damage_local_init prm%output = homogDamage%get_asStrings('output',defaultVal=emptyStringArray) #endif - NofMyHomog = count(material_homogenizationAt == h) + Nmaterialpoints = count(material_homogenizationAt == h) damageState(h)%sizeState = 1 - allocate(damageState(h)%state0 (1,NofMyHomog), source=damage_initialPhi(h)) - allocate(damageState(h)%subState0(1,NofMyHomog), source=damage_initialPhi(h)) - allocate(damageState(h)%state (1,NofMyHomog), source=damage_initialPhi(h)) + allocate(damageState(h)%state0 (1,Nmaterialpoints), source=damage_initialPhi(h)) + allocate(damageState(h)%subState0(1,Nmaterialpoints), source=damage_initialPhi(h)) + allocate(damageState(h)%state (1,Nmaterialpoints), source=damage_initialPhi(h)) nullify(damageMapping(h)%p) damageMapping(h)%p => material_homogenizationMemberAt @@ -143,8 +143,8 @@ subroutine damage_local_getSourceAndItsTangent(phiDot, dPhiDot_dPhi, phi, ip, el call constitutive_damage_getRateAndItsTangents(phiDot, dPhiDot_dPhi, phi, ip, el) - phiDot = phiDot/real(homogenization_Ngrains(material_homogenizationAt(el)),pReal) - dPhiDot_dPhi = dPhiDot_dPhi/real(homogenization_Ngrains(material_homogenizationAt(el)),pReal) + phiDot = phiDot/real(homogenization_Nconstituents(material_homogenizationAt(el)),pReal) + dPhiDot_dPhi = dPhiDot_dPhi/real(homogenization_Nconstituents(material_homogenizationAt(el)),pReal) end subroutine damage_local_getSourceAndItsTangent diff --git a/src/damage_none.f90 b/src/damage_none.f90 index 4e01998bc..2279bc06b 100644 --- a/src/damage_none.f90 +++ b/src/damage_none.f90 @@ -16,18 +16,18 @@ contains !-------------------------------------------------------------------------------------------------- subroutine damage_none_init - integer :: h,NofMyHomog + integer :: h,Nmaterialpoints print'(/,a)', ' <<<+- damage_none init -+>>>'; flush(6) - do h = 1, material_Nhomogenization + do h = 1, size(material_name_homogenization) if (damage_type(h) /= DAMAGE_NONE_ID) cycle - NofMyHomog = count(material_homogenizationAt == h) + Nmaterialpoints = count(material_homogenizationAt == h) damageState(h)%sizeState = 0 - allocate(damageState(h)%state0 (0,NofMyHomog)) - allocate(damageState(h)%subState0(0,NofMyHomog)) - allocate(damageState(h)%state (0,NofMyHomog)) + allocate(damageState(h)%state0 (0,Nmaterialpoints)) + allocate(damageState(h)%subState0(0,Nmaterialpoints)) + allocate(damageState(h)%state (0,Nmaterialpoints)) deallocate(damage(h)%p) allocate (damage(h)%p(1), source=damage_initialPhi(h)) diff --git a/src/damage_nonlocal.f90 b/src/damage_nonlocal.f90 index f31b10242..24a51cf54 100644 --- a/src/damage_nonlocal.f90 +++ b/src/damage_nonlocal.f90 @@ -46,7 +46,7 @@ contains !-------------------------------------------------------------------------------------------------- subroutine damage_nonlocal_init - integer :: Ninstance,NofMyHomog,h + integer :: Ninstances,Nmaterialpoints,h class(tNode), pointer :: & num_generic, & material_homogenization, & @@ -60,8 +60,8 @@ subroutine damage_nonlocal_init num_generic => config_numerics%get('generic',defaultVal= emptyDict) num%charLength = num_generic%get_asFloat('charLength',defaultVal=1.0_pReal) - Ninstance = count(damage_type == DAMAGE_nonlocal_ID) - allocate(param(Ninstance)) + Ninstances = count(damage_type == DAMAGE_nonlocal_ID) + allocate(param(Ninstances)) material_homogenization => config_material%get('homogenization') do h = 1, material_homogenization%length @@ -76,11 +76,11 @@ subroutine damage_nonlocal_init prm%output = homogDamage%get_asStrings('output',defaultVal=emptyStringArray) #endif - NofMyHomog = count(material_homogenizationAt == h) + Nmaterialpoints = count(material_homogenizationAt == h) damageState(h)%sizeState = 1 - allocate(damageState(h)%state0 (1,NofMyHomog), source=damage_initialPhi(h)) - allocate(damageState(h)%subState0(1,NofMyHomog), source=damage_initialPhi(h)) - allocate(damageState(h)%state (1,NofMyHomog), source=damage_initialPhi(h)) + allocate(damageState(h)%state0 (1,Nmaterialpoints), source=damage_initialPhi(h)) + allocate(damageState(h)%subState0(1,Nmaterialpoints), source=damage_initialPhi(h)) + allocate(damageState(h)%state (1,Nmaterialpoints), source=damage_initialPhi(h)) nullify(damageMapping(h)%p) damageMapping(h)%p => material_homogenizationMemberAt @@ -110,8 +110,8 @@ subroutine damage_nonlocal_getSourceAndItsTangent(phiDot, dPhiDot_dPhi, phi, ip, dPhiDot_dPhi = 0.0_pReal call constitutive_damage_getRateAndItsTangents(phiDot, dPhiDot_dPhi, phi, ip, el) - phiDot = phiDot/real(homogenization_Ngrains(material_homogenizationAt(el)),pReal) - dPhiDot_dPhi = dPhiDot_dPhi/real(homogenization_Ngrains(material_homogenizationAt(el)),pReal) + phiDot = phiDot/real(homogenization_Nconstituents(material_homogenizationAt(el)),pReal) + dPhiDot_dPhi = dPhiDot_dPhi/real(homogenization_Nconstituents(material_homogenizationAt(el)),pReal) end subroutine damage_nonlocal_getSourceAndItsTangent @@ -132,13 +132,13 @@ function damage_nonlocal_getDiffusion(ip,el) homog = material_homogenizationAt(el) damage_nonlocal_getDiffusion = 0.0_pReal - do grain = 1, homogenization_Ngrains(homog) + do grain = 1, homogenization_Nconstituents(homog) damage_nonlocal_getDiffusion = damage_nonlocal_getDiffusion + & crystallite_push33ToRef(grain,ip,el,lattice_D(1:3,1:3,material_phaseAt(grain,el))) enddo damage_nonlocal_getDiffusion = & - num%charLength**2*damage_nonlocal_getDiffusion/real(homogenization_Ngrains(homog),pReal) + num%charLength**2*damage_nonlocal_getDiffusion/real(homogenization_Nconstituents(homog),pReal) end function damage_nonlocal_getDiffusion @@ -156,12 +156,12 @@ real(pReal) function damage_nonlocal_getMobility(ip,el) damage_nonlocal_getMobility = 0.0_pReal - do ipc = 1, homogenization_Ngrains(material_homogenizationAt(el)) + do ipc = 1, homogenization_Nconstituents(material_homogenizationAt(el)) damage_nonlocal_getMobility = damage_nonlocal_getMobility + lattice_M(material_phaseAt(ipc,el)) enddo damage_nonlocal_getMobility = damage_nonlocal_getMobility/& - real(homogenization_Ngrains(material_homogenizationAt(el)),pReal) + real(homogenization_Nconstituents(material_homogenizationAt(el)),pReal) end function damage_nonlocal_getMobility diff --git a/src/discretization.f90 b/src/discretization.f90 index e6e53fcf4..0b8925e4a 100644 --- a/src/discretization.f90 +++ b/src/discretization.f90 @@ -11,8 +11,8 @@ module discretization private integer, public, protected :: & - discretization_nIP, & - discretization_nElem + discretization_nIPs, & + discretization_Nelems integer, public, protected, dimension(:), allocatable :: & discretization_materialAt @@ -51,8 +51,8 @@ subroutine discretization_init(materialAt,& print'(/,a)', ' <<<+- discretization init -+>>>'; flush(6) - discretization_nElem = size(materialAt,1) - discretization_nIP = size(IPcoords0,2)/discretization_nElem + discretization_Nelems = size(materialAt,1) + discretization_nIPs = size(IPcoords0,2)/discretization_Nelems discretization_materialAt = materialAt diff --git a/src/grid/DAMASK_grid.f90 b/src/grid/DAMASK_grid.f90 index 4b8302def..a8271cffc 100644 --- a/src/grid/DAMASK_grid.f90 +++ b/src/grid/DAMASK_grid.f90 @@ -27,6 +27,19 @@ program DAMASK_grid implicit none + type :: tLoadCase + type(rotation) :: rot !< rotation of BC + type(tBoundaryCondition) :: stress, & !< stress BC + deformation !< deformation BC (dot_F, F, or L) + real(pReal) :: t, & !< length of increment + r !< ratio of geometric progression + integer :: N, & !< number of increments + f_out, & !< frequency of result writes + f_restart !< frequency of restart writes + logical :: drop_guessing !< do not follow trajectory of former loadcase + integer(kind(FIELD_UNDEFINED_ID)), allocatable :: ID(:) + end type tLoadCase + !-------------------------------------------------------------------------------------------------- ! variables related to information from load case and geom file real(pReal), dimension(9) :: temp_valueVector = 0.0_pReal !< temporarily from loadcase file when reading in tensors (initialize to 0.0) @@ -176,7 +189,7 @@ program DAMASK_grid load_step => load_steps%get(l) - step_mech => load_step%get('mech') + step_mech => load_step%get('mechanics') loadCases(l)%stress%myType='P' readMech: do m = 1, step_mech%length select case (step_mech%getKey(m)) @@ -201,38 +214,36 @@ program DAMASK_grid loadCases(l)%stress%mask = transpose(reshape(temp_maskVector,[ 3,3])) loadCases(l)%stress%values = math_9to33(temp_valueVector) end select - call loadCases(l)%rot%fromAxisAngle(step_mech%get_asFloats('R', & - defaultVal = real([0.0,0.0,1.0,0.0],pReal)),degrees=.true.) + call loadCases(l)%rot%fromAxisAngle(step_mech%get_asFloats('R',defaultVal = real([0.0,0.0,1.0,0.0],pReal)),degrees=.true.) enddo readMech if (.not. allocated(loadCases(l)%deformation%myType)) call IO_error(error_ID=837,ext_msg = 'L/F/dot_F missing') step_discretization => load_step%get('discretization') if(.not. step_discretization%contains('t')) call IO_error(error_ID=837,ext_msg = 't missing') if(.not. step_discretization%contains('N')) call IO_error(error_ID=837,ext_msg = 'N missing') - loadCases(l)%time = step_discretization%get_asFloat('t') - loadCases(l)%incs = step_discretization%get_asFloat('N') - loadCases(l)%logscale = step_discretization%get_asBool ('log_timestep', defaultVal= .false.) - loadCases(l)%outputfrequency = step_discretization%get_asInt ('f_out', defaultVal=1) - loadCases(l)%restartfrequency = step_discretization%get_asInt ('f_restart', defaultVal=huge(0)) + loadCases(l)%t = step_discretization%get_asFloat('t') + loadCases(l)%N = step_discretization%get_asInt ('N') + loadCases(l)%r = step_discretization%get_asFloat('r', defaultVal= 1.0_pReal) + loadCases(l)%f_out = step_discretization%get_asInt ('f_out', defaultVal=1) + loadCases(l)%f_restart = step_discretization%get_asInt ('f_restart', defaultVal=huge(0)) - loadCases(l)%followFormerTrajectory = .not. (load_step%get_asBool('drop_guessing',defaultVal=.false.) .or. & - merge(.false.,.true.,l > 1)) ! do not continue to predict deformation along former trajectory + loadCases(l)%drop_guessing = (load_step%get_asBool('drop_guessing',defaultVal=.false.) .or. & + merge(.false.,.true.,l > 1)) reportAndCheck: if (worldrank == 0) then write (loadcase_string, '(i0)' ) l print'(/,a,i0)', ' load case: ', l - if (.not. loadCases(l)%followFormerTrajectory) & - print*, ' drop guessing along trajectory' + print*, ' drop_guessing:', loadCases(l)%drop_guessing if (loadCases(l)%deformation%myType == 'L') then do j = 1, 3 if (any(loadCases(l)%deformation%mask(j,1:3) .eqv. .true.) .and. & any(loadCases(l)%deformation%mask(j,1:3) .eqv. .false.)) errorID = 832 ! each row should be either fully or not at all defined enddo - print*, ' velocity gradient:' + print*, ' L:' else if (loadCases(l)%deformation%myType == 'F') then - print*, ' deformation gradient at end of load case:' + print*, ' F:' else if (loadCases(l)%deformation%myType == 'dot_F') then - print*, ' deformation gradient rate:' + print*, ' dot_F:' endif do i = 1, 3; do j = 1, 3 if(loadCases(l)%deformation%mask(i,j)) then @@ -245,27 +256,32 @@ program DAMASK_grid if (any(loadCases(l)%stress%mask .eqv. loadCases(l)%deformation%mask)) errorID = 831 ! exclusive or masking only if (any(loadCases(l)%stress%mask .and. transpose(loadCases(l)%stress%mask) .and. (math_I3<1))) & errorID = 838 ! no rotation is allowed by stress BC - print*, ' stress / GPa:' + print*, ' P / MPa:' do i = 1, 3; do j = 1, 3 if(loadCases(l)%stress%mask(i,j)) then - write(IO_STDOUT,'(2x,f12.7)',advance='no') loadCases(l)%stress%values(i,j)*1e-9_pReal + write(IO_STDOUT,'(2x,f12.4)',advance='no') loadCases(l)%stress%values(i,j)*1e-6_pReal else write(IO_STDOUT,'(2x,12a)',advance='no') ' x ' endif enddo; write(IO_STDOUT,'(/)',advance='no') enddo if (any(dNeq(loadCases(l)%rot%asMatrix(), math_I3))) & - write(IO_STDOUT,'(2x,a,/,3(3(3x,f12.7,1x)/))',advance='no') 'rotation of loadframe:',& + write(IO_STDOUT,'(2x,a,/,3(3(3x,f12.7,1x)/))',advance='no') 'R:',& transpose(loadCases(l)%rot%asMatrix()) - if (loadCases(l)%time < 0.0_pReal) errorID = 834 ! negative time increment - print'(a,f0.3)', ' time: ', loadCases(l)%time - if (loadCases(l)%incs < 1) errorID = 835 ! non-positive incs count - print'(a,i0)', ' increments: ', loadCases(l)%incs - if (loadCases(l)%outputfrequency < 1) errorID = 836 ! non-positive result frequency - print'(a,i0)', ' output frequency: ', loadCases(l)%outputfrequency - if (loadCases(l)%restartfrequency < 1) errorID = 839 ! non-positive restart frequency - if (loadCases(l)%restartfrequency < huge(0)) & - print'(a,i0)', ' restart frequency: ', loadCases(l)%restartfrequency + + if (loadCases(l)%t < 0.0_pReal) errorID = 834 + print'(a,f0.3)', ' t: ', loadCases(l)%t + if (loadCases(l)%N < 1) errorID = 835 + print'(a,i0)', ' N: ', loadCases(l)%N + if (loadCases(l)%f_out < 1) errorID = 836 + print'(a,i0)', ' f_out: ', loadCases(l)%f_out + if (loadCases(l)%r <= 0.0) errorID = 833 + print'(a,f0.3)', ' r: ', loadCases(l)%r + + if (loadCases(l)%f_restart < 1) errorID = 839 + if (loadCases(l)%f_restart < huge(0)) & + print'(a,i0)', ' f_restart: ', loadCases(l)%f_restart + if (errorID > 0) call IO_error(error_ID = errorID, ext_msg = loadcase_string) ! exit with error message endif reportAndCheck enddo @@ -308,26 +324,19 @@ program DAMASK_grid loadCaseLooping: do l = 1, size(loadCases) time0 = time ! load case start time - guess = loadCases(l)%followFormerTrajectory ! change of load case? homogeneous guess for the first inc + guess = .not. loadCases(l)%drop_guessing ! change of load case? homogeneous guess for the first inc - incLooping: do inc = 1, loadCases(l)%incs + incLooping: do inc = 1, loadCases(l)%N totalIncsCounter = totalIncsCounter + 1 !-------------------------------------------------------------------------------------------------- ! forwarding time timeIncOld = timeinc ! last timeinc that brought former inc to an end - if (.not. loadCases(l)%logscale) then ! linear scale - timeinc = loadCases(l)%time/real(loadCases(l)%incs,pReal) + if (dEq(loadCases(l)%r,1.0_pReal,1.e-9_pReal)) then ! linear scale + timeinc = loadCases(l)%t/real(loadCases(l)%N,pReal) else - if (l == 1) then ! 1st load case of logarithmic scale - timeinc = loadCases(1)%time*(2.0_pReal**real(max(inc-1,1)-loadCases(1)%incs ,pReal)) ! assume 1st inc is equal to 2nd - else ! not-1st load case of logarithmic scale - timeinc = time0 * & - ( (1.0_pReal + loadCases(l)%time/time0 )**(real( inc ,pReal)/& - real(loadCases(l)%incs ,pReal))& - -(1.0_pReal + loadCases(l)%time/time0 )**(real( inc-1 ,pReal)/& - real(loadCases(l)%incs ,pReal))) - endif + timeinc = loadCases(l)%t * (loadCases(l)%r**(inc-1)-loadCases(l)%r**inc) & + / (1.0_pReal-loadCases(l)%r**loadCases(l)%N) endif timeinc = timeinc * real(subStepFactor,pReal)**real(-cutBackLevel,pReal) ! depending on cut back level, decrease time step @@ -338,7 +347,7 @@ program DAMASK_grid stepFraction = 0 ! fraction scaled by stepFactor**cutLevel subStepLooping: do while (stepFraction < subStepFactor**cutBackLevel) - remainingLoadCaseTime = loadCases(l)%time+time0 - time + remainingLoadCaseTime = loadCases(l)%t+time0 - time time = time + timeinc ! forward target time stepFraction = stepFraction + 1 ! count step @@ -347,11 +356,11 @@ program DAMASK_grid print'(/,a)', ' ###########################################################################' print'(1x,a,es12.5,6(a,i0))', & 'Time', time, & - 's: Increment ', inc,'/',loadCases(l)%incs,& + 's: Increment ', inc,'/',loadCases(l)%N,& '-', stepFraction,'/',subStepFactor**cutBackLevel,& ' of load case ', l,'/',size(loadCases) write(incInfo,'(4(a,i0))') & - 'Increment ',totalIncsCounter,'/',sum(loadCases%incs),& + 'Increment ',totalIncsCounter,'/',sum(loadCases%N),& '-', stepFraction,'/',subStepFactor**cutBackLevel flush(IO_STDOUT) @@ -420,7 +429,7 @@ program DAMASK_grid else ! no more options to continue call IO_warning(850) if (worldrank == 0) close(statUnit) - call quit(0) ! quit + call quit(0) endif enddo subStepLooping @@ -433,12 +442,12 @@ program DAMASK_grid print'(/,a,i0,a)', ' increment ', totalIncsCounter, ' NOT converged' endif; flush(IO_STDOUT) - if (mod(inc,loadCases(l)%outputFrequency) == 0) then ! at output frequency + if (mod(inc,loadCases(l)%f_out) == 0) then print'(1/,a)', ' ... writing results to file ......................................' flush(IO_STDOUT) call CPFEM_results(totalIncsCounter,time) endif - if (mod(inc,loadCases(l)%restartFrequency) == 0) then + if (mod(inc,loadCases(l)%f_restart) == 0) then call mech_restartWrite call CPFEM_restartWrite endif diff --git a/src/grid/discretization_grid.f90 b/src/grid/discretization_grid.f90 index 84223e0c8..e869c0781 100644 --- a/src/grid/discretization_grid.f90 +++ b/src/grid/discretization_grid.f90 @@ -56,25 +56,33 @@ subroutine discretization_grid_init(restart) myGrid !< domain grid of this process integer, dimension(:), allocatable :: & - materialAt + materialAt, materialAt_global integer :: & j, & - debug_element, & - debug_ip + debug_element, debug_ip, & + ierr integer(C_INTPTR_T) :: & devNull, z, z_offset + integer, dimension(worldsize) :: & + displs, sendcounts print'(/,a)', ' <<<+- discretization_grid init -+>>>'; flush(IO_STDOUT) - call readVTR(grid,geomSize,origin,materialAt) + if(worldrank == 0) call readVTR(grid,geomSize,origin,materialAt_global) + + + call MPI_Bcast(grid,3,MPI_INTEGER,0,PETSC_COMM_WORLD, ierr) + if (ierr /= 0) error stop 'MPI error' + call MPI_Bcast(geomSize,3,MPI_DOUBLE,0,PETSC_COMM_WORLD, ierr) + if (ierr /= 0) error stop 'MPI error' + call MPI_Bcast(origin,3,MPI_DOUBLE,0,PETSC_COMM_WORLD, ierr) + if (ierr /= 0) error stop 'MPI error' print'(/,a,3(i12 ))', ' grid a b c: ', grid print'(a,3(es12.5))', ' size x y z: ', geomSize print'(a,3(es12.5))', ' origin x y z: ', origin -!-------------------------------------------------------------------------------------------------- -! grid solver specific quantities if(worldsize>grid(3)) call IO_error(894, ext_msg='number of processes exceeds grid(3)') call fftw_mpi_init @@ -84,6 +92,8 @@ subroutine discretization_grid_init(restart) PETSC_COMM_WORLD, & z, & ! domain grid size along z z_offset) ! domain grid offset along z + if(z==0_C_INTPTR_T) call IO_error(894, ext_msg='Cannot distribute MPI processes') + grid3 = int(z) grid3Offset = int(z_offset) size3 = geomSize(3)*real(grid3,pReal) /real(grid(3),pReal) @@ -91,14 +101,14 @@ subroutine discretization_grid_init(restart) myGrid = [grid(1:2),grid3] mySize = [geomSize(1:2),size3] -!------------------------------------------------------------------------------------------------- -! debug parameters - debug_element = config_debug%get_asInt('element',defaultVal=1) - debug_ip = config_debug%get_asInt('integrationpoint',defaultVal=1) + call MPI_Gather(product(grid(1:2))*grid3Offset,1,MPI_INTEGER,displs, 1,MPI_INTEGER,0,PETSC_COMM_WORLD,ierr) + if (ierr /= 0) error stop 'MPI error' + call MPI_Gather(product(myGrid), 1,MPI_INTEGER,sendcounts,1,MPI_INTEGER,0,PETSC_COMM_WORLD,ierr) + if (ierr /= 0) error stop 'MPI error' -!-------------------------------------------------------------------------------------------------- -! general discretization - materialAt = materialAt(product(grid(1:2))*grid3Offset+1:product(grid(1:2))*(grid3Offset+grid3)) ! reallocate/shrink in case of MPI + allocate(materialAt(product(myGrid))) + call MPI_scatterv(materialAt_global,sendcounts,displs,MPI_INTEGER,materialAt,size(materialAt),MPI_INTEGER,0,PETSC_COMM_WORLD,ierr) + if (ierr /= 0) error stop 'MPI error' call discretization_init(materialAt, & IPcoordinates0(myGrid,mySize,grid3Offset), & @@ -129,10 +139,12 @@ subroutine discretization_grid_init(restart) call geometry_plastic_nonlocal_setIPareaNormal (cellSurfaceNormal(product(myGrid))) call geometry_plastic_nonlocal_setIPneighborhood(IPneighborhood(myGrid)) -!-------------------------------------------------------------------------------------------------- -! sanity checks for debugging - if (debug_element < 1 .or. debug_element > product(myGrid)) call IO_error(602,ext_msg='element') ! selected element does not exist - if (debug_ip /= 1) call IO_error(602,ext_msg='IP') ! selected IP does not exist +!------------------------------------------------------------------------------------------------- +! debug parameters + debug_element = config_debug%get_asInt('element',defaultVal=1) + if (debug_element < 1 .or. debug_element > product(myGrid)) call IO_error(602,ext_msg='element') + debug_ip = config_debug%get_asInt('integrationpoint',defaultVal=1) + if (debug_ip /= 1) call IO_error(602,ext_msg='IP') end subroutine discretization_grid_init diff --git a/src/grid/grid_damage_spectral.f90 b/src/grid/grid_damage_spectral.f90 index 7e529fc68..4c014f3c0 100644 --- a/src/grid/grid_damage_spectral.f90 +++ b/src/grid/grid_damage_spectral.f90 @@ -95,10 +95,10 @@ subroutine grid_damage_spectral_init !-------------------------------------------------------------------------------------------------- ! set default and user defined options for PETSc - call PETScOptionsInsertString(PETSC_NULL_OPTIONS,'-damage_snes_type newtonls -damage_snes_mf & + call PetscOptionsInsertString(PETSC_NULL_OPTIONS,'-damage_snes_type newtonls -damage_snes_mf & &-damage_snes_ksp_ew -damage_ksp_type fgmres',ierr) CHKERRQ(ierr) - call PETScOptionsInsertString(PETSC_NULL_OPTIONS,num_grid%get_asString('petsc_options',defaultVal=''),ierr) + call PetscOptionsInsertString(PETSC_NULL_OPTIONS,num_grid%get_asString('petsc_options',defaultVal=''),ierr) CHKERRQ(ierr) !-------------------------------------------------------------------------------------------------- diff --git a/src/grid/grid_mech_FEM.f90 b/src/grid/grid_mech_FEM.f90 index dfbd3f2f3..7d0830f67 100644 --- a/src/grid/grid_mech_FEM.f90 +++ b/src/grid/grid_mech_FEM.f90 @@ -13,6 +13,7 @@ module grid_mech_FEM use prec use parallelization use DAMASK_interface + use IO use HDF5_utilities use math use spectral_utilities @@ -141,10 +142,10 @@ subroutine grid_mech_FEM_init !-------------------------------------------------------------------------------------------------- ! set default and user defined options for PETSc - call PETScOptionsInsertString(PETSC_NULL_OPTIONS,'-mech_snes_type newtonls -mech_ksp_type fgmres & + call PetscOptionsInsertString(PETSC_NULL_OPTIONS,'-mech_snes_type newtonls -mech_ksp_type fgmres & &-mech_ksp_max_it 25 -mech_pc_type ml -mech_mg_levels_ksp_type chebyshev',ierr) CHKERRQ(ierr) - call PETScOptionsInsertString(PETSC_NULL_OPTIONS,num_grid%get_asString('petsc_options',defaultVal=''),ierr) + call PetscOptionsInsertString(PETSC_NULL_OPTIONS,num_grid%get_asString('petsc_options',defaultVal=''),ierr) CHKERRQ(ierr) !-------------------------------------------------------------------------------------------------- @@ -236,7 +237,7 @@ subroutine grid_mech_FEM_init F = spread(spread(spread(math_I3,3,grid(1)),4,grid(2)),5,grid3) endif restartRead - materialpoint_F0 = reshape(F_lastInc, [3,3,1,product(grid(1:2))*grid3]) ! set starting condition for materialpoint_stressAndItsTangent + homogenization_F0 = reshape(F_lastInc, [3,3,1,product(grid(1:2))*grid3]) ! set starting condition for materialpoint_stressAndItsTangent call utilities_updateCoords(F) call utilities_constitutiveResponse(P_current,temp33_Real,C_volAvg,devNull, & ! stress field, stress avg, global average of stiffness and (min+max)/2 F, & ! target F @@ -364,7 +365,7 @@ subroutine grid_mech_FEM_forward(cutBack,guess,timeinc,timeinc_old,loadCaseTime, F_lastInc = F - materialpoint_F0 = reshape(F, [3,3,1,product(grid(1:2))*grid3]) + homogenization_F0 = reshape(F, [3,3,1,product(grid(1:2))*grid3]) endif !-------------------------------------------------------------------------------------------------- @@ -557,9 +558,9 @@ subroutine formResidual(da_local,x_local, & ii = i-xstart+1; jj = j-ystart+1; kk = k-zstart+1 ele = ele + 1 f_elem = matmul(transpose(BMat),transpose(P_current(1:3,1:3,ii,jj,kk)))*detJ + & - matmul(HGMat,x_elem)*(materialpoint_dPdF(1,1,1,1,1,ele) + & - materialpoint_dPdF(2,2,2,2,1,ele) + & - materialpoint_dPdF(3,3,3,3,1,ele))/3.0_pReal + matmul(HGMat,x_elem)*(homogenization_dPdF(1,1,1,1,1,ele) + & + homogenization_dPdF(2,2,2,2,1,ele) + & + homogenization_dPdF(3,3,3,3,1,ele))/3.0_pReal ctr = 0 do kk = 0, 1; do jj = 0, 1; do ii = 0, 1 ctr = ctr + 1 @@ -636,18 +637,18 @@ subroutine formJacobian(da_local,x_local,Jac_pre,Jac,dummy,ierr) row = col ele = ele + 1 K_ele = 0.0 - K_ele(1 :8 ,1 :8 ) = HGMat*(materialpoint_dPdF(1,1,1,1,1,ele) + & - materialpoint_dPdF(2,2,2,2,1,ele) + & - materialpoint_dPdF(3,3,3,3,1,ele))/3.0_pReal - K_ele(9 :16,9 :16) = HGMat*(materialpoint_dPdF(1,1,1,1,1,ele) + & - materialpoint_dPdF(2,2,2,2,1,ele) + & - materialpoint_dPdF(3,3,3,3,1,ele))/3.0_pReal - K_ele(17:24,17:24) = HGMat*(materialpoint_dPdF(1,1,1,1,1,ele) + & - materialpoint_dPdF(2,2,2,2,1,ele) + & - materialpoint_dPdF(3,3,3,3,1,ele))/3.0_pReal + K_ele(1 :8 ,1 :8 ) = HGMat*(homogenization_dPdF(1,1,1,1,1,ele) + & + homogenization_dPdF(2,2,2,2,1,ele) + & + homogenization_dPdF(3,3,3,3,1,ele))/3.0_pReal + K_ele(9 :16,9 :16) = HGMat*(homogenization_dPdF(1,1,1,1,1,ele) + & + homogenization_dPdF(2,2,2,2,1,ele) + & + homogenization_dPdF(3,3,3,3,1,ele))/3.0_pReal + K_ele(17:24,17:24) = HGMat*(homogenization_dPdF(1,1,1,1,1,ele) + & + homogenization_dPdF(2,2,2,2,1,ele) + & + homogenization_dPdF(3,3,3,3,1,ele))/3.0_pReal K_ele = K_ele + & matmul(transpose(BMatFull), & - matmul(reshape(reshape(materialpoint_dPdF(1:3,1:3,1:3,1:3,1,ele), & + matmul(reshape(reshape(homogenization_dPdF(1:3,1:3,1:3,1:3,1,ele), & shape=[3,3,3,3], order=[2,1,4,3]),shape=[9,9]),BMatFull))*detJ call MatSetValuesStencil(Jac,24,row,24,col,K_ele,ADD_VALUES,ierr) CHKERRQ(ierr) diff --git a/src/grid/grid_mech_spectral_basic.f90 b/src/grid/grid_mech_spectral_basic.f90 index d2f6b40da..8677a998f 100644 --- a/src/grid/grid_mech_spectral_basic.f90 +++ b/src/grid/grid_mech_spectral_basic.f90 @@ -13,6 +13,7 @@ module grid_mech_spectral_basic use prec use parallelization use DAMASK_interface + use IO use HDF5_utilities use math use spectral_utilities @@ -140,9 +141,9 @@ subroutine grid_mech_spectral_basic_init !-------------------------------------------------------------------------------------------------- ! set default and user defined options for PETSc - call PETScOptionsInsertString(PETSC_NULL_OPTIONS,'-mech_snes_type ngmres',ierr) + call PetscOptionsInsertString(PETSC_NULL_OPTIONS,'-mech_snes_type ngmres',ierr) CHKERRQ(ierr) - call PETScOptionsInsertString(PETSC_NULL_OPTIONS,num_grid%get_asString('petsc_options',defaultVal=''),ierr) + call PetscOptionsInsertString(PETSC_NULL_OPTIONS,num_grid%get_asString('petsc_options',defaultVal=''),ierr) CHKERRQ(ierr) !-------------------------------------------------------------------------------------------------- @@ -198,7 +199,7 @@ subroutine grid_mech_spectral_basic_init F = reshape(F_lastInc,[9,grid(1),grid(2),grid3]) endif restartRead - materialpoint_F0 = reshape(F_lastInc, [3,3,1,product(grid(1:2))*grid3]) ! set starting condition for materialpoint_stressAndItsTangent + homogenization_F0 = reshape(F_lastInc, [3,3,1,product(grid(1:2))*grid3]) ! set starting condition for materialpoint_stressAndItsTangent call utilities_updateCoords(reshape(F,shape(F_lastInc))) call utilities_constitutiveResponse(P,temp33_Real,C_volAvg,C_minMaxAvg, & ! stress field, stress avg, global average of stiffness and (min+max)/2 reshape(F,shape(F_lastInc)), & ! target F @@ -324,7 +325,7 @@ subroutine grid_mech_spectral_basic_forward(cutBack,guess,timeinc,timeinc_old,lo rotation_BC%rotate(F_aimDot,active=.true.)) F_lastInc = reshape(F,[3,3,grid(1),grid(2),grid3]) - materialpoint_F0 = reshape(F, [3,3,1,product(grid(1:2))*grid3]) + homogenization_F0 = reshape(F, [3,3,1,product(grid(1:2))*grid3]) endif !-------------------------------------------------------------------------------------------------- diff --git a/src/grid/grid_mech_spectral_polarisation.f90 b/src/grid/grid_mech_spectral_polarisation.f90 index 3d495ddf0..053d958ad 100644 --- a/src/grid/grid_mech_spectral_polarisation.f90 +++ b/src/grid/grid_mech_spectral_polarisation.f90 @@ -13,6 +13,7 @@ module grid_mech_spectral_polarisation use prec use parallelization use DAMASK_interface + use IO use HDF5_utilities use math use spectral_utilities @@ -158,9 +159,9 @@ subroutine grid_mech_spectral_polarisation_init !-------------------------------------------------------------------------------------------------- ! set default and user defined options for PETSc - call PETScOptionsInsertString(PETSC_NULL_OPTIONS,'-mech_snes_type ngmres',ierr) + call PetscOptionsInsertString(PETSC_NULL_OPTIONS,'-mech_snes_type ngmres',ierr) CHKERRQ(ierr) - call PETScOptionsInsertString(PETSC_NULL_OPTIONS,num_grid%get_asString('petsc_options',defaultVal=''),ierr) + call PetscOptionsInsertString(PETSC_NULL_OPTIONS,num_grid%get_asString('petsc_options',defaultVal=''),ierr) CHKERRQ(ierr) !-------------------------------------------------------------------------------------------------- @@ -224,7 +225,7 @@ subroutine grid_mech_spectral_polarisation_init F_tau_lastInc = 2.0_pReal*F_lastInc endif restartRead - materialpoint_F0 = reshape(F_lastInc, [3,3,1,product(grid(1:2))*grid3]) ! set starting condition for materialpoint_stressAndItsTangent + homogenization_F0 = reshape(F_lastInc, [3,3,1,product(grid(1:2))*grid3]) ! set starting condition for materialpoint_stressAndItsTangent call utilities_updateCoords(reshape(F,shape(F_lastInc))) call utilities_constitutiveResponse(P,temp33_Real,C_volAvg,C_minMaxAvg, & ! stress field, stress avg, global average of stiffness and (min+max)/2 reshape(F,shape(F_lastInc)), & ! target F @@ -364,7 +365,7 @@ subroutine grid_mech_spectral_polarisation_forward(cutBack,guess,timeinc,timeinc F_lastInc = reshape(F, [3,3,grid(1),grid(2),grid3]) F_tau_lastInc = reshape(F_tau,[3,3,grid(1),grid(2),grid3]) - materialpoint_F0 = reshape(F,[3,3,1,product(grid(1:2))*grid3]) + homogenization_F0 = reshape(F,[3,3,1,product(grid(1:2))*grid3]) endif !-------------------------------------------------------------------------------------------------- @@ -497,7 +498,7 @@ subroutine converged(snes_local,PETScIter,devNull1,devNull2,devNull3,reason,dumm err_div/divTol, ' (',err_div, ' / m, tol = ',divTol,')' print '(a,f12.2,a,es8.2,a,es9.2,a)', ' error curl = ', & err_curl/curlTol,' (',err_curl,' -, tol = ',curlTol,')' - print '(a,f12.2,a,es8.2,a,es9.2,a)', ' error stress BC = ', & + print '(a,f12.2,a,es8.2,a,es9.2,a)', ' error stress BC = ', & err_BC/BCTol, ' (',err_BC, ' Pa, tol = ',BCTol,')' print'(/,a)', ' ===========================================================================' flush(IO_STDOUT) @@ -606,7 +607,7 @@ subroutine formResidual(in, FandF_tau, & do k = 1, grid3; do j = 1, grid(2); do i = 1, grid(1) e = e + 1 residual_F(1:3,1:3,i,j,k) = & - math_mul3333xx33(math_invSym3333(materialpoint_dPdF(1:3,1:3,1:3,1:3,1,e) + C_scale), & + math_mul3333xx33(math_invSym3333(homogenization_dPdF(1:3,1:3,1:3,1:3,1,e) + C_scale), & residual_F(1:3,1:3,i,j,k) - matmul(F(1:3,1:3,i,j,k), & math_mul3333xx33(C_scale,F_tau(1:3,1:3,i,j,k) - F(1:3,1:3,i,j,k) - math_I3))) & + residual_F_tau(1:3,1:3,i,j,k) diff --git a/src/grid/grid_thermal_spectral.f90 b/src/grid/grid_thermal_spectral.f90 index b4f9acddb..68a1c5ed1 100644 --- a/src/grid/grid_thermal_spectral.f90 +++ b/src/grid/grid_thermal_spectral.f90 @@ -89,9 +89,9 @@ subroutine grid_thermal_spectral_init !-------------------------------------------------------------------------------------------------- ! set default and user defined options for PETSc - call PETScOptionsInsertString(PETSC_NULL_OPTIONS,'-thermal_snes_type ngmres',ierr) + call PetscOptionsInsertString(PETSC_NULL_OPTIONS,'-thermal_snes_type ngmres',ierr) CHKERRQ(ierr) - call PETScOptionsInsertString(PETSC_NULL_OPTIONS,num_grid%get_asString('petsc_options',defaultVal=''),ierr) + call PetscOptionsInsertString(PETSC_NULL_OPTIONS,num_grid%get_asString('petsc_options',defaultVal=''),ierr) CHKERRQ(ierr) !-------------------------------------------------------------------------------------------------- diff --git a/src/grid/spectral_utilities.f90 b/src/grid/spectral_utilities.f90 index 77047a317..1e1608d7c 100644 --- a/src/grid/spectral_utilities.f90 +++ b/src/grid/spectral_utilities.f90 @@ -5,6 +5,7 @@ !-------------------------------------------------------------------------------------------------- module spectral_utilities use, intrinsic :: iso_c_binding + #include use PETScSys @@ -14,6 +15,7 @@ module spectral_utilities use math use rotations use IO + use config use discretization_grid use discretization use homogenization @@ -87,19 +89,6 @@ module spectral_utilities character(len=:), allocatable :: myType end type tBoundaryCondition - type, public :: tLoadCase - type(rotation) :: rot !< rotation of BC - type(tBoundaryCondition) :: stress, & !< stress BC - deformation !< deformation BC (dot_F, F, or L) - real(pReal) :: time !< length of increment - integer :: incs, & !< number of increments - outputfrequency, & !< frequency of result writes - restartfrequency !< frequency of restart writes - logical :: followFormerTrajectory, & !< follow trajectory of former loadcase - logscale !< logarithmic time inc flag - integer(kind(FIELD_UNDEFINED_ID)), allocatable :: ID(:) - end type tLoadCase - type, public :: tSolutionParams real(pReal), dimension(3,3) :: stress_BC logical, dimension(3,3) :: stress_mask @@ -214,11 +203,11 @@ subroutine spectral_utilities_init num_grid => config_numerics%get('grid',defaultVal=emptyDict) - call PETScOptionsClear(PETSC_NULL_OPTIONS,ierr) + call PetscOptionsClear(PETSC_NULL_OPTIONS,ierr) CHKERRQ(ierr) - if(debugPETSc) call PETScOptionsInsertString(PETSC_NULL_OPTIONS,trim(PETSCDEBUG),ierr) + if(debugPETSc) call PetscOptionsInsertString(PETSC_NULL_OPTIONS,trim(PETSCDEBUG),ierr) CHKERRQ(ierr) - call PETScOptionsInsertString(PETSC_NULL_OPTIONS,& + call PetscOptionsInsertString(PETSC_NULL_OPTIONS,& num_grid%get_asString('petsc_options',defaultVal=''),ierr) CHKERRQ(ierr) @@ -313,12 +302,12 @@ subroutine spectral_utilities_init tensorSize, FFTW_MPI_DEFAULT_BLOCK, FFTW_MPI_DEFAULT_BLOCK, &! no. of transforms, default iblock and oblock tensorField_real, tensorField_fourier, & ! input data, output data PETSC_COMM_WORLD, FFTW_planner_flag) ! use all processors, planer precision - if (.not. C_ASSOCIATED(planTensorForth)) call IO_error(810, ext_msg='planTensorForth') + if (.not. C_ASSOCIATED(planTensorForth)) error stop 'FFTW error' planTensorBack = fftw_mpi_plan_many_dft_c2r(3, [gridFFTW(3),gridFFTW(2),gridFFTW(1)], & ! dimension, logical length in each dimension in reversed order tensorSize, FFTW_MPI_DEFAULT_BLOCK, FFTW_MPI_DEFAULT_BLOCK, &! no. of transforms, default iblock and oblock tensorField_fourier,tensorField_real, & ! input data, output data PETSC_COMM_WORLD, FFTW_planner_flag) ! all processors, planer precision - if (.not. C_ASSOCIATED(planTensorBack)) call IO_error(810, ext_msg='planTensorBack') + if (.not. C_ASSOCIATED(planTensorBack)) error stop 'FFTW error' !-------------------------------------------------------------------------------------------------- ! vector MPI fftw plans @@ -326,12 +315,12 @@ subroutine spectral_utilities_init vecSize, FFTW_MPI_DEFAULT_BLOCK, FFTW_MPI_DEFAULT_BLOCK,&! no. of transforms, default iblock and oblock vectorField_real, vectorField_fourier, & ! input data, output data PETSC_COMM_WORLD, FFTW_planner_flag) ! use all processors, planer precision - if (.not. C_ASSOCIATED(planVectorForth)) call IO_error(810, ext_msg='planVectorForth') + if (.not. C_ASSOCIATED(planVectorForth)) error stop 'FFTW error' planVectorBack = fftw_mpi_plan_many_dft_c2r(3, [gridFFTW(3),gridFFTW(2),gridFFTW(1)], & ! dimension, logical length in each dimension in reversed order vecSize, FFTW_MPI_DEFAULT_BLOCK, FFTW_MPI_DEFAULT_BLOCK, & ! no. of transforms, default iblock and oblock vectorField_fourier,vectorField_real, & ! input data, output data PETSC_COMM_WORLD, FFTW_planner_flag) ! all processors, planer precision - if (.not. C_ASSOCIATED(planVectorBack)) call IO_error(810, ext_msg='planVectorBack') + if (.not. C_ASSOCIATED(planVectorBack)) error stop 'FFTW error' !-------------------------------------------------------------------------------------------------- ! scalar MPI fftw plans @@ -339,12 +328,12 @@ subroutine spectral_utilities_init scalarSize, FFTW_MPI_DEFAULT_BLOCK, FFTW_MPI_DEFAULT_BLOCK, &! no. of transforms, default iblock and oblock scalarField_real, scalarField_fourier, & ! input data, output data PETSC_COMM_WORLD, FFTW_planner_flag) ! use all processors, planer precision - if (.not. C_ASSOCIATED(planScalarForth)) call IO_error(810, ext_msg='planScalarForth') + if (.not. C_ASSOCIATED(planScalarForth)) error stop 'FFTW error' planScalarBack = fftw_mpi_plan_many_dft_c2r(3, [gridFFTW(3),gridFFTW(2),gridFFTW(1)], & ! dimension, logical length in each dimension in reversed order, no. of transforms scalarSize, FFTW_MPI_DEFAULT_BLOCK, FFTW_MPI_DEFAULT_BLOCK, &! no. of transforms, default iblock and oblock scalarField_fourier,scalarField_real, & ! input data, output data PETSC_COMM_WORLD, FFTW_planner_flag) ! use all processors, planer precision - if (.not. C_ASSOCIATED(planScalarBack)) call IO_error(810, ext_msg='planScalarBack') + if (.not. C_ASSOCIATED(planScalarBack)) error stop 'FFTW error' !-------------------------------------------------------------------------------------------------- ! calculation of discrete angular frequencies, ordered as in FFTW (wrap around) @@ -603,10 +592,9 @@ real(pReal) function utilities_divergenceRMS() enddo; enddo if(grid(1) == 1) utilities_divergenceRMS = utilities_divergenceRMS * 0.5_pReal ! counted twice in case of grid(1) == 1 call MPI_Allreduce(MPI_IN_PLACE,utilities_divergenceRMS,1,MPI_DOUBLE,MPI_SUM,PETSC_COMM_WORLD,ierr) - if(ierr /=0) call IO_error(894, ext_msg='utilities_divergenceRMS') + if(ierr /=0) error stop 'MPI error' utilities_divergenceRMS = sqrt(utilities_divergenceRMS) * wgt ! RMS in real space calculated with Parsevals theorem from Fourier space - end function utilities_divergenceRMS @@ -664,7 +652,7 @@ real(pReal) function utilities_curlRMS() enddo; enddo call MPI_Allreduce(MPI_IN_PLACE,utilities_curlRMS,1,MPI_DOUBLE,MPI_SUM,PETSC_COMM_WORLD,ierr) - if(ierr /=0) call IO_error(894, ext_msg='utilities_curlRMS') + if(ierr /=0) error stop 'MPI error' utilities_curlRMS = sqrt(utilities_curlRMS) * wgt if(grid(1) == 1) utilities_curlRMS = utilities_curlRMS * 0.5_pReal ! counted twice in case of grid(1) == 1 @@ -713,7 +701,6 @@ function utilities_maskedCompliance(rot_BC,mask_stress,C) allocate(s_reduced,mold = c_reduced) call math_invert(s_reduced, errmatinv, c_reduced) ! invert reduced stiffness if (any(IEEE_is_NaN(s_reduced))) errmatinv = .true. - if (errmatinv) call IO_error(error_ID=400,ext_msg='utilities_maskedCompliance') !-------------------------------------------------------------------------------------------------- ! check if inversion was successful @@ -725,7 +712,7 @@ function utilities_maskedCompliance(rot_BC,mask_stress,C) write(IO_STDOUT,trim(formatString),advance='no') ' C * S (load) ', & transpose(matmul(c_reduced,s_reduced)) write(IO_STDOUT,trim(formatString),advance='no') ' S (load) ', transpose(s_reduced) - if(errmatinv) call IO_error(error_ID=400,ext_msg='utilities_maskedCompliance') + if(errmatinv) error stop 'matrix inversion error' endif temp99_real = reshape(unpack(reshape(s_reduced,[size_reduced**2]),reshape(mask,[81]),0.0_pReal),[9,9]) else @@ -802,7 +789,7 @@ end subroutine utilities_fourierTensorDivergence !-------------------------------------------------------------------------------------------------- -!> @brief calculate constitutive response from materialpoint_F0 to F during timeinc +!> @brief calculate constitutive response from homogenization_F0 to F during timeinc !-------------------------------------------------------------------------------------------------- subroutine utilities_constitutiveResponse(P,P_av,C_volAvg,C_minmaxAvg,& F,timeinc,rotation_BC) @@ -824,11 +811,11 @@ subroutine utilities_constitutiveResponse(P,P_av,C_volAvg,C_minmaxAvg,& print'(/,a)', ' ... evaluating constitutive response ......................................' flush(IO_STDOUT) - materialpoint_F = reshape(F,[3,3,1,product(grid(1:2))*grid3]) ! set materialpoint target F to estimated field + homogenization_F = reshape(F,[3,3,1,product(grid(1:2))*grid3]) ! set materialpoint target F to estimated field call materialpoint_stressAndItsTangent(timeinc) ! calculate P field - P = reshape(materialpoint_P, [3,3,grid(1),grid(2),grid3]) + P = reshape(homogenization_P, [3,3,grid(1),grid(2),grid3]) P_av = sum(sum(sum(P,dim=5),dim=4),dim=3) * wgt ! average of P call MPI_Allreduce(MPI_IN_PLACE,P_av,9,MPI_DOUBLE,MPI_SUM,PETSC_COMM_WORLD,ierr) if (debugRotation) & @@ -845,32 +832,33 @@ subroutine utilities_constitutiveResponse(P,P_av,C_volAvg,C_minmaxAvg,& dPdF_min = huge(1.0_pReal) dPdF_norm_min = huge(1.0_pReal) do i = 1, product(grid(1:2))*grid3 - if (dPdF_norm_max < sum(materialpoint_dPdF(1:3,1:3,1:3,1:3,1,i)**2.0_pReal)) then - dPdF_max = materialpoint_dPdF(1:3,1:3,1:3,1:3,1,i) - dPdF_norm_max = sum(materialpoint_dPdF(1:3,1:3,1:3,1:3,1,i)**2.0_pReal) + if (dPdF_norm_max < sum(homogenization_dPdF(1:3,1:3,1:3,1:3,1,i)**2.0_pReal)) then + dPdF_max = homogenization_dPdF(1:3,1:3,1:3,1:3,1,i) + dPdF_norm_max = sum(homogenization_dPdF(1:3,1:3,1:3,1:3,1,i)**2.0_pReal) endif - if (dPdF_norm_min > sum(materialpoint_dPdF(1:3,1:3,1:3,1:3,1,i)**2.0_pReal)) then - dPdF_min = materialpoint_dPdF(1:3,1:3,1:3,1:3,1,i) - dPdF_norm_min = sum(materialpoint_dPdF(1:3,1:3,1:3,1:3,1,i)**2.0_pReal) + if (dPdF_norm_min > sum(homogenization_dPdF(1:3,1:3,1:3,1:3,1,i)**2.0_pReal)) then + dPdF_min = homogenization_dPdF(1:3,1:3,1:3,1:3,1,i) + dPdF_norm_min = sum(homogenization_dPdF(1:3,1:3,1:3,1:3,1,i)**2.0_pReal) endif end do valueAndRank = [dPdF_norm_max,real(worldrank,pReal)] call MPI_Allreduce(MPI_IN_PLACE,valueAndRank,1, MPI_2DOUBLE_PRECISION, MPI_MAXLOC, PETSC_COMM_WORLD, ierr) - if (ierr /= 0) call IO_error(894, ext_msg='MPI_Allreduce max') + if (ierr /= 0) error stop 'MPI error' call MPI_Bcast(dPdF_max,81,MPI_DOUBLE,int(valueAndRank(2)),PETSC_COMM_WORLD, ierr) - if (ierr /= 0) call IO_error(894, ext_msg='MPI_Bcast max') + if (ierr /= 0) error stop 'MPI error' valueAndRank = [dPdF_norm_min,real(worldrank,pReal)] call MPI_Allreduce(MPI_IN_PLACE,valueAndRank,1, MPI_2DOUBLE_PRECISION, MPI_MINLOC, PETSC_COMM_WORLD, ierr) - if (ierr /= 0) call IO_error(894, ext_msg='MPI_Allreduce min') + if (ierr /= 0) error stop 'MPI error' call MPI_Bcast(dPdF_min,81,MPI_DOUBLE,int(valueAndRank(2)),PETSC_COMM_WORLD, ierr) - if (ierr /= 0) call IO_error(894, ext_msg='MPI_Bcast min') + if (ierr /= 0) error stop 'MPI error' C_minmaxAvg = 0.5_pReal*(dPdF_max + dPdF_min) - C_volAvg = sum(sum(materialpoint_dPdF,dim=6),dim=5) + C_volAvg = sum(sum(homogenization_dPdF,dim=6),dim=5) call MPI_Allreduce(MPI_IN_PLACE,C_volAvg,81,MPI_DOUBLE,MPI_SUM,PETSC_COMM_WORLD,ierr) + if (ierr /= 0) error stop 'MPI error' C_volAvg = C_volAvg * wgt @@ -994,12 +982,11 @@ subroutine utilities_updateCoords(F) real(pReal), dimension(3, grid(1)+1,grid(2)+1,grid3+1) :: nodeCoords integer :: & i,j,k,n, & - rank_t, & - rank_b, & - c, r, & + rank_t, rank_b, & + c, & ierr - integer, dimension(MPI_STATUS_SIZE) :: & - s + integer, dimension(4) :: request + integer, dimension(MPI_STATUS_SIZE,4) :: status real(pReal), dimension(3) :: step real(pReal), dimension(3,3) :: Favg integer, dimension(3) :: me @@ -1035,7 +1022,7 @@ subroutine utilities_updateCoords(F) ! average F if (grid3Offset == 0) Favg = real(tensorField_fourier(1:3,1:3,1,1,1),pReal)*wgt call MPI_Bcast(Favg,9,MPI_DOUBLE,0,PETSC_COMM_WORLD,ierr) - if(ierr /=0) call IO_error(894, ext_msg='update_IPcoords/MPI_Bcast') + if(ierr /=0) error stop 'MPI error' !-------------------------------------------------------------------------------------------------- ! pad cell center fluctuations along z-direction (needed when running MPI simulation) @@ -1045,20 +1032,20 @@ subroutine utilities_updateCoords(F) rank_b = modulo(worldrank-1,worldsize) ! send bottom layer to process below - call MPI_Isend(IPfluct_padded(:,:,:,2), c,MPI_DOUBLE,rank_b,0,PETSC_COMM_WORLD,r,ierr) - if(ierr /=0) call IO_error(894, ext_msg='update_IPcoords/MPI_Isend') - call MPI_Irecv(IPfluct_padded(:,:,:,grid3+2),c,MPI_DOUBLE,rank_t,0,PETSC_COMM_WORLD,r,ierr) - if(ierr /=0) call IO_error(894, ext_msg='update_IPcoords/MPI_Irecv') - call MPI_Wait(r,s,ierr) - if(ierr /=0) call IO_error(894, ext_msg='update_IPcoords/MPI_Wait') + call MPI_Isend(IPfluct_padded(:,:,:,2), c,MPI_DOUBLE,rank_b,0,PETSC_COMM_WORLD,request(1),ierr) + if(ierr /=0) error stop 'MPI error' + call MPI_Irecv(IPfluct_padded(:,:,:,grid3+2),c,MPI_DOUBLE,rank_t,0,PETSC_COMM_WORLD,request(2),ierr) + if(ierr /=0) error stop 'MPI error' ! send top layer to process above - call MPI_Isend(IPfluct_padded(:,:,:,grid3+1),c,MPI_DOUBLE,rank_t,0,PETSC_COMM_WORLD,r,ierr) - if(ierr /=0) call IO_error(894, ext_msg='update_IPcoords/MPI_Isend') - call MPI_Irecv(IPfluct_padded(:,:,:,1), c,MPI_DOUBLE,rank_b,0,PETSC_COMM_WORLD,r,ierr) - if(ierr /=0) call IO_error(894, ext_msg='update_IPcoords/MPI_Irecv') - call MPI_Wait(r,s,ierr) - if(ierr /=0) call IO_error(894, ext_msg='update_IPcoords/MPI_Wait') + call MPI_Isend(IPfluct_padded(:,:,:,grid3+1),c,MPI_DOUBLE,rank_t,1,PETSC_COMM_WORLD,request(3),ierr) + if(ierr /=0) error stop 'MPI error' + call MPI_Irecv(IPfluct_padded(:,:,:,1), c,MPI_DOUBLE,rank_b,1,PETSC_COMM_WORLD,request(4),ierr) + if(ierr /=0) error stop 'MPI error' + + call MPI_Waitall(4,request,status,ierr) + if(ierr /=0) error stop 'MPI error' + if(any(status(MPI_ERROR,:) /= 0)) error stop 'MPI error' !-------------------------------------------------------------------------------------------------- ! calculate nodal displacements diff --git a/src/homogenization.f90 b/src/homogenization.f90 index 5aa10fcf3..347634212 100644 --- a/src/homogenization.f90 +++ b/src/homogenization.f90 @@ -31,12 +31,12 @@ module homogenization !-------------------------------------------------------------------------------------------------- ! General variables for the homogenization at a material point real(pReal), dimension(:,:,:,:), allocatable, public :: & - materialpoint_F0, & !< def grad of IP at start of FE increment - materialpoint_F !< def grad of IP to be reached at end of FE increment + homogenization_F0, & !< def grad of IP at start of FE increment + homogenization_F !< def grad of IP to be reached at end of FE increment real(pReal), dimension(:,:,:,:), allocatable, public, protected :: & - materialpoint_P !< first P--K stress of IP + homogenization_P !< first P--K stress of IP real(pReal), dimension(:,:,:,:,:,:), allocatable, public, protected :: & - materialpoint_dPdF !< tangent of first P--K stress at IP + homogenization_dPdF !< tangent of first P--K stress at IP type :: tNumerics integer :: & @@ -158,7 +158,7 @@ subroutine homogenization_init debugHomog%grain = config_debug%get_asInt('grain',defaultVal = 1) if (debugHomog%grain < 1 & - .or. debugHomog%grain > homogenization_Ngrains(material_homogenizationAt(debugHomog%element))) & + .or. debugHomog%grain > homogenization_Nconstituents(material_homogenizationAt(debugHomog%element))) & call IO_error(602,ext_msg='constituent', el=debugHomog%element, g=debugHomog%grain) @@ -181,10 +181,10 @@ subroutine homogenization_init !-------------------------------------------------------------------------------------------------- ! allocate and initialize global variables - allocate(materialpoint_dPdF(3,3,3,3,discretization_nIP,discretization_nElem), source=0.0_pReal) - materialpoint_F0 = spread(spread(math_I3,3,discretization_nIP),4,discretization_nElem) ! initialize to identity - materialpoint_F = materialpoint_F0 ! initialize to identity - allocate(materialpoint_P(3,3,discretization_nIP,discretization_nElem), source=0.0_pReal) + allocate(homogenization_dPdF(3,3,3,3,discretization_nIPs,discretization_Nelems), source=0.0_pReal) + homogenization_F0 = spread(spread(math_I3,3,discretization_nIPs),4,discretization_Nelems) ! initialize to identity + homogenization_F = homogenization_F0 ! initialize to identity + allocate(homogenization_P(3,3,discretization_nIPs,discretization_Nelems), source=0.0_pReal) print'(/,a)', ' <<<+- homogenization init -+>>>'; flush(IO_STDOUT) @@ -213,13 +213,13 @@ subroutine materialpoint_stressAndItsTangent(dt) i, & !< integration point number e, & !< element number myNgrains - real(pReal), dimension(discretization_nIP,discretization_nElem) :: & + real(pReal), dimension(discretization_nIPs,discretization_Nelems) :: & subFrac, & subStep - logical, dimension(discretization_nIP,discretization_nElem) :: & + logical, dimension(discretization_nIPs,discretization_Nelems) :: & requested, & converged - logical, dimension(2,discretization_nIP,discretization_nElem) :: & + logical, dimension(2,discretization_nIPs,discretization_Nelems) :: & doneAndHappy @@ -257,7 +257,7 @@ subroutine materialpoint_stressAndItsTangent(dt) !$OMP PARALLEL DO elementLooping1: do e = FEsolving_execElem(1),FEsolving_execElem(2) - myNgrains = homogenization_Ngrains(material_homogenizationAt(e)) + myNgrains = homogenization_Nconstituents(material_homogenizationAt(e)) IpLooping1: do i = FEsolving_execIP(1),FEsolving_execIP(2) if (converged(i,e)) then @@ -327,11 +327,11 @@ subroutine materialpoint_stressAndItsTangent(dt) ! deformation partitioning !$OMP PARALLEL DO PRIVATE(myNgrains) elementLooping2: do e = FEsolving_execElem(1),FEsolving_execElem(2) - myNgrains = homogenization_Ngrains(material_homogenizationAt(e)) + myNgrains = homogenization_Nconstituents(material_homogenizationAt(e)) IpLooping2: do i = FEsolving_execIP(1),FEsolving_execIP(2) if(requested(i,e) .and. .not. doneAndHappy(1,i,e)) then ! requested but not yet done - call partitionDeformation(materialpoint_F0(1:3,1:3,i,e) & - + (materialpoint_F(1:3,1:3,i,e)-materialpoint_F0(1:3,1:3,i,e))& + call partitionDeformation(homogenization_F0(1:3,1:3,i,e) & + + (homogenization_F(1:3,1:3,i,e)-homogenization_F0(1:3,1:3,i,e))& *(subStep(i,e)+subFrac(i,e)), & i,e) crystallite_dt(1:myNgrains,i,e) = dt*subStep(i,e) ! propagate materialpoint dt to grains @@ -357,8 +357,8 @@ subroutine materialpoint_stressAndItsTangent(dt) doneAndHappy(1:2,i,e) = [.true.,.false.] else doneAndHappy(1:2,i,e) = updateState(dt*subStep(i,e), & - materialpoint_F0(1:3,1:3,i,e) & - + (materialpoint_F(1:3,1:3,i,e)-materialpoint_F0(1:3,1:3,i,e)) & + homogenization_F0(1:3,1:3,i,e) & + + (homogenization_F(1:3,1:3,i,e)-homogenization_F0(1:3,1:3,i,e)) & *(subStep(i,e)+subFrac(i,e)), & i,e) converged(i,e) = all(doneAndHappy(1:2,i,e)) ! converged if done and happy @@ -408,12 +408,12 @@ subroutine partitionDeformation(subF,ip,el) case (HOMOGENIZATION_ISOSTRAIN_ID) chosenHomogenization call mech_isostrain_partitionDeformation(& - crystallite_partitionedF(1:3,1:3,1:homogenization_Ngrains(material_homogenizationAt(el)),ip,el), & + crystallite_partitionedF(1:3,1:3,1:homogenization_Nconstituents(material_homogenizationAt(el)),ip,el), & subF) case (HOMOGENIZATION_RGC_ID) chosenHomogenization call mech_RGC_partitionDeformation(& - crystallite_partitionedF(1:3,1:3,1:homogenization_Ngrains(material_homogenizationAt(el)),ip,el), & + crystallite_partitionedF(1:3,1:3,1:homogenization_Nconstituents(material_homogenizationAt(el)),ip,el), & subF,& ip, & el) @@ -437,19 +437,19 @@ function updateState(subdt,subF,ip,el) el !< element number integer :: c logical, dimension(2) :: updateState - real(pReal) :: dPdFs(3,3,3,3,homogenization_Ngrains(material_homogenizationAt(el))) + real(pReal) :: dPdFs(3,3,3,3,homogenization_Nconstituents(material_homogenizationAt(el))) updateState = .true. chosenHomogenization: select case(homogenization_type(material_homogenizationAt(el))) case (HOMOGENIZATION_RGC_ID) chosenHomogenization - do c=1,homogenization_Ngrains(material_homogenizationAt(el)) + do c=1,homogenization_Nconstituents(material_homogenizationAt(el)) dPdFs(:,:,:,:,c) = crystallite_stressTangent(c,ip,el) enddo updateState = & updateState .and. & - mech_RGC_updateState(crystallite_P(1:3,1:3,1:homogenization_Ngrains(material_homogenizationAt(el)),ip,el), & - crystallite_partitionedF(1:3,1:3,1:homogenization_Ngrains(material_homogenizationAt(el)),ip,el), & - crystallite_partitionedF0(1:3,1:3,1:homogenization_Ngrains(material_homogenizationAt(el)),ip,el),& + mech_RGC_updateState(crystallite_P(1:3,1:3,1:homogenization_Nconstituents(material_homogenizationAt(el)),ip,el), & + crystallite_partitionedF(1:3,1:3,1:homogenization_Nconstituents(material_homogenizationAt(el)),ip,el), & + crystallite_partitionedF0(1:3,1:3,1:homogenization_Nconstituents(material_homogenizationAt(el)),ip,el),& subF,& subdt, & dPdFs, & @@ -487,33 +487,33 @@ subroutine averageStressAndItsTangent(ip,el) ip, & !< integration point el !< element number integer :: c - real(pReal) :: dPdFs(3,3,3,3,homogenization_Ngrains(material_homogenizationAt(el))) + real(pReal) :: dPdFs(3,3,3,3,homogenization_Nconstituents(material_homogenizationAt(el))) chosenHomogenization: select case(homogenization_type(material_homogenizationAt(el))) case (HOMOGENIZATION_NONE_ID) chosenHomogenization - materialpoint_P(1:3,1:3,ip,el) = crystallite_P(1:3,1:3,1,ip,el) - materialpoint_dPdF(1:3,1:3,1:3,1:3,ip,el) = crystallite_stressTangent(1,ip,el) + homogenization_P(1:3,1:3,ip,el) = crystallite_P(1:3,1:3,1,ip,el) + homogenization_dPdF(1:3,1:3,1:3,1:3,ip,el) = crystallite_stressTangent(1,ip,el) case (HOMOGENIZATION_ISOSTRAIN_ID) chosenHomogenization - do c = 1, homogenization_Ngrains(material_homogenizationAt(el)) + do c = 1, homogenization_Nconstituents(material_homogenizationAt(el)) dPdFs(:,:,:,:,c) = crystallite_stressTangent(c,ip,el) enddo call mech_isostrain_averageStressAndItsTangent(& - materialpoint_P(1:3,1:3,ip,el), & - materialpoint_dPdF(1:3,1:3,1:3,1:3,ip,el),& - crystallite_P(1:3,1:3,1:homogenization_Ngrains(material_homogenizationAt(el)),ip,el), & + homogenization_P(1:3,1:3,ip,el), & + homogenization_dPdF(1:3,1:3,1:3,1:3,ip,el),& + crystallite_P(1:3,1:3,1:homogenization_Nconstituents(material_homogenizationAt(el)),ip,el), & dPdFs, & homogenization_typeInstance(material_homogenizationAt(el))) case (HOMOGENIZATION_RGC_ID) chosenHomogenization - do c = 1, homogenization_Ngrains(material_homogenizationAt(el)) + do c = 1, homogenization_Nconstituents(material_homogenizationAt(el)) dPdFs(:,:,:,:,c) = crystallite_stressTangent(c,ip,el) enddo call mech_RGC_averageStressAndItsTangent(& - materialpoint_P(1:3,1:3,ip,el), & - materialpoint_dPdF(1:3,1:3,1:3,1:3,ip,el),& - crystallite_P(1:3,1:3,1:homogenization_Ngrains(material_homogenizationAt(el)),ip,el), & + homogenization_P(1:3,1:3,ip,el), & + homogenization_dPdF(1:3,1:3,1:3,1:3,ip,el),& + crystallite_P(1:3,1:3,1:homogenization_Nconstituents(material_homogenizationAt(el)),ip,el), & dPdFs, & homogenization_typeInstance(material_homogenizationAt(el))) end select chosenHomogenization @@ -529,20 +529,20 @@ subroutine homogenization_results material_homogenization_type => homogenization_type integer :: p - character(len=pStringLen) :: group_base,group + character(len=:), allocatable :: group_base,group !real(pReal), dimension(:,:,:), allocatable :: temp do p=1,size(material_name_homogenization) - group_base = 'current/materialpoint/'//trim(material_name_homogenization(p)) + group_base = 'current/homogenization/'//trim(material_name_homogenization(p)) call results_closeGroup(results_addGroup(group_base)) group = trim(group_base)//'/generic' call results_closeGroup(results_addGroup(group)) - !temp = reshape(materialpoint_F,[3,3,discretization_nIP*discretization_nElem]) + !temp = reshape(homogenization_F,[3,3,discretization_nIPs*discretization_Nelems]) !call results_writeDataset(group,temp,'F',& ! 'deformation gradient','1') - !temp = reshape(materialpoint_P,[3,3,discretization_nIP*discretization_nElem]) + !temp = reshape(homogenization_P,[3,3,discretization_nIPs*discretization_Nelems]) !call results_writeDataset(group,temp,'P',& ! '1st Piola-Kirchhoff stress','Pa') diff --git a/src/homogenization_mech_RGC.f90 b/src/homogenization_mech_RGC.f90 index ea5bffd28..585752469 100644 --- a/src/homogenization_mech_RGC.f90 +++ b/src/homogenization_mech_RGC.f90 @@ -81,9 +81,9 @@ module subroutine mech_RGC_init(num_homogMech) num_homogMech !< pointer to mechanical homogenization numerics data integer :: & - Ninstance, & + Ninstances, & h, & - NofMyHomog, & + Nmaterialpoints, & sizeState, nIntFaceTot class (tNode), pointer :: & @@ -94,8 +94,8 @@ module subroutine mech_RGC_init(num_homogMech) print'(/,a)', ' <<<+- homogenization_mech_rgc init -+>>>' - Ninstance = count(homogenization_type == HOMOGENIZATION_RGC_ID) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) + Ninstances = count(homogenization_type == HOMOGENIZATION_RGC_ID) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) print*, 'Tjahjanto et al., International Journal of Material Forming 2(1):939–942, 2009' print*, 'https://doi.org/10.1007/s12289-009-0619-1'//IO_EOL @@ -105,10 +105,10 @@ module subroutine mech_RGC_init(num_homogMech) - allocate(param(Ninstance)) - allocate(state(Ninstance)) - allocate(state0(Ninstance)) - allocate(dependentState(Ninstance)) + allocate(param(Ninstances)) + allocate(state(Ninstances)) + allocate(state0(Ninstances)) + allocate(dependentState(Ninstances)) num_RGC => num_homogMech%get('RGC',defaultVal=emptyDict) @@ -145,7 +145,7 @@ module subroutine mech_RGC_init(num_homogMech) do h = 1, size(homogenization_type) if (homogenization_type(h) /= HOMOGENIZATION_RGC_ID) cycle homog => material_homogenization%get(h) - homogMech => homog%get('mech') + homogMech => homog%get('mechanics') associate(prm => param(homogenization_typeInstance(h)), & stt => state(homogenization_typeInstance(h)), & st0 => state0(homogenization_typeInstance(h)), & @@ -164,7 +164,7 @@ module subroutine mech_RGC_init(num_homogMech) #endif prm%N_constituents = homogMech%get_asInts('cluster_size',requiredSize=3) - if (homogenization_Ngrains(h) /= product(prm%N_constituents)) & + if (homogenization_Nconstituents(h) /= product(prm%N_constituents)) & call IO_error(211,ext_msg='N_constituents (mech_RGC)') prm%xi_alpha = homogMech%get_asFloat('xi_alpha') @@ -173,7 +173,7 @@ module subroutine mech_RGC_init(num_homogMech) prm%D_alpha = homogMech%get_asFloats('D_alpha', requiredSize=3) prm%a_g = homogMech%get_asFloats('a_g', requiredSize=3) - NofMyHomog = count(material_homogenizationAt == h) + Nmaterialpoints = count(material_homogenizationAt == h) nIntFaceTot = 3*( (prm%N_constituents(1)-1)*prm%N_constituents(2)*prm%N_constituents(3) & + prm%N_constituents(1)*(prm%N_constituents(2)-1)*prm%N_constituents(3) & + prm%N_constituents(1)*prm%N_constituents(2)*(prm%N_constituents(3)-1)) @@ -181,24 +181,24 @@ module subroutine mech_RGC_init(num_homogMech) + size(['avg constitutive work ','average penalty energy']) homogState(h)%sizeState = sizeState - allocate(homogState(h)%state0 (sizeState,NofMyHomog), source=0.0_pReal) - allocate(homogState(h)%subState0(sizeState,NofMyHomog), source=0.0_pReal) - allocate(homogState(h)%state (sizeState,NofMyHomog), source=0.0_pReal) + allocate(homogState(h)%state0 (sizeState,Nmaterialpoints), source=0.0_pReal) + allocate(homogState(h)%subState0(sizeState,Nmaterialpoints), source=0.0_pReal) + allocate(homogState(h)%state (sizeState,Nmaterialpoints), source=0.0_pReal) stt%relaxationVector => homogState(h)%state(1:nIntFaceTot,:) st0%relaxationVector => homogState(h)%state0(1:nIntFaceTot,:) stt%work => homogState(h)%state(nIntFaceTot+1,:) stt%penaltyEnergy => homogState(h)%state(nIntFaceTot+2,:) - allocate(dst%volumeDiscrepancy( NofMyHomog), source=0.0_pReal) - allocate(dst%relaxationRate_avg( NofMyHomog), source=0.0_pReal) - allocate(dst%relaxationRate_max( NofMyHomog), source=0.0_pReal) - allocate(dst%mismatch( 3,NofMyHomog), source=0.0_pReal) + allocate(dst%volumeDiscrepancy( Nmaterialpoints), source=0.0_pReal) + allocate(dst%relaxationRate_avg( Nmaterialpoints), source=0.0_pReal) + allocate(dst%relaxationRate_max( Nmaterialpoints), source=0.0_pReal) + allocate(dst%mismatch( 3,Nmaterialpoints), source=0.0_pReal) !-------------------------------------------------------------------------------------------------- ! assigning cluster orientations - dependentState(homogenization_typeInstance(h))%orientation = spread(eu2om(prm%a_g*inRad),3,NofMyHomog) - !dst%orientation = spread(eu2om(prm%a_g*inRad),3,NofMyHomog) ifort version 18.0.1 crashes (for whatever reason) + dependentState(homogenization_typeInstance(h))%orientation = spread(eu2om(prm%a_g*inRad),3,Nmaterialpoints) + !dst%orientation = spread(eu2om(prm%a_g*inRad),3,Nmaterialpoints) ifort version 18.0.1 crashes (for whatever reason) end associate diff --git a/src/homogenization_mech_isostrain.f90 b/src/homogenization_mech_isostrain.f90 index f064578c8..751518e09 100644 --- a/src/homogenization_mech_isostrain.f90 +++ b/src/homogenization_mech_isostrain.f90 @@ -18,7 +18,7 @@ submodule(homogenization) homogenization_mech_isostrain mapping end type - type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstance) + type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstances) contains @@ -29,9 +29,9 @@ contains module subroutine mech_isostrain_init integer :: & - Ninstance, & + Ninstances, & h, & - NofMyHomog + Nmaterialpoints class(tNode), pointer :: & material_homogenization, & homog, & @@ -39,19 +39,19 @@ module subroutine mech_isostrain_init print'(/,a)', ' <<<+- homogenization_mech_isostrain init -+>>>' - Ninstance = count(homogenization_type == HOMOGENIZATION_ISOSTRAIN_ID) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) + Ninstances = count(homogenization_type == HOMOGENIZATION_ISOSTRAIN_ID) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) - allocate(param(Ninstance)) ! one container of parameters per instance + allocate(param(Ninstances)) ! one container of parameters per instance material_homogenization => config_material%get('homogenization') do h = 1, size(homogenization_type) if (homogenization_type(h) /= HOMOGENIZATION_ISOSTRAIN_ID) cycle homog => material_homogenization%get(h) - homogMech => homog%get('mech') + homogMech => homog%get('mechanics') associate(prm => param(homogenization_typeInstance(h))) - prm%N_constituents = homogenization_Ngrains(h) + prm%N_constituents = homogenization_Nconstituents(h) select case(homogMech%get_asString('mapping',defaultVal = 'sum')) case ('sum') prm%mapping = parallel_ID @@ -61,11 +61,11 @@ module subroutine mech_isostrain_init call IO_error(211,ext_msg='sum'//' (mech_isostrain)') end select - NofMyHomog = count(material_homogenizationAt == h) + Nmaterialpoints = count(material_homogenizationAt == h) homogState(h)%sizeState = 0 - allocate(homogState(h)%state0 (0,NofMyHomog)) - allocate(homogState(h)%subState0(0,NofMyHomog)) - allocate(homogState(h)%state (0,NofMyHomog)) + allocate(homogState(h)%state0 (0,Nmaterialpoints)) + allocate(homogState(h)%subState0(0,Nmaterialpoints)) + allocate(homogState(h)%state (0,Nmaterialpoints)) end associate diff --git a/src/homogenization_mech_none.f90 b/src/homogenization_mech_none.f90 index a58147c45..5b12247cd 100644 --- a/src/homogenization_mech_none.f90 +++ b/src/homogenization_mech_none.f90 @@ -14,26 +14,26 @@ contains module subroutine mech_none_init integer :: & - Ninstance, & + Ninstances, & h, & - NofMyHomog + Nmaterialpoints print'(/,a)', ' <<<+- homogenization_mech_none init -+>>>' - Ninstance = count(homogenization_type == HOMOGENIZATION_NONE_ID) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) + Ninstances = count(homogenization_type == HOMOGENIZATION_NONE_ID) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) do h = 1, size(homogenization_type) if(homogenization_type(h) /= HOMOGENIZATION_NONE_ID) cycle - if(homogenization_Ngrains(h) /= 1) & + if(homogenization_Nconstituents(h) /= 1) & call IO_error(211,ext_msg='N_constituents (mech_none)') - NofMyHomog = count(material_homogenizationAt == h) + Nmaterialpoints = count(material_homogenizationAt == h) homogState(h)%sizeState = 0 - allocate(homogState(h)%state0 (0,NofMyHomog)) - allocate(homogState(h)%subState0(0,NofMyHomog)) - allocate(homogState(h)%state (0,NofMyHomog)) + allocate(homogState(h)%state0 (0,Nmaterialpoints)) + allocate(homogState(h)%subState0(0,Nmaterialpoints)) + allocate(homogState(h)%state (0,Nmaterialpoints)) enddo diff --git a/src/kinematics_cleavage_opening.f90 b/src/kinematics_cleavage_opening.f90 index d52fdbc1c..66bce6a92 100644 --- a/src/kinematics_cleavage_opening.f90 +++ b/src/kinematics_cleavage_opening.f90 @@ -20,7 +20,7 @@ submodule(constitutive:constitutive_damage) kinematics_cleavage_opening cleavage_systems end type tParameters - type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstance) + type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstances) contains @@ -35,31 +35,29 @@ module function kinematics_cleavage_opening_init(kinematics_length) result(myKin integer, intent(in) :: kinematics_length logical, dimension(:,:), allocatable :: myKinematics - integer :: Ninstance,p,k + integer :: Ninstances,p,k integer, dimension(:), allocatable :: N_cl !< active number of cleavage systems per family character(len=pStringLen) :: extmsg = '' class(tNode), pointer :: & phases, & phase, & - pl, & kinematics, & kinematic_type print'(/,a)', ' <<<+- kinematics_cleavage_opening init -+>>>' myKinematics = kinematics_active('cleavage_opening',kinematics_length) - Ninstance = count(myKinematics) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(myKinematics) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return phases => config_material%get('phase') - allocate(param(Ninstance)) + allocate(param(Ninstances)) allocate(kinematics_cleavage_opening_instance(phases%length), source=0) do p = 1, phases%length if(any(myKinematics(:,p))) kinematics_cleavage_opening_instance(p) = count(myKinematics(:,1:p)) - phase => phases%get(p) - pl => phase%get('plasticity') + phase => phases%get(p) if(count(myKinematics(:,p)) == 0) cycle kinematics => phase%get('kinematics') do k = 1, kinematics%length diff --git a/src/kinematics_slipplane_opening.f90 b/src/kinematics_slipplane_opening.f90 index e0de5e181..aa0bdfbde 100644 --- a/src/kinematics_slipplane_opening.f90 +++ b/src/kinematics_slipplane_opening.f90 @@ -22,7 +22,7 @@ submodule(constitutive:constitutive_damage) kinematics_slipplane_opening P_n end type tParameters - type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstance) + type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstances) contains @@ -37,13 +37,14 @@ module function kinematics_slipplane_opening_init(kinematics_length) result(myKi integer, intent(in) :: kinematics_length logical, dimension(:,:), allocatable :: myKinematics - integer :: Ninstance,p,i,k + integer :: Ninstances,p,i,k character(len=pStringLen) :: extmsg = '' integer, dimension(:), allocatable :: N_sl real(pReal), dimension(:,:), allocatable :: d,n,t class(tNode), pointer :: & phases, & phase, & + mech, & pl, & kinematics, & kinematic_type @@ -51,18 +52,19 @@ module function kinematics_slipplane_opening_init(kinematics_length) result(myKi print'(/,a)', ' <<<+- kinematics_slipplane init -+>>>' myKinematics = kinematics_active('slipplane_opening',kinematics_length) - Ninstance = count(myKinematics) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(myKinematics) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return phases => config_material%get('phase') allocate(kinematics_slipplane_opening_instance(phases%length), source=0) - allocate(param(Ninstance)) + allocate(param(Ninstances)) do p = 1, phases%length if(any(myKinematics(:,p))) kinematics_slipplane_opening_instance(p) = count(myKinematics(:,1:p)) - phase => phases%get(p) - pl => phase%get('plasticity') + phase => phases%get(p) + mech => phase%get('mechanics') + pl => mech%get('plasticity') if(count(myKinematics(:,p)) == 0) cycle kinematics => phase%get('kinematics') do k = 1, kinematics%length diff --git a/src/kinematics_thermal_expansion.f90 b/src/kinematics_thermal_expansion.f90 index 772f5abbf..2b8b04d85 100644 --- a/src/kinematics_thermal_expansion.f90 +++ b/src/kinematics_thermal_expansion.f90 @@ -29,30 +29,28 @@ module function kinematics_thermal_expansion_init(kinematics_length) result(myKi integer, intent(in) :: kinematics_length logical, dimension(:,:), allocatable :: myKinematics - integer :: Ninstance,p,i,k + integer :: Ninstances,p,i,k real(pReal), dimension(:), allocatable :: temp class(tNode), pointer :: & phases, & phase, & - pl, & kinematics, & kinematic_type print'(/,a)', ' <<<+- kinematics_thermal_expansion init -+>>>' myKinematics = kinematics_active('thermal_expansion',kinematics_length) - Ninstance = count(myKinematics) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(myKinematics) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return phases => config_material%get('phase') - allocate(param(Ninstance)) + allocate(param(Ninstances)) allocate(kinematics_thermal_expansion_instance(phases%length), source=0) do p = 1, phases%length if(any(myKinematics(:,p))) kinematics_thermal_expansion_instance(p) = count(myKinematics(:,1:p)) phase => phases%get(p) - pl => phase%get('plasticity') if(count(myKinematics(:,p)) == 0) cycle kinematics => phase%get('kinematics') do k = 1, kinematics%length diff --git a/src/lattice.f90 b/src/lattice.f90 index 78b3894c0..676232efe 100644 --- a/src/lattice.f90 +++ b/src/lattice.f90 @@ -17,7 +17,7 @@ module lattice private !-------------------------------------------------------------------------------------------------- -! face centered cubic +! face centered cubic (cF) integer, dimension(*), parameter :: & FCC_NSLIPSYSTEM = [12, 6] !< # of slip systems per family for fcc @@ -108,7 +108,7 @@ module lattice ],pReal),shape(FCC_SYSTEMCLEAVAGE)) !-------------------------------------------------------------------------------------------------- -! body centered cubic +! body centered cubic (cI) integer, dimension(*), parameter :: & BCC_NSLIPSYSTEM = [12, 12] !< # of slip systems per family for bcc @@ -186,7 +186,7 @@ module lattice ],pReal),shape(BCC_SYSTEMCLEAVAGE)) !-------------------------------------------------------------------------------------------------- -! hexagonal +! hexagonal (hP) integer, dimension(*), parameter :: & HEX_NSLIPSYSTEM = [3, 3, 3, 6, 12, 6] !< # of slip systems per family for hex @@ -279,7 +279,7 @@ module lattice ],pReal),shape(HEX_SYSTEMTWIN)) !< twin systems for hex, sorted by P. Eisenlohr CCW around starting next to a_1 axis !-------------------------------------------------------------------------------------------------- -! body centered tetragonal +! body centered tetragonal (tI) integer, dimension(*), parameter :: & BCT_NSLIPSYSTEM = [2, 2, 2, 4, 2, 4, 2, 2, 4, 8, 4, 8, 8 ] !< # of slip systems per family for bct (Sn) Bieler J. Electr Mater 2009 @@ -361,7 +361,7 @@ module lattice ],pReal),shape(BCT_SYSTEMSLIP)) !< slip systems for bct sorted by Bieler !-------------------------------------------------------------------------------------------------- -! orthorhombic +! orthorhombic primitive (oP) integer, dimension(*), parameter :: & ORT_NCLEAVAGESYSTEM = [1, 1, 1] !< # of cleavage systems per family for ortho @@ -455,8 +455,9 @@ subroutine lattice_init class(tNode), pointer :: & phases, & phase, & + mech, & elasticity - + print'(/,a)', ' <<<+- lattice init -+>>>'; flush(IO_STDOUT) phases => config_material%get('phase') @@ -475,7 +476,8 @@ subroutine lattice_init do p = 1, phases%length phase => phases%get(p) - elasticity => phase%get('elasticity') + mech => phase%get('mechanics') + elasticity => mech%get('elasticity') lattice_C66(1,1,p) = elasticity%get_asFloat('C_11') lattice_C66(1,2,p) = elasticity%get_asFloat('C_12') @@ -488,18 +490,18 @@ subroutine lattice_init lattice_C66(6,6,p) = elasticity%get_asFloat('C_66',defaultVal=0.0_pReal) select case(phase%get_asString('lattice')) - case('iso') - lattice_structure(p) = lattice_ISO_ID - case('fcc') + case('cF') lattice_structure(p) = lattice_FCC_ID - case('bcc') + case('cI') lattice_structure(p) = lattice_BCC_ID - case('hex') + case('hP') lattice_structure(p) = lattice_HEX_ID - case('bct') + case('tI') lattice_structure(p) = lattice_BCT_ID - case('ort') + case('oP') lattice_structure(p) = lattice_ORT_ID + case('aP') + lattice_structure(p) = lattice_ISO_ID case default call IO_error(130,ext_msg='lattice_init: '//phase%get_asString('lattice')) end select @@ -586,17 +588,14 @@ function lattice_characteristicShear_Twin(Ntwin,structure,CoverA) result(charact 4 & ],[HEX_NTWIN]) ! indicator to formulas below - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_characteristicShear_Twin: '//trim(structure)) - a = 0 myFamilies: do f = 1,size(Ntwin,1) mySystems: do s = 1,Ntwin(f) a = a + 1 select case(structure) - case('fcc','bcc') + case('cF','cI') characteristicShear(a) = 0.5_pReal*sqrt(2.0_pReal) - case('hex') + case('hP') if (cOverA < 1.0_pReal .or. cOverA > 2.0_pReal) & call IO_error(131,ext_msg='lattice_characteristicShear_Twin') p = sum(HEX_NTWINSYSTEM(1:f-1))+s @@ -634,19 +633,16 @@ function lattice_C66_twin(Ntwin,C66,structure,CoverA) type(rotation) :: R integer :: i - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_C66_twin: '//trim(structure)) - select case(structure) - case('fcc') + case('cF') coordinateSystem = buildCoordinateSystem(Ntwin,FCC_NSLIPSYSTEM,FCC_SYSTEMTWIN,& - trim(structure),0.0_pReal) - case('bcc') + structure,0.0_pReal) + case('cI') coordinateSystem = buildCoordinateSystem(Ntwin,BCC_NSLIPSYSTEM,BCC_SYSTEMTWIN,& - trim(structure),0.0_pReal) - case('hex') + structure,0.0_pReal) + case('hP') coordinateSystem = buildCoordinateSystem(Ntwin,HEX_NSLIPSYSTEM,HEX_SYSTEMTWIN,& - 'hex',cOverA) + structure,cOverA) case default call IO_error(137,ext_msg='lattice_C66_twin: '//trim(structure)) end select @@ -676,12 +672,9 @@ function lattice_C66_trans(Ntrans,C_parent66,structure_target, & real(pReal) :: a_bcc, a_fcc, cOverA_trans integer :: i - if (len_trim(structure_target) /= 3) & - call IO_error(137,ext_msg='lattice_C66_trans (target): '//trim(structure_target)) - !-------------------------------------------------------------------------------------------------- ! elasticity matrix of the target phase in cube orientation - if (structure_target(1:3) == 'hex') then + if (structure_target == 'hP') then if (cOverA_trans < 1.0_pReal .or. cOverA_trans > 2.0_pReal) & call IO_error(131,ext_msg='lattice_C66_trans: '//trim(structure_target)) C_bar66(1,1) = (C_parent66(1,1) + C_parent66(1,2) + 2.0_pReal*C_parent66(4,4))/2.0_pReal @@ -697,8 +690,8 @@ function lattice_C66_trans(Ntrans,C_parent66,structure_target, & C_target_unrotated66(1,3) = C_bar66(1,3) C_target_unrotated66(3,3) = C_bar66(3,3) C_target_unrotated66(4,4) = C_bar66(4,4) - C_bar66(1,4)**2.0_pReal/(0.5_pReal*(C_bar66(1,1) - C_bar66(1,2))) - C_target_unrotated66 = applyLatticeSymmetryC66(C_target_unrotated66,'hex') - elseif (structure_target(1:3) == 'bcc') then + C_target_unrotated66 = applyLatticeSymmetryC66(C_target_unrotated66,'hP') + elseif (structure_target == 'cI') then if (a_bcc <= 0.0_pReal .or. a_fcc <= 0.0_pReal) & call IO_error(134,ext_msg='lattice_C66_trans: '//trim(structure_target)) C_target_unrotated66 = C_parent66 @@ -741,9 +734,9 @@ function lattice_nonSchmidMatrix(Nslip,nonSchmidCoefficients,sense) result(nonSc if (abs(sense) /= 1) error stop 'Sense in lattice_nonSchmidMatrix' coordinateSystem = buildCoordinateSystem(Nslip,BCC_NSLIPSYSTEM,BCC_SYSTEMSLIP,& - 'bcc',0.0_pReal) + 'cI',0.0_pReal) coordinateSystem(1:3,1,1:sum(Nslip)) = coordinateSystem(1:3,1,1:sum(Nslip))*real(sense,pReal) ! convert unidirectional coordinate system - nonSchmidMatrix = lattice_SchmidMatrix_slip(Nslip,'bcc',0.0_pReal) ! Schmid contribution + nonSchmidMatrix = lattice_SchmidMatrix_slip(Nslip,'cI',0.0_pReal) ! Schmid contribution do i = 1,sum(Nslip) direction = coordinateSystem(1:3,1,i) @@ -964,20 +957,17 @@ function lattice_interaction_SlipBySlip(Nslip,interactionValues,structure) resul ],shape(BCT_INTERACTIONSLIPSLIP)) - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_interaction_SlipBySlip: '//trim(structure)) - select case(structure) - case('fcc') + case('cF') interactionTypes = FCC_INTERACTIONSLIPSLIP NslipMax = FCC_NSLIPSYSTEM - case('bcc') + case('cI') interactionTypes = BCC_INTERACTIONSLIPSLIP NslipMax = BCC_NSLIPSYSTEM - case('hex') + case('hP') interactionTypes = HEX_INTERACTIONSLIPSLIP NslipMax = HEX_NSLIPSYSTEM - case('bct') + case('tI') interactionTypes = BCT_INTERACTIONSLIPSLIP NslipMax = BCT_NSLIPSYSTEM case default @@ -1068,17 +1058,14 @@ function lattice_interaction_TwinByTwin(Ntwin,interactionValues,structure) resul 20,20,20,20,20,20, 19,19,19,19,19,19, 18,18,18,18,18,18, 17,17,17,17,17,16 & ],shape(HEX_INTERACTIONTWINTWIN)) !< Twin-twin interaction types for hex - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_interaction_TwinByTwin: '//trim(structure)) - select case(structure) - case('fcc') + case('cF') interactionTypes = FCC_INTERACTIONTWINTWIN NtwinMax = FCC_NTWINSYSTEM - case('bcc') + case('cI') interactionTypes = BCC_INTERACTIONTWINTWIN NtwinMax = BCC_NTWINSYSTEM - case('hex') + case('hP') interactionTypes = HEX_INTERACTIONTWINTWIN NtwinMax = HEX_NTWINSYSTEM case default @@ -1120,10 +1107,7 @@ function lattice_interaction_TransByTrans(Ntrans,interactionValues,structure) re 2,2,2,2,2,2,2,2,2,1,1,1 & ],shape(FCC_INTERACTIONTRANSTRANS)) !< Trans-trans interaction types for fcc - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_interaction_TransByTrans: '//trim(structure)) - - if(structure == 'fcc') then + if(structure == 'cF') then interactionTypes = FCC_INTERACTIONTRANSTRANS NtransMax = FCC_NTRANSSYSTEM else @@ -1250,19 +1234,16 @@ function lattice_interaction_SlipByTwin(Nslip,Ntwin,interactionValues,structure) ! ],shape(HEX_INTERACTIONSLIPTWIN)) !< Slip-twin interaction types for hex - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_interaction_SlipByTwin: '//trim(structure)) - select case(structure) - case('fcc') + case('cF') interactionTypes = FCC_INTERACTIONSLIPTWIN NslipMax = FCC_NSLIPSYSTEM NtwinMax = FCC_NTWINSYSTEM - case('bcc') + case('cI') interactionTypes = BCC_INTERACTIONSLIPTWIN NslipMax = BCC_NSLIPSYSTEM NtwinMax = BCC_NTWINSYSTEM - case('hex') + case('hP') interactionTypes = HEX_INTERACTIONSLIPTWIN NslipMax = HEX_NSLIPSYSTEM NtwinMax = HEX_NTWINSYSTEM @@ -1314,11 +1295,8 @@ function lattice_interaction_SlipByTrans(Nslip,Ntrans,interactionValues,structur 4,4,4,4,4,4,4,4,4,4,4,4 & ],shape(FCC_INTERACTIONSLIPTRANS)) !< Slip-trans interaction types for fcc - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_interaction_SlipByTrans: '//trim(structure)) - select case(structure) - case('fcc') + case('cF') interactionTypes = FCC_INTERACTIONSLIPTRANS NslipMax = FCC_NSLIPSYSTEM NtransMax = FCC_NTRANSSYSTEM @@ -1384,19 +1362,16 @@ function lattice_interaction_TwinBySlip(Ntwin,Nslip,interactionValues,structure) 4, 4, 4, 8, 8, 8, 12,12,12, 16,16,16,16,16,16, 20,20,20,20,20,20,20,20,20,20,20,20, 24,24,24,24,24,24 & ],shape(HEX_INTERACTIONTWINSLIP)) !< Twin-slip interaction types for hex - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_interaction_TwinBySlip: '//trim(structure)) - select case(structure) - case('fcc') + case('cF') interactionTypes = FCC_INTERACTIONTWINSLIP NtwinMax = FCC_NTWINSYSTEM NslipMax = FCC_NSLIPSYSTEM - case('bcc') + case('cI') interactionTypes = BCC_INTERACTIONTWINSLIP NtwinMax = BCC_NTWINSYSTEM NslipMax = BCC_NSLIPSYSTEM - case('hex') + case('hP') interactionTypes = HEX_INTERACTIONTWINSLIP NtwinMax = HEX_NTWINSYSTEM NslipMax = HEX_NSLIPSYSTEM @@ -1425,20 +1400,17 @@ function lattice_SchmidMatrix_slip(Nslip,structure,cOverA) result(SchmidMatrix) integer, dimension(:), allocatable :: NslipMax integer :: i - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_SchmidMatrix_slip: '//trim(structure)) - select case(structure) - case('fcc') + case('cF') NslipMax = FCC_NSLIPSYSTEM slipSystems = FCC_SYSTEMSLIP - case('bcc') + case('cI') NslipMax = BCC_NSLIPSYSTEM slipSystems = BCC_SYSTEMSLIP - case('hex') + case('hP') NslipMax = HEX_NSLIPSYSTEM slipSystems = HEX_SYSTEMSLIP - case('bct') + case('tI') NslipMax = BCT_NSLIPSYSTEM slipSystems = BCT_SYSTEMSLIP case default @@ -1478,17 +1450,14 @@ function lattice_SchmidMatrix_twin(Ntwin,structure,cOverA) result(SchmidMatrix) integer, dimension(:), allocatable :: NtwinMax integer :: i - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_SchmidMatrix_twin: '//trim(structure)) - select case(structure) - case('fcc') + case('cF') NtwinMax = FCC_NTWINSYSTEM twinSystems = FCC_SYSTEMTWIN - case('bcc') + case('cI') NtwinMax = BCC_NTWINSYSTEM twinSystems = BCC_SYSTEMTWIN - case('hex') + case('hP') NtwinMax = HEX_NTWINSYSTEM twinSystems = HEX_SYSTEMTWIN case default @@ -1526,15 +1495,13 @@ function lattice_SchmidMatrix_trans(Ntrans,structure_target,cOverA,a_bcc,a_fcc) real(pReal), dimension(3,3,sum(Ntrans)) :: devNull real(pReal) :: a_bcc, a_fcc - if (len_trim(structure_target) /= 3) & - call IO_error(137,ext_msg='lattice_SchmidMatrix_trans: '//trim(structure_target)) - if (structure_target(1:3) /= 'bcc' .and. structure_target(1:3) /= 'hex') & + if (structure_target /= 'cI' .and. structure_target /= 'hP') & call IO_error(137,ext_msg='lattice_SchmidMatrix_trans: '//trim(structure_target)) - if (structure_target(1:3) == 'hex' .and. (cOverA < 1.0_pReal .or. cOverA > 2.0_pReal)) & + if (structure_target == 'hP' .and. (cOverA < 1.0_pReal .or. cOverA > 2.0_pReal)) & call IO_error(131,ext_msg='lattice_SchmidMatrix_trans: '//trim(structure_target)) - if (structure_target(1:3) == 'bcc' .and. (a_bcc <= 0.0_pReal .or. a_fcc <= 0.0_pReal)) & + if (structure_target == 'cI' .and. (a_bcc <= 0.0_pReal .or. a_fcc <= 0.0_pReal)) & call IO_error(134,ext_msg='lattice_SchmidMatrix_trans: '//trim(structure_target)) call buildTransformationSystem(devNull,SchmidMatrix,Ntrans,cOverA,a_fcc,a_bcc) @@ -1558,17 +1525,14 @@ function lattice_SchmidMatrix_cleavage(Ncleavage,structure,cOverA) result(Schmid integer, dimension(:), allocatable :: NcleavageMax integer :: i - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_SchmidMatrix_cleavage: '//trim(structure)) - select case(structure) - case('ort') + case('oP') NcleavageMax = ORT_NCLEAVAGESYSTEM cleavageSystems = ORT_SYSTEMCLEAVAGE - case('fcc') + case('cF') NcleavageMax = FCC_NCLEAVAGESYSTEM cleavageSystems = FCC_SYSTEMCLEAVAGE - case('bcc') + case('cI') NcleavageMax = BCC_NCLEAVAGESYSTEM cleavageSystems = BCC_SYSTEMCLEAVAGE case default @@ -1660,20 +1624,17 @@ function lattice_labels_slip(Nslip,structure) result(labels) real(pReal), dimension(:,:), allocatable :: slipSystems integer, dimension(:), allocatable :: NslipMax - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_labels_slip: '//trim(structure)) - select case(structure) - case('fcc') + case('cF') NslipMax = FCC_NSLIPSYSTEM slipSystems = FCC_SYSTEMSLIP - case('bcc') + case('cI') NslipMax = BCC_NSLIPSYSTEM slipSystems = BCC_SYSTEMSLIP - case('hex') + case('hP') NslipMax = HEX_NSLIPSYSTEM slipSystems = HEX_SYSTEMSLIP - case('bct') + case('tI') NslipMax = BCT_NSLIPSYSTEM slipSystems = BCT_SYSTEMSLIP case default @@ -1704,19 +1665,16 @@ function lattice_applyLatticeSymmetry33(T,structure) result(T_sym) T_sym = 0.0_pReal - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_applyLatticeSymmetry33: '//trim(structure)) - select case(structure) - case('iso','fcc','bcc') + case('aP','cF','cI') do k=1,3 T_sym(k,k) = T(1,1) enddo - case('hex') + case('hP') T_sym(1,1) = T(1,1) T_sym(2,2) = T(1,1) T_sym(3,3) = T(3,3) - case('ort','bct') + case('oP','tI') T_sym(1,1) = T(1,1) T_sym(2,2) = T(2,2) T_sym(3,3) = T(3,3) @@ -1742,11 +1700,8 @@ function applyLatticeSymmetryC66(C66,structure) result(C66_sym) C66_sym = 0.0_pReal - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='applyLatticeSymmetryC66: '//trim(structure)) - select case(structure) - case ('iso') + case ('aP') do k=1,3 do j=1,3 C66_sym(k,j) = C66(1,2) @@ -1754,7 +1709,7 @@ function applyLatticeSymmetryC66(C66,structure) result(C66_sym) C66_sym(k,k) = C66(1,1) C66_sym(k+3,k+3) = 0.5_pReal*(C66(1,1)-C66(1,2)) enddo - case ('fcc','bcc') + case ('cF','cI') do k=1,3 do j=1,3 C66_sym(k,j) = C66(1,2) @@ -1762,7 +1717,7 @@ function applyLatticeSymmetryC66(C66,structure) result(C66_sym) C66_sym(k,k) = C66(1,1) C66_sym(k+3,k+3) = C66(4,4) enddo - case ('hex') + case ('hP') C66_sym(1,1) = C66(1,1) C66_sym(2,2) = C66(1,1) C66_sym(3,3) = C66(3,3) @@ -1775,7 +1730,7 @@ function applyLatticeSymmetryC66(C66,structure) result(C66_sym) C66_sym(4,4) = C66(4,4) C66_sym(5,5) = C66(4,4) C66_sym(6,6) = 0.5_pReal*(C66(1,1)-C66(1,2)) - case ('ort') + case ('oP') C66_sym(1,1) = C66(1,1) C66_sym(2,2) = C66(2,2) C66_sym(3,3) = C66(3,3) @@ -1788,7 +1743,7 @@ function applyLatticeSymmetryC66(C66,structure) result(C66_sym) C66_sym(4,4) = C66(4,4) C66_sym(5,5) = C66(5,5) C66_sym(6,6) = C66(6,6) - case ('bct') + case ('tI') C66_sym(1,1) = C66(1,1) C66_sym(2,2) = C66(1,1) C66_sym(3,3) = C66(3,3) @@ -1822,17 +1777,14 @@ function lattice_labels_twin(Ntwin,structure) result(labels) real(pReal), dimension(:,:), allocatable :: twinSystems integer, dimension(:), allocatable :: NtwinMax - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='lattice_labels_twin: '//trim(structure)) - select case(structure) - case('fcc') + case('cF') NtwinMax = FCC_NTWINSYSTEM twinSystems = FCC_SYSTEMTWIN - case('bcc') + case('cI') NtwinMax = BCC_NTWINSYSTEM twinSystems = BCC_SYSTEMTWIN - case('hex') + case('hP') NtwinMax = HEX_NTWINSYSTEM twinSystems = HEX_SYSTEMTWIN case default @@ -1911,20 +1863,17 @@ function coordinateSystem_slip(Nslip,structure,cOverA) result(coordinateSystem) real(pReal), dimension(:,:), allocatable :: slipSystems integer, dimension(:), allocatable :: NslipMax - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='coordinateSystem_slip: '//trim(structure)) - select case(structure) - case('fcc') + case('cF') NslipMax = FCC_NSLIPSYSTEM slipSystems = FCC_SYSTEMSLIP - case('bcc') + case('cI') NslipMax = BCC_NSLIPSYSTEM slipSystems = BCC_SYSTEMSLIP - case('hex') + case('hP') NslipMax = HEX_NSLIPSYSTEM slipSystems = HEX_SYSTEMSLIP - case('bct') + case('tI') NslipMax = BCT_NSLIPSYSTEM slipSystems = BCT_SYSTEMSLIP case default @@ -2011,11 +1960,9 @@ function buildCoordinateSystem(active,potential,system,structure,cOverA) f, & !< index of my family s !< index of my system in current family - if (len_trim(structure) /= 3) & - call IO_error(137,ext_msg='buildCoordinateSystem: '//trim(structure)) - if (trim(structure) == 'bct' .and. cOverA > 2.0_pReal) & + if (structure == 'tI' .and. cOverA > 2.0_pReal) & call IO_error(131,ext_msg='buildCoordinateSystem:'//trim(structure)) - if (trim(structure) == 'hex' .and. (cOverA < 1.0_pReal .or. cOverA > 2.0_pReal)) & + if (structure == 'hP' .and. (cOverA < 1.0_pReal .or. cOverA > 2.0_pReal)) & call IO_error(131,ext_msg='buildCoordinateSystem:'//trim(structure)) a = 0 @@ -2024,13 +1971,13 @@ function buildCoordinateSystem(active,potential,system,structure,cOverA) a = a + 1 p = sum(potential(1:f-1))+s - select case(trim(structure)) + select case(structure) - case ('fcc','bcc','iso','ort','bct') + case ('cF','cI','aP','oP','tI') direction = system(1:3,p) normal = system(4:6,p) - case ('hex') + case ('hP') direction = [ system(1,p)*1.5_pReal, & (system(1,p)+2.0_pReal*system(2,p))*sqrt(0.75_pReal), & system(4,p)*cOverA ] ! direction [uvtw]->[3u/2 (u+2v)*sqrt(3)/2 w*(p/a)]) @@ -2255,11 +2202,11 @@ function equivalent_nu(C,assumption) result(nu) / 9.0_pReal elseif(IO_lc(assumption) == 'reuss') then call math_invert(S,error,C) - if(error) call IO_error(0) + if(error) error stop 'matrix inversion failed' K = 1.0_pReal & / (S(1,1)+S(2,2)+S(3,3) +2.0_pReal*(S(1,2)+S(2,3)+S(1,3))) else - call IO_error(0) + error stop 'invalid assumption' K = 0.0_pReal endif @@ -2287,11 +2234,11 @@ function equivalent_mu(C,assumption) result(mu) / 15.0_pReal elseif(IO_lc(assumption) == 'reuss') then call math_invert(S,error,C) - if(error) call IO_error(0) + if(error) error stop 'matrix inversion failed' mu = 15.0_pReal & / (4.0_pReal*(S(1,1)+S(2,2)+S(3,3)) -4.0_pReal*(S(1,2)+S(2,3)+S(1,3)) +3.0_pReal*(S(4,4)+S(5,5)+S(6,6))) else - call IO_error(0) + error stop 'invalid assumption' mu = 0.0_pReal endif @@ -2313,15 +2260,15 @@ subroutine selfTest call random_number(r) system = reshape([1.0_pReal+r(1),0.0_pReal,0.0_pReal, 0.0_pReal,1.0_pReal+r(2),0.0_pReal],[6,1]) - CoSy = buildCoordinateSystem([1],[1],system,'fcc',0.0_pReal) + CoSy = buildCoordinateSystem([1],[1],system,'cF',0.0_pReal) if(any(dNeq(CoSy(1:3,1:3,1),math_I3))) error stop 'buildCoordinateSystem' call random_number(C) C(1,1) = C(1,1) + 1.0_pReal - C = applyLatticeSymmetryC66(C,'iso') + C = applyLatticeSymmetryC66(C,'aP') if(dNeq(C(6,6),equivalent_mu(C,'voigt'),1.0e-12_pReal)) error stop 'equivalent_mu/voigt' if(dNeq(C(6,6),equivalent_mu(C,'voigt'),1.0e-12_pReal)) error stop 'equivalent_mu/reuss' - + lambda = C(1,2) if(dNeq(lambda*0.5_pReal/(lambda+equivalent_mu(C,'voigt')),equivalent_nu(C,'voigt'),1.0e-12_pReal)) & error stop 'equivalent_nu/voigt' diff --git a/src/material.f90 b/src/material.f90 index 11dfeb42e..8679afdc4 100644 --- a/src/material.f90 +++ b/src/material.f90 @@ -52,7 +52,7 @@ module material HOMOGENIZATION_RGC_ID end enum - character(len=pStringLen), public, protected, allocatable, dimension(:) :: & + character(len=pStringLen), public, protected, allocatable, dimension(:) :: & material_name_phase, & !< name of each phase material_name_homogenization !< name of each homogenization @@ -64,13 +64,10 @@ module material homogenization_type !< type of each homogenization integer, public, protected :: & - material_Nhomogenization !< number of homogenizations - - integer, public, protected :: & - homogenization_maxNgrains !< max number of grains in any USED homogenization + homogenization_maxNconstituents !< max number of grains in any USED homogenization integer, dimension(:), allocatable, public, protected :: & - homogenization_Ngrains, & !< number of grains in each homogenization + homogenization_Nconstituents, & !< number of grains in each homogenization homogenization_typeInstance, & !< instance of particular type of each homogenization thermal_typeInstance, & !< instance of particular type of each thermal transport damage_typeInstance !< instance of particular type of each nonlocal damage @@ -83,9 +80,9 @@ module material material_homogenizationAt !< homogenization ID of each element integer, dimension(:,:), allocatable, public, target :: & ! (ip,elem) ToDo: ugly target for mapping hack material_homogenizationMemberAt !< position of the element within its homogenization instance - integer, dimension(:,:), allocatable, public, protected :: & ! (constituent,elem) + integer, dimension(:,:), allocatable, public, protected :: & ! (constituent,elem) material_phaseAt !< phase ID of each element - integer, dimension(:,:,:), allocatable, public, protected :: & ! (constituent,elem) + integer, dimension(:,:,:), allocatable, public, protected :: & ! (constituent,IP,elem) material_phaseMemberAt !< position of the element within its phase instance type(tState), allocatable, dimension(:), public :: & @@ -96,11 +93,6 @@ module material type(Rotation), dimension(:,:,:), allocatable, public, protected :: & material_orientation0 !< initial orientation of each grain,IP,element - integer, dimension(:), allocatable, private :: & - material_Nconstituents !< number of constituents in each material - - - ! BEGIN DEPRECATED integer, dimension(:,:), allocatable, private, target :: mappingHomogenizationConst !< mapping from material points to offset in constant state/field ! END DEPRECATED @@ -157,28 +149,10 @@ contains subroutine material_init(restart) logical, intent(in) :: restart - - integer :: ph, myHomog - class(tNode), pointer :: & - phases, & - material_homogenization - character(len=pStringLen) :: sectionName + integer :: myHomog print'(/,a)', ' <<<+- material init -+>>>'; flush(IO_STDOUT) - phases => config_material%get('phase') - allocate(material_name_phase(phases%length)) - do ph = 1, phases%length - write(sectionName,'(i0,a)') ph,'_' - material_name_phase(ph) = trim(adjustl(sectionName))//phases%getKey(ph) !ToDO: No reason to do. Update damage tests - enddo - - material_homogenization => config_material%get('homogenization') - allocate(material_name_homogenization(material_homogenization%length)) - do myHomog = 1, material_homogenization%length - write(sectionName,'(i0,a)') myHomog,'_' - material_name_homogenization(myHomog) = trim(adjustl(sectionName))//material_homogenization%getKey(myHomog) - enddo call material_parseMaterial print*, 'Material parsed' @@ -187,34 +161,32 @@ subroutine material_init(restart) print*, 'Homogenization parsed' - if(homogenization_maxNgrains > size(material_phaseAt,1)) call IO_error(148) + allocate(homogState (size(material_name_homogenization))) + allocate(thermalState (size(material_name_homogenization))) + allocate(damageState (size(material_name_homogenization))) - allocate(homogState (material_Nhomogenization)) - allocate(thermalState (material_Nhomogenization)) - allocate(damageState (material_Nhomogenization)) + allocate(thermalMapping (size(material_name_homogenization))) + allocate(damageMapping (size(material_name_homogenization))) - allocate(thermalMapping (material_Nhomogenization)) - allocate(damageMapping (material_Nhomogenization)) + allocate(temperature (size(material_name_homogenization))) + allocate(damage (size(material_name_homogenization))) - allocate(temperature (material_Nhomogenization)) - allocate(damage (material_Nhomogenization)) - - allocate(temperatureRate (material_Nhomogenization)) + allocate(temperatureRate (size(material_name_homogenization))) if (.not. restart) then call results_openJobFile call results_mapping_constituent(material_phaseAt,material_phaseMemberAt,material_name_phase) - call results_mapping_materialpoint(material_homogenizationAt,material_homogenizationMemberAt,material_name_homogenization) + call results_mapping_homogenization(material_homogenizationAt,material_homogenizationMemberAt,material_name_homogenization) call results_closeJobFile endif !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! BEGIN DEPRECATED - allocate(mappingHomogenizationConst( discretization_nIP,discretization_nElem),source=1) + allocate(mappingHomogenizationConst( discretization_nIPs,discretization_Nelems),source=1) ! hack needed to initialize field values used during constitutive initialization - do myHomog = 1,material_Nhomogenization + do myHomog = 1, size(material_name_homogenization) thermalMapping (myHomog)%p => mappingHomogenizationConst damageMapping (myHomog)%p => mappingHomogenizationConst allocate(temperature (myHomog)%p(1), source=thermal_initialT(myHomog)) @@ -225,6 +197,7 @@ subroutine material_init(restart) end subroutine material_init + !-------------------------------------------------------------------------------------------------- !> @brief parses the homogenization part from the material configuration ! ToDo: This should be done in homogenization @@ -241,22 +214,19 @@ subroutine material_parseHomogenization integer :: h material_homogenization => config_material%get('homogenization') - material_Nhomogenization = material_homogenization%length - allocate(homogenization_type(material_Nhomogenization), source=HOMOGENIZATION_undefined_ID) - allocate(thermal_type(material_Nhomogenization), source=THERMAL_isothermal_ID) - allocate(damage_type (material_Nhomogenization), source=DAMAGE_none_ID) - allocate(homogenization_typeInstance(material_Nhomogenization), source=0) - allocate(thermal_typeInstance(material_Nhomogenization), source=0) - allocate(damage_typeInstance(material_Nhomogenization), source=0) - allocate(homogenization_Ngrains(material_Nhomogenization), source=0) - allocate(thermal_initialT(material_Nhomogenization), source=300.0_pReal) - allocate(damage_initialPhi(material_Nhomogenization), source=1.0_pReal) + allocate(homogenization_type(size(material_name_homogenization)), source=HOMOGENIZATION_undefined_ID) + allocate(thermal_type(size(material_name_homogenization)), source=THERMAL_isothermal_ID) + allocate(damage_type (size(material_name_homogenization)), source=DAMAGE_none_ID) + allocate(homogenization_typeInstance(size(material_name_homogenization)), source=0) + allocate(thermal_typeInstance(size(material_name_homogenization)), source=0) + allocate(damage_typeInstance(size(material_name_homogenization)), source=0) + allocate(thermal_initialT(size(material_name_homogenization)), source=300.0_pReal) + allocate(damage_initialPhi(size(material_name_homogenization)), source=1.0_pReal) - do h=1, material_Nhomogenization + do h=1, size(material_name_homogenization) homog => material_homogenization%get(h) - homogMech => homog%get('mech') - homogenization_Ngrains(h) = homog%get_asInt('N_constituents') + homogMech => homog%get('mechanics') select case (homogMech%get_asString('type')) case('none') homogenization_type(h) = HOMOGENIZATION_NONE_ID @@ -302,15 +272,12 @@ subroutine material_parseHomogenization endif enddo - do h=1, material_Nhomogenization + do h=1, size(material_name_homogenization) homogenization_typeInstance(h) = count(homogenization_type(1:h) == homogenization_type(h)) thermal_typeInstance(h) = count(thermal_type (1:h) == thermal_type (h)) damage_typeInstance(h) = count(damage_type (1:h) == damage_type (h)) enddo - homogenization_maxNgrains = maxval(homogenization_Ngrains) - - end subroutine material_parseHomogenization @@ -324,7 +291,8 @@ subroutine material_parseMaterial constituents, & !> list of constituents constituent, & !> constituent definition phases, & - homogenizations + homogenizations, & + homogenization integer, dimension(:), allocatable :: & counterPhase, & @@ -333,65 +301,106 @@ subroutine material_parseMaterial real(pReal) :: & frac integer :: & - e, & - i, & - m, & - c, & - maxNconstituents + e, i, c, & + h - materials => config_material%get('material') - if(any(discretization_materialAt > materials%length)) & - call IO_error(155,ext_msg='More materials requested than found in material.yaml') - - allocate(material_Nconstituents(materials%length),source=0) - do m = 1, materials%length - material => materials%get(m) - constituents => material%get('constituents') - material_Nconstituents(m) = constituents%length - enddo - maxNconstituents = maxval(material_Nconstituents) - - allocate(material_homogenizationAt(discretization_nElem),source=0) - allocate(material_homogenizationMemberAt(discretization_nIP,discretization_nElem),source=0) - allocate(material_phaseAt(maxNconstituents,discretization_nElem),source=0) - allocate(material_phaseMemberAt(maxNconstituents,discretization_nIP,discretization_nElem),source=0) - - allocate(material_orientation0(maxNconstituents,discretization_nIP,discretization_nElem)) - - phases => config_material%get('phase') - allocate(counterPhase(phases%length),source=0) + materials => config_material%get('material') + phases => config_material%get('phase') homogenizations => config_material%get('homogenization') + + call sanityCheck(materials, homogenizations) + material_name_phase = getKeys(phases) + material_name_homogenization = getKeys(homogenizations) + + allocate(homogenization_Nconstituents(homogenizations%length)) + do h=1, homogenizations%length + homogenization => homogenizations%get(h) + homogenization_Nconstituents(h) = homogenization%get_asInt('N_constituents') + enddo + homogenization_maxNconstituents = maxval(homogenization_Nconstituents) + + allocate(counterPhase(phases%length),source=0) allocate(counterHomogenization(homogenizations%length),source=0) - do e = 1, discretization_nElem + allocate(material_homogenizationAt(discretization_Nelems),source=0) + allocate(material_homogenizationMemberAt(discretization_nIPs,discretization_Nelems),source=0) + allocate(material_phaseAt(homogenization_maxNconstituents,discretization_Nelems),source=0) + allocate(material_phaseMemberAt(homogenization_maxNconstituents,discretization_nIPs,discretization_Nelems),source=0) + + allocate(material_orientation0(homogenization_maxNconstituents,discretization_nIPs,discretization_Nelems)) + + do e = 1, discretization_Nelems material => materials%get(discretization_materialAt(e)) constituents => material%get('constituents') - + material_homogenizationAt(e) = homogenizations%getIndex(material%get_asString('homogenization')) - do i = 1, discretization_nIP + do i = 1, discretization_nIPs counterHomogenization(material_homogenizationAt(e)) = counterHomogenization(material_homogenizationAt(e)) + 1 material_homogenizationMemberAt(i,e) = counterHomogenization(material_homogenizationAt(e)) enddo - + frac = 0.0_pReal do c = 1, constituents%length constituent => constituents%get(c) frac = frac + constituent%get_asFloat('fraction') - + material_phaseAt(c,e) = phases%getIndex(constituent%get_asString('phase')) - do i = 1, discretization_nIP + do i = 1, discretization_nIPs counterPhase(material_phaseAt(c,e)) = counterPhase(material_phaseAt(c,e)) + 1 material_phaseMemberAt(c,i,e) = counterPhase(material_phaseAt(c,e)) - - call material_orientation0(c,i,e)%fromQuaternion(constituent%get_asFloats('O',requiredSize=4)) + + call material_orientation0(c,i,e)%fromQuaternion(constituent%get_asFloats('O',requiredSize=4)) ! should be done in crystallite enddo - + enddo if (dNeq(frac,1.0_pReal)) call IO_error(153,ext_msg='constituent') - + enddo end subroutine material_parseMaterial +!-------------------------------------------------------------------------------------------------- +!> @brief Check if material.yaml is consistent and contains sufficient # of materials +!-------------------------------------------------------------------------------------------------- +subroutine sanityCheck(materials,homogenizations) + + class(tNode), intent(in) :: materials, & + homogenizations + + class(tNode), pointer :: material, & + homogenization, & + constituents + integer :: m + + if(maxval(discretization_materialAt) > materials%length) & + call IO_error(155,ext_msg='More materials requested than found in material.yaml') + + do m = 1, materials%length + material => materials%get(m) + constituents => material%get('constituents') + homogenization => homogenizations%get(material%get_asString('homogenization')) + if(constituents%length /= homogenization%get_asInt('N_constituents')) call IO_error(148) + enddo + +end subroutine sanityCheck + + +!-------------------------------------------------------------------------------------------------- +!> @brief Get all keys from a dictionary +!-------------------------------------------------------------------------------------------------- +function getKeys(dict) + + class(tNode), intent(in) :: dict + character(len=pStringLen), dimension(:), allocatable :: getKeys + + integer :: i + + allocate(getKeys(dict%length)) + do i=1, dict%length + getKeys(i) = dict%getKey(i) + enddo + +end function getKeys + end module material diff --git a/src/math.f90 b/src/math.f90 index 163f4df6a..8005b5406 100644 --- a/src/math.f90 +++ b/src/math.f90 @@ -17,8 +17,8 @@ module math #if __INTEL_COMPILER >= 1900 ! do not make use associated entities available to other modules private :: & - prec, & - IO + IO, & + config #endif real(pReal), parameter :: PI = acos(-1.0_pReal) !< ratio of a circle's circumference to its diameter @@ -499,7 +499,7 @@ function math_invSym3333(A) call dgetrf(6,6,temp66,6,ipiv6,ierr_i) call dgetri(6,temp66,6,ipiv6,work,size(work,1),ierr_f) if (ierr_i /= 0 .or. ierr_f /= 0) then - call IO_error(400, ext_msg = 'math_invSym3333') + error stop 'matrix inversion error' else math_invSym3333 = math_66toSym3333(temp66) endif @@ -1200,8 +1200,8 @@ subroutine selfTest if(any(dNeq(math_exp33(math_I3,0),math_I3))) & error stop 'math_exp33(math_I3,1)' - if(any(dNeq(math_exp33(math_I3,256),exp(1.0_pReal)*math_I3))) & - error stop 'math_exp33(math_I3,256)' + if(any(dNeq(math_exp33(math_I3,128),exp(1.0_pReal)*math_I3))) & + error stop 'math_exp33(math_I3,128)' call random_number(v9) if(any(dNeq(math_33to9(math_9to33(v9)),v9))) & diff --git a/src/mesh/DAMASK_mesh.f90 b/src/mesh/DAMASK_mesh.f90 index bfa8d22ce..1e353892c 100644 --- a/src/mesh/DAMASK_mesh.f90 +++ b/src/mesh/DAMASK_mesh.f90 @@ -20,7 +20,7 @@ program DAMASK_mesh use discretization_mesh use FEM_Utilities use mesh_mech_FEM - + implicit none !-------------------------------------------------------------------------------------------------- @@ -56,7 +56,7 @@ program DAMASK_mesh totalIncsCounter = 0, & !< total # of increments statUnit = 0, & !< file unit for statistics output stagIter, & - component + component class(tNode), pointer :: & num_mesh character(len=pStringLen), dimension(:), allocatable :: fileContent @@ -80,7 +80,7 @@ program DAMASK_mesh call CPFEM_initAll print'(/,a)', ' <<<+- DAMASK_mesh init -+>>>'; flush(IO_STDOUT) -!--------------------------------------------------------------------- +!--------------------------------------------------------------------- ! reading field information from numerics file and do sanity checks num_mesh => config_numerics%get('mesh', defaultVal=emptyDict) stagItMax = num_mesh%get_asInt('maxStaggeredIter',defaultVal=10) @@ -100,7 +100,7 @@ program DAMASK_mesh do l = 1, size(fileContent) line = fileContent(l) if (IO_isBlank(line)) cycle ! skip empty lines - + chunkPos = IO_stringPos(line) do i = 1, chunkPos(1) ! reading compulsory parameters for loadcase select case (IO_lc(IO_stringValue(line,chunkPos,i))) @@ -109,15 +109,16 @@ program DAMASK_mesh end select enddo ! count all identifiers to allocate memory and do sanity check enddo - - allocate (loadCases(N_def)) - + + if(N_def < 1) call IO_error(error_ID = 837) + allocate(loadCases(N_def)) + do i = 1, size(loadCases) allocate(loadCases(i)%fieldBC(nActiveFields)) field = 1 loadCases(i)%fieldBC(field)%ID = FIELD_MECH_ID enddo - + do i = 1, size(loadCases) do field = 1, nActiveFields select case (loadCases(i)%fieldBC(field)%ID) @@ -133,21 +134,21 @@ program DAMASK_mesh case (3) loadCases(i)%fieldBC(field)%componentBC(component)%ID = COMPONENT_MECH_Z_ID end select - enddo + enddo end select do component = 1, loadCases(i)%fieldBC(field)%nComponents allocate(loadCases(i)%fieldBC(field)%componentBC(component)%Value(mesh_Nboundaries), source = 0.0_pReal) allocate(loadCases(i)%fieldBC(field)%componentBC(component)%Mask (mesh_Nboundaries), source = .false.) enddo - enddo - enddo + enddo + enddo !-------------------------------------------------------------------------------------------------- ! reading the load case and assign values to the allocated data structure do l = 1, size(fileContent) line = fileContent(l) if (IO_isBlank(line)) cycle ! skip empty lines - + chunkPos = IO_stringPos(line) do i = 1, chunkPos(1) select case (IO_lc(IO_stringValue(line,chunkPos,i))) @@ -161,7 +162,7 @@ program DAMASK_mesh do faceSet = 1, mesh_Nboundaries if (mesh_boundaries(faceSet) == currentFace) currentFaceSet = faceSet enddo - if (currentFaceSet < 0) call IO_error(error_ID = errorID, ext_msg = 'invalid BC') + if (currentFaceSet < 0) call IO_error(error_ID = 837, ext_msg = 'invalid BC') case('t','time','delta') ! increment time loadCases(currentLoadCase)%time = IO_floatValue(line,chunkPos,i+1) case('n','incs','increments','steps') ! number of increments @@ -170,7 +171,7 @@ program DAMASK_mesh loadCases(currentLoadCase)%incs = IO_intValue(line,chunkPos,i+1) loadCases(currentLoadCase)%logscale = 1 case('freq','frequency','outputfreq') ! frequency of result writings - loadCases(currentLoadCase)%outputfrequency = IO_intValue(line,chunkPos,i+1) + loadCases(currentLoadCase)%outputfrequency = IO_intValue(line,chunkPos,i+1) case('guessreset','dropguessing') loadCases(currentLoadCase)%followFormerTrajectory = .false. ! do not continue to predict deformation along former trajectory @@ -185,7 +186,7 @@ program DAMASK_mesh case('z') ID = COMPONENT_MECH_Z_ID end select - + do field = 1, nActiveFields if (loadCases(currentLoadCase)%fieldBC(field)%ID == FIELD_MECH_ID) then do component = 1, loadcases(currentLoadCase)%fieldBC(field)%nComponents @@ -197,11 +198,11 @@ program DAMASK_mesh endif enddo endif - enddo + enddo end select enddo enddo - + !-------------------------------------------------------------------------------------------------- ! consistency checks and output of load case loadCases(1)%followFormerTrajectory = .false. ! cannot guess along trajectory for first inc of first currentLoadCase @@ -215,17 +216,17 @@ program DAMASK_mesh select case (loadCases(currentLoadCase)%fieldBC(field)%ID) case(FIELD_MECH_ID) print'(a)', ' Field '//trim(FIELD_MECH_label) - + end select do faceSet = 1, mesh_Nboundaries do component = 1, loadCases(currentLoadCase)%fieldBC(field)%nComponents if (loadCases(currentLoadCase)%fieldBC(field)%componentBC(component)%Mask(faceSet)) & print'(a,i2,a,i2,a,f12.7)', ' Face ', mesh_boundaries(faceSet), & - ' Component ', component, & + ' Component ', component, & ' Value ', loadCases(currentLoadCase)%fieldBC(field)% & componentBC(component)%Value(faceSet) enddo - enddo + enddo enddo print'(a,f12.6)', ' time: ', loadCases(currentLoadCase)%time if (loadCases(currentLoadCase)%incs < 1) errorID = 835 ! non-positive incs count @@ -244,7 +245,7 @@ program DAMASK_mesh case(FIELD_MECH_ID) call FEM_mech_init(loadCases(1)%fieldBC(field)) end select - enddo + enddo if (worldrank == 0) then open(newunit=statUnit,file=trim(getSolverJobName())//'.sta',form='FORMATTED',status='REPLACE') @@ -254,9 +255,9 @@ program DAMASK_mesh loadCaseLooping: do currentLoadCase = 1, size(loadCases) time0 = time ! load case start time guess = loadCases(currentLoadCase)%followFormerTrajectory ! change of load case? homogeneous guess for the first inc - + incLooping: do inc = 1, loadCases(currentLoadCase)%incs - totalIncsCounter = totalIncsCounter + 1 + totalIncsCounter = totalIncsCounter + 1 !-------------------------------------------------------------------------------------------------- ! forwarding time @@ -266,7 +267,7 @@ program DAMASK_mesh else if (currentLoadCase == 1) then ! 1st load case of logarithmic scale if (inc == 1) then ! 1st inc of 1st load case of logarithmic scale - timeinc = loadCases(1)%time*(2.0_pReal**real( 1-loadCases(1)%incs ,pReal)) ! assume 1st inc is equal to 2nd + timeinc = loadCases(1)%time*(2.0_pReal**real( 1-loadCases(1)%incs ,pReal)) ! assume 1st inc is equal to 2nd else ! not-1st inc of 1st load case of logarithmic scale timeinc = loadCases(1)%time*(2.0_pReal**real(inc-1-loadCases(1)%incs ,pReal)) endif @@ -287,7 +288,7 @@ program DAMASK_mesh remainingLoadCaseTime = loadCases(currentLoadCase)%time+time0 - time time = time + timeinc ! forward target time stepFraction = stepFraction + 1 ! count step - + !-------------------------------------------------------------------------------------------------- ! report begin of new step print'(/,a)', ' ###########################################################################' @@ -310,8 +311,8 @@ program DAMASK_mesh guess,timeinc,timeIncOld,loadCases(currentLoadCase)%fieldBC(field)) end select - enddo - + enddo + !-------------------------------------------------------------------------------------------------- ! solve fields stagIter = 0 @@ -332,10 +333,10 @@ program DAMASK_mesh stagIterate = stagIter < stagItMax & .and. all(solres(:)%converged) & .and. .not. all(solres(:)%stagConverged) ! stationary with respect to staggered iteration - enddo - -! check solution - cutBack = .False. + enddo + +! check solution + cutBack = .False. if(.not. all(solres(:)%converged .and. solres(:)%stagConverged)) then ! no solution found if (cutBackLevel < maxCutBack) then ! do cut back print'(/,a)', ' cut back detected' @@ -344,7 +345,7 @@ program DAMASK_mesh cutBackLevel = cutBackLevel + 1 time = time - timeinc ! rewind time timeinc = timeinc/2.0_pReal - else ! default behavior, exit if spectral solver does not converge + else ! default behavior, exit if spectral solver does not converge call IO_warning(850) call quit(1) ! quit endif @@ -374,8 +375,8 @@ program DAMASK_mesh enddo incLooping enddo loadCaseLooping - - + + !-------------------------------------------------------------------------------------------------- ! report summary of whole calculation print'(/,a)', ' ###########################################################################' diff --git a/src/mesh/FEM_utilities.f90 b/src/mesh/FEM_utilities.f90 index 4927d0c1c..118735e89 100644 --- a/src/mesh/FEM_utilities.f90 +++ b/src/mesh/FEM_utilities.f90 @@ -12,11 +12,11 @@ module FEM_utilities use PETScis use prec - use FEsolving - use homogenization use config use math + use IO use discretization_mesh + use homogenization implicit none private @@ -164,7 +164,7 @@ subroutine utilities_constitutiveResponse(timeinc,P_av,forwardData) cutBack = .false. ! reset cutBack status - P_av = sum(sum(materialpoint_P,dim=4),dim=3) * wgt ! average of P + P_av = sum(sum(homogenization_P,dim=4),dim=3) * wgt ! average of P call MPI_Allreduce(MPI_IN_PLACE,P_av,9,MPI_DOUBLE,MPI_SUM,PETSC_COMM_WORLD,ierr) end subroutine utilities_constitutiveResponse diff --git a/src/mesh/discretization_mesh.f90 b/src/mesh/discretization_mesh.f90 index bc96951a1..7dbd05e46 100644 --- a/src/mesh/discretization_mesh.f90 +++ b/src/mesh/discretization_mesh.f90 @@ -17,6 +17,7 @@ module discretization_mesh use IO use config use discretization + use results use FEsolving use FEM_quadrature use YAML_types @@ -182,6 +183,10 @@ subroutine discretization_mesh_init(restart) reshape(mesh_ipCoordinates,[3,mesh_maxNips*mesh_NcpElems]), & mesh_node0) + call results_openJobFile + call results_closeGroup(results_addGroup('geometry')) + call results_closeJobFile + end subroutine discretization_mesh_init diff --git a/src/mesh/mesh_mech_FEM.f90 b/src/mesh/mesh_mech_FEM.f90 index f7d33adcf..8aa084ac8 100644 --- a/src/mesh/mesh_mech_FEM.f90 +++ b/src/mesh/mesh_mech_FEM.f90 @@ -400,15 +400,15 @@ subroutine FEM_mech_formResidual(dm_local,xx_local,f_local,dummy,ierr) (((qPt*nBasis + basis)*dimPlex + comp)*dimPlex+comp+1)*dimPlex)) enddo enddo - materialpoint_F(1:dimPlex,1:dimPlex,qPt+1,cell+1) = & + homogenization_F(1:dimPlex,1:dimPlex,qPt+1,cell+1) = & reshape(matmul(BMat,x_scal),shape=[dimPlex,dimPlex], order=[2,1]) enddo if (num%BBarStabilisation) then - detFAvg = math_det33(sum(materialpoint_F(1:3,1:3,1:nQuadrature,cell+1),dim=3)/real(nQuadrature)) + detFAvg = math_det33(sum(homogenization_F(1:3,1:3,1:nQuadrature,cell+1),dim=3)/real(nQuadrature)) do qPt = 1, nQuadrature - materialpoint_F(1:dimPlex,1:dimPlex,qPt,cell+1) = & - materialpoint_F(1:dimPlex,1:dimPlex,qPt,cell+1)* & - (detFAvg/math_det33(materialpoint_F(1:3,1:3,qPt,cell+1)))**(1.0/real(dimPlex)) + homogenization_F(1:dimPlex,1:dimPlex,qPt,cell+1) = & + homogenization_F(1:dimPlex,1:dimPlex,qPt,cell+1)* & + (detFAvg/math_det33(homogenization_F(1:3,1:3,qPt,cell+1)))**(1.0/real(dimPlex)) enddo endif @@ -443,7 +443,7 @@ subroutine FEM_mech_formResidual(dm_local,xx_local,f_local,dummy,ierr) enddo f_scal = f_scal + & matmul(transpose(BMat), & - reshape(transpose(materialpoint_P(1:dimPlex,1:dimPlex,qPt+1,cell+1)), & + reshape(transpose(homogenization_P(1:dimPlex,1:dimPlex,qPt+1,cell+1)), & shape=[dimPlex*dimPlex]))*qWeights(qPt+1) enddo f_scal = f_scal*abs(detJ) @@ -545,7 +545,7 @@ subroutine FEM_mech_formJacobian(dm_local,xx_local,Jac_pre,Jac,dummy,ierr) (((qPt*nBasis + basis)*dimPlex + comp)*dimPlex+comp+1)*dimPlex)) enddo enddo - MatA = matmul(reshape(reshape(materialpoint_dPdF(1:dimPlex,1:dimPlex,1:dimPlex,1:dimPlex,qPt+1,cell+1), & + MatA = matmul(reshape(reshape(homogenization_dPdF(1:dimPlex,1:dimPlex,1:dimPlex,1:dimPlex,qPt+1,cell+1), & shape=[dimPlex,dimPlex,dimPlex,dimPlex], order=[2,1,4,3]), & shape=[dimPlex*dimPlex,dimPlex*dimPlex]),BMat)*qWeights(qPt+1) if (num%BBarStabilisation) then @@ -553,12 +553,12 @@ subroutine FEM_mech_formJacobian(dm_local,xx_local,Jac_pre,Jac,dummy,ierr) FInv = math_inv33(F) K_eA = K_eA + matmul(transpose(BMat),MatA)*math_det33(FInv)**(1.0/real(dimPlex)) K_eB = K_eB - & - matmul(transpose(matmul(reshape(materialpoint_F(1:dimPlex,1:dimPlex,qPt+1,cell+1), & + matmul(transpose(matmul(reshape(homogenization_F(1:dimPlex,1:dimPlex,qPt+1,cell+1), & shape=[dimPlex*dimPlex,1]), & matmul(reshape(FInv(1:dimPlex,1:dimPlex), & shape=[1,dimPlex*dimPlex],order=[2,1]),BMat))),MatA) MatB = MatB + & - matmul(reshape(materialpoint_F(1:dimPlex,1:dimPlex,qPt+1,cell+1),shape=[1,dimPlex*dimPlex]),MatA) + matmul(reshape(homogenization_F(1:dimPlex,1:dimPlex,qPt+1,cell+1),shape=[1,dimPlex*dimPlex]),MatA) FAvg = FAvg + F BMatAvg = BMatAvg + BMat else @@ -630,7 +630,7 @@ subroutine FEM_mech_forward(guess,timeinc,timeinc_old,fieldBC) ! forward last inc if (guess .and. .not. cutBack) then ForwardData = .True. - materialpoint_F0 = materialpoint_F + homogenization_F0 = homogenization_F call SNESGetDM(mech_snes,dm_local,ierr); CHKERRQ(ierr) !< retrieve mesh info from mech_snes into dm_local call DMGetSection(dm_local,section,ierr); CHKERRQ(ierr) call DMGetLocalVector(dm_local,x_local,ierr); CHKERRQ(ierr) diff --git a/src/parallelization.f90 b/src/parallelization.f90 index fb50a1a23..11a3574ec 100644 --- a/src/parallelization.f90 +++ b/src/parallelization.f90 @@ -50,10 +50,21 @@ subroutine parallelization_init if (threadLevel>>' diff --git a/src/prec.f90 b/src/prec.f90 index b2866a4f4..738775e3b 100644 --- a/src/prec.f90 +++ b/src/prec.f90 @@ -93,7 +93,8 @@ subroutine prec_init print'(a,i19)', ' Maximum value: ',huge(0) print'(/,a,i3)', ' Size of float in bit: ',storage_size(0.0_pReal) print'(a,e10.3)', ' Maximum value: ',huge(0.0_pReal) - print'(a,e10.3)', ' Minimum value: ',tiny(0.0_pReal) + print'(a,e10.3)', ' Minimum value: ',PREAL_MIN + print'(a,e10.3)', ' Epsilon value: ',PREAL_EPSILON print'(a,i3)', ' Decimal precision: ',precision(0.0_pReal) call selfTest diff --git a/src/quit.f90 b/src/quit.f90 index 5c421c86a..26dc23bac 100644 --- a/src/quit.f90 +++ b/src/quit.f90 @@ -23,7 +23,7 @@ subroutine quit(stop_id) call h5close_f(error) if (error /= 0) write(6,'(a,i5)') ' Error in h5close_f ',error - call PETScFinalize(ierr) + call PetscFinalize(ierr) CHKERRQ(ierr) #ifdef _OPENMP diff --git a/src/results.f90 b/src/results.f90 index aec90d7be..bf276f561 100644 --- a/src/results.f90 +++ b/src/results.f90 @@ -7,6 +7,7 @@ module results use DAMASK_interface use parallelization + use IO use rotations use HDF5_utilities #ifdef PETSc @@ -56,7 +57,7 @@ module results results_addAttribute, & results_removeLink, & results_mapping_constituent, & - results_mapping_materialpoint + results_mapping_homogenization contains subroutine results_init(restart) @@ -73,12 +74,11 @@ subroutine results_init(restart) if(.not. restart) then resultsFile = HDF5_openFile(trim(getSolverJobName())//'.hdf5','w',.true.) call results_addAttribute('DADF5_version_major',0) - call results_addAttribute('DADF5_version_minor',7) + call results_addAttribute('DADF5_version_minor',9) call results_addAttribute('DAMASK_version',DAMASKVERSION) call get_command(commandLine) - call results_addAttribute('call',trim(commandLine)) + call results_addAttribute('Call',trim(commandLine)) call results_closeGroup(results_addGroup('mapping')) - call results_closeGroup(results_addGroup('mapping/cellResults')) call results_closeJobFile endif @@ -121,12 +121,6 @@ subroutine results_addIncrement(inc,time) call results_closeGroup(results_addGroup('current/phase')) call results_closeGroup(results_addGroup('current/homogenization')) - ! for backward compatibility - call results_setLink(trim('/inc'//trim(adjustl(incChar)))//'/phase',& - trim('/inc'//trim(adjustl(incChar)))//'/constituent') - call results_setLink(trim('/inc'//trim(adjustl(incChar)))//'/homogenization',& - trim('/inc'//trim(adjustl(incChar)))//'/materialpoint') - end subroutine results_addIncrement @@ -535,33 +529,46 @@ subroutine results_mapping_constituent(phaseAt,memberAtLocal,label) integer(SIZE_T) :: type_size_string, type_size_int - integer :: ierr, i + integer :: hdferr, ierr, i !--------------------------------------------------------------------------------------------------- ! compound type: name of phase section + position/index within results array - call h5tcopy_f(H5T_NATIVE_CHARACTER, dt_id, ierr) - call h5tset_size_f(dt_id, int(len(label(1)),SIZE_T), ierr) - call h5tget_size_f(dt_id, type_size_string, ierr) + call h5tcopy_f(H5T_NATIVE_CHARACTER, dt_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tset_size_f(dt_id, int(len(label(1)),SIZE_T), hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tget_size_f(dt_id, type_size_string, hdferr) + if(hdferr < 0) error stop 'HDF5 error' - call h5tget_size_f(H5T_NATIVE_INTEGER, type_size_int, ierr) + call h5tget_size_f(H5T_NATIVE_INTEGER, type_size_int, hdferr) + if(hdferr < 0) error stop 'HDF5 error' - call h5tcreate_f(H5T_COMPOUND_F, type_size_string + type_size_int, dtype_id, ierr) - call h5tinsert_f(dtype_id, "Name", 0_SIZE_T, dt_id,ierr) - call h5tinsert_f(dtype_id, "Position", type_size_string, H5T_NATIVE_INTEGER, ierr) + call h5tcreate_f(H5T_COMPOUND_F, type_size_string + type_size_int, dtype_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tinsert_f(dtype_id, "Name", 0_SIZE_T, dt_id,hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tinsert_f(dtype_id, "Position", type_size_string, H5T_NATIVE_INTEGER, hdferr) + if(hdferr < 0) error stop 'HDF5 error' !-------------------------------------------------------------------------------------------------- ! create memory types for each component of the compound type - call h5tcreate_f(H5T_COMPOUND_F, type_size_string, name_id, ierr) - call h5tinsert_f(name_id, "Name", 0_SIZE_T, dt_id, ierr) + call h5tcreate_f(H5T_COMPOUND_F, type_size_string, name_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tinsert_f(name_id, "Name", 0_SIZE_T, dt_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' - call h5tcreate_f(H5T_COMPOUND_F, type_size_int, position_id, ierr) - call h5tinsert_f(position_id, "Position", 0_SIZE_T, H5T_NATIVE_INTEGER, ierr) + call h5tcreate_f(H5T_COMPOUND_F, type_size_int, position_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tinsert_f(position_id, "Position", 0_SIZE_T, H5T_NATIVE_INTEGER, hdferr) + if(hdferr < 0) error stop 'HDF5 error' - call h5tclose_f(dt_id, ierr) + call h5tclose_f(dt_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' !-------------------------------------------------------------------------------------------------- ! prepare MPI communication (transparent for non-MPI runs) - call h5pcreate_f(H5P_DATASET_XFER_F, plist_id, ierr) + call h5pcreate_f(H5P_DATASET_XFER_F, plist_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' memberOffset = 0 do i=1, size(label) memberOffset(i,worldrank) = count(phaseAt == i)*size(memberAtLocal,2) ! number of points/instance of this process @@ -572,14 +579,14 @@ subroutine results_mapping_constituent(phaseAt,memberAtLocal,label) !-------------------------------------------------------------------------------------------------- ! MPI settings and communication #ifdef PETSc - call h5pset_dxpl_mpio_f(plist_id, H5FD_MPIO_COLLECTIVE_F, ierr) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_constituent: h5pset_dxpl_mpio_f') + call h5pset_dxpl_mpio_f(plist_id, H5FD_MPIO_COLLECTIVE_F, hdferr) + if(hdferr < 0) error stop 'HDF5 error' call MPI_allreduce(MPI_IN_PLACE,writeSize,worldsize,MPI_INT,MPI_SUM,PETSC_COMM_WORLD,ierr) ! get output at each process - if (ierr /= 0) call IO_error(894,ext_msg='results_mapping_constituent: MPI_allreduce/writeSize') + if(ierr /= 0) error stop 'MPI error' call MPI_allreduce(MPI_IN_PLACE,memberOffset,size(memberOffset),MPI_INT,MPI_SUM,PETSC_COMM_WORLD,ierr)! get offset at each process - if (ierr /= 0) call IO_error(894,ext_msg='results_mapping_constituent: MPI_allreduce/memberOffset') + if(ierr /= 0) error stop 'MPI error' #endif myShape = int([size(phaseAt,1),writeSize(worldrank)], HSIZE_T) @@ -588,14 +595,14 @@ subroutine results_mapping_constituent(phaseAt,memberAtLocal,label) !-------------------------------------------------------------------------------------------------- ! create dataspace in memory (local shape = hyperslab) and in file (global shape) - call h5screate_simple_f(2,myShape,memspace_id,ierr,myShape) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_constituent: h5screate_simple_f/memspace_id') + call h5screate_simple_f(2,myShape,memspace_id,hdferr,myShape) + if(hdferr < 0) error stop 'HDF5 error' - call h5screate_simple_f(2,totalShape,filespace_id,ierr,totalShape) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_constituent: h5screate_simple_f/filespace_id') + call h5screate_simple_f(2,totalShape,filespace_id,hdferr,totalShape) + if(hdferr < 0) error stop 'HDF5 error' - call h5sselect_hyperslab_f(filespace_id, H5S_SELECT_SET_F, myOffset, myShape, ierr) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_constituent: h5sselect_hyperslab_f') + call h5sselect_hyperslab_f(filespace_id, H5S_SELECT_SET_F, myOffset, myShape, hdferr) + if(hdferr < 0) error stop 'HDF5 error' !--------------------------------------------------------------------------------------------------- ! expand phaseAt to consider IPs (is not stored per IP) @@ -611,32 +618,36 @@ subroutine results_mapping_constituent(phaseAt,memberAtLocal,label) !-------------------------------------------------------------------------------------------------- ! write the components of the compound type individually - call h5pset_preserve_f(plist_id, .TRUE., ierr) + call h5pset_preserve_f(plist_id, .TRUE., hdferr) + if(hdferr < 0) error stop 'HDF5 error' loc_id = results_openGroup('/mapping') - call h5dcreate_f(loc_id, 'phase', dtype_id, filespace_id, dset_id, ierr) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_constituent: h5dcreate_f') + call h5dcreate_f(loc_id, 'phase', dtype_id, filespace_id, dset_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' call h5dwrite_f(dset_id, name_id, reshape(label(pack(phaseAtMaterialpoint,.true.)),myShape), & - myShape, ierr, file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_constituent: h5dwrite_f/name_id') + myShape, hdferr, file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) + if(hdferr < 0) error stop 'HDF5 error' call h5dwrite_f(dset_id, position_id, reshape(pack(memberAtGlobal,.true.),myShape), & - myShape, ierr, file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_constituent: h5dwrite_f/position_id') + myShape, hdferr, file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) + if(hdferr < 0) error stop 'HDF5 error' !-------------------------------------------------------------------------------------------------- ! close all call HDF5_closeGroup(loc_id) - call h5pclose_f(plist_id, ierr) - call h5sclose_f(filespace_id, ierr) - call h5sclose_f(memspace_id, ierr) - call h5dclose_f(dset_id, ierr) - call h5tclose_f(dtype_id, ierr) - call h5tclose_f(name_id, ierr) - call h5tclose_f(position_id, ierr) - - ! for backward compatibility - call results_setLink('/mapping/phase','/mapping/cellResults/constituent') + call h5pclose_f(plist_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5sclose_f(filespace_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5sclose_f(memspace_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5dclose_f(dset_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tclose_f(dtype_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tclose_f(name_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tclose_f(position_id, hdferr) end subroutine results_mapping_constituent @@ -644,7 +655,7 @@ end subroutine results_mapping_constituent !-------------------------------------------------------------------------------------------------- !> @brief adds the unique mapping from spatial position and constituent ID to results !-------------------------------------------------------------------------------------------------- -subroutine results_mapping_materialpoint(homogenizationAt,memberAtLocal,label) +subroutine results_mapping_homogenization(homogenizationAt,memberAtLocal,label) integer, dimension(:), intent(in) :: homogenizationAt !< homogenization section at (element) integer, dimension(:,:), intent(in) :: memberAtLocal !< homogenization member at (IP,element) @@ -673,51 +684,64 @@ subroutine results_mapping_materialpoint(homogenizationAt,memberAtLocal,label) integer(SIZE_T) :: type_size_string, type_size_int - integer :: ierr, i + integer :: hdferr, ierr, i !--------------------------------------------------------------------------------------------------- ! compound type: name of phase section + position/index within results array - call h5tcopy_f(H5T_NATIVE_CHARACTER, dt_id, ierr) - call h5tset_size_f(dt_id, int(len(label(1)),SIZE_T), ierr) - call h5tget_size_f(dt_id, type_size_string, ierr) + call h5tcopy_f(H5T_NATIVE_CHARACTER, dt_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tset_size_f(dt_id, int(len(label(1)),SIZE_T), hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tget_size_f(dt_id, type_size_string, hdferr) + if(hdferr < 0) error stop 'HDF5 error' - call h5tget_size_f(H5T_NATIVE_INTEGER, type_size_int, ierr) + call h5tget_size_f(H5T_NATIVE_INTEGER, type_size_int, hdferr) + if(hdferr < 0) error stop 'HDF5 error' - call h5tcreate_f(H5T_COMPOUND_F, type_size_string + type_size_int, dtype_id, ierr) - call h5tinsert_f(dtype_id, "Name", 0_SIZE_T, dt_id,ierr) - call h5tinsert_f(dtype_id, "Position", type_size_string, H5T_NATIVE_INTEGER, ierr) + call h5tcreate_f(H5T_COMPOUND_F, type_size_string + type_size_int, dtype_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tinsert_f(dtype_id, "Name", 0_SIZE_T, dt_id,hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tinsert_f(dtype_id, "Position", type_size_string, H5T_NATIVE_INTEGER, hdferr) + if(hdferr < 0) error stop 'HDF5 error' !-------------------------------------------------------------------------------------------------- ! create memory types for each component of the compound type - call h5tcreate_f(H5T_COMPOUND_F, type_size_string, name_id, ierr) - call h5tinsert_f(name_id, "Name", 0_SIZE_T, dt_id, ierr) + call h5tcreate_f(H5T_COMPOUND_F, type_size_string, name_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tinsert_f(name_id, "Name", 0_SIZE_T, dt_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' - call h5tcreate_f(H5T_COMPOUND_F, type_size_int, position_id, ierr) - call h5tinsert_f(position_id, "Position", 0_SIZE_T, H5T_NATIVE_INTEGER, ierr) + call h5tcreate_f(H5T_COMPOUND_F, type_size_int, position_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tinsert_f(position_id, "Position", 0_SIZE_T, H5T_NATIVE_INTEGER, hdferr) + if(hdferr < 0) error stop 'HDF5 error' - call h5tclose_f(dt_id, ierr) + call h5tclose_f(dt_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' !-------------------------------------------------------------------------------------------------- ! prepare MPI communication (transparent for non-MPI runs) - call h5pcreate_f(H5P_DATASET_XFER_F, plist_id, ierr) + call h5pcreate_f(H5P_DATASET_XFER_F, plist_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' memberOffset = 0 do i=1, size(label) - memberOffset(i,worldrank) = count(homogenizationAt == i)*size(memberAtLocal,1) ! number of points/instance of this process + memberOffset(i,worldrank) = count(homogenizationAt == i)*size(memberAtLocal,1) ! number of points/instance of this process enddo writeSize = 0 - writeSize(worldrank) = size(memberAtLocal) ! total number of points by this process + writeSize(worldrank) = size(memberAtLocal) ! total number of points by this process !-------------------------------------------------------------------------------------------------- ! MPI settings and communication #ifdef PETSc - call h5pset_dxpl_mpio_f(plist_id, H5FD_MPIO_COLLECTIVE_F, ierr) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_materialpoint: h5pset_dxpl_mpio_f') + call h5pset_dxpl_mpio_f(plist_id, H5FD_MPIO_COLLECTIVE_F, hdferr) + if(hdferr < 0) error stop 'HDF5 error' call MPI_allreduce(MPI_IN_PLACE,writeSize,worldsize,MPI_INT,MPI_SUM,PETSC_COMM_WORLD,ierr) ! get output at each process - if (ierr /= 0) call IO_error(894,ext_msg='results_mapping_materialpoint: MPI_allreduce/writeSize') + if(ierr /= 0) error stop 'MPI error' call MPI_allreduce(MPI_IN_PLACE,memberOffset,size(memberOffset),MPI_INT,MPI_SUM,PETSC_COMM_WORLD,ierr)! get offset at each process - if (ierr /= 0) call IO_error(894,ext_msg='results_mapping_materialpoint: MPI_allreduce/memberOffset') + if(ierr /= 0) error stop 'MPI error' #endif myShape = int([writeSize(worldrank)], HSIZE_T) @@ -726,14 +750,14 @@ subroutine results_mapping_materialpoint(homogenizationAt,memberAtLocal,label) !-------------------------------------------------------------------------------------------------- ! create dataspace in memory (local shape = hyperslab) and in file (global shape) - call h5screate_simple_f(1,myShape,memspace_id,ierr,myShape) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_materialpoint: h5screate_simple_f/memspace_id') + call h5screate_simple_f(1,myShape,memspace_id,hdferr,myShape) + if(hdferr < 0) error stop 'HDF5 error' - call h5screate_simple_f(1,totalShape,filespace_id,ierr,totalShape) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_materialpoint: h5screate_simple_f/filespace_id') + call h5screate_simple_f(1,totalShape,filespace_id,hdferr,totalShape) + if(hdferr < 0) error stop 'HDF5 error' - call h5sselect_hyperslab_f(filespace_id, H5S_SELECT_SET_F, myOffset, myShape, ierr) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_materialpoint: h5sselect_hyperslab_f') + call h5sselect_hyperslab_f(filespace_id, H5S_SELECT_SET_F, myOffset, myShape, hdferr) + if(hdferr < 0) error stop 'HDF5 error' !--------------------------------------------------------------------------------------------------- ! expand phaseAt to consider IPs (is not stored per IP) @@ -749,34 +773,38 @@ subroutine results_mapping_materialpoint(homogenizationAt,memberAtLocal,label) !-------------------------------------------------------------------------------------------------- ! write the components of the compound type individually - call h5pset_preserve_f(plist_id, .TRUE., ierr) + call h5pset_preserve_f(plist_id, .TRUE., hdferr) loc_id = results_openGroup('/mapping') - call h5dcreate_f(loc_id, 'homogenization', dtype_id, filespace_id, dset_id, ierr) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_materialpoint: h5dcreate_f') + call h5dcreate_f(loc_id, 'homogenization', dtype_id, filespace_id, dset_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' call h5dwrite_f(dset_id, name_id, reshape(label(pack(homogenizationAtMaterialpoint,.true.)),myShape), & - myShape, ierr, file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_materialpoint: h5dwrite_f/name_id') + myShape, hdferr, file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) + if(hdferr < 0) error stop 'HDF5 error' call h5dwrite_f(dset_id, position_id, reshape(pack(memberAtGlobal,.true.),myShape), & - myShape, ierr, file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) - if (ierr < 0) call IO_error(1,ext_msg='results_mapping_materialpoint: h5dwrite_f/position_id') + myShape, hdferr, file_space_id = filespace_id, mem_space_id = memspace_id, xfer_prp = plist_id) + if(hdferr < 0) error stop 'HDF5 error' !-------------------------------------------------------------------------------------------------- ! close all call HDF5_closeGroup(loc_id) - call h5pclose_f(plist_id, ierr) - call h5sclose_f(filespace_id, ierr) - call h5sclose_f(memspace_id, ierr) - call h5dclose_f(dset_id, ierr) - call h5tclose_f(dtype_id, ierr) - call h5tclose_f(name_id, ierr) - call h5tclose_f(position_id, ierr) + call h5pclose_f(plist_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5sclose_f(filespace_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5sclose_f(memspace_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5dclose_f(dset_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tclose_f(dtype_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tclose_f(name_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' + call h5tclose_f(position_id, hdferr) + if(hdferr < 0) error stop 'HDF5 error' - ! for backward compatibility - call results_setLink('/mapping/homogenization','/mapping/cellResults/materialpoint') - -end subroutine results_mapping_materialpoint +end subroutine results_mapping_homogenization !-------------------------------------------------------------------------------------------------- @@ -793,263 +821,4 @@ character(len=24) function now() end function now - -!!-------------------------------------------------------------------------------------------------- -!!> @brief adds the backward mapping from spatial position and constituent ID to results -!!-------------------------------------------------------------------------------------------------- -!subroutine HDF5_backwardMappingPhase(material_phase,phasememberat,phase_name,dataspace_size,mpiOffset,mpiOffset_phase) - -! integer(pInt), intent(in), dimension(:,:,:) :: material_phase, phasememberat -! character(len=*), intent(in), dimension(:) :: phase_name -! integer(pInt), intent(in), dimension(:) :: dataspace_size, mpiOffset_phase -! integer(pInt), intent(in) :: mpiOffset - -! integer(pInt) :: hdferr, NmatPoints, Nconstituents, i, j -! integer(HID_T) :: mapping_id, dtype_id, dset_id, space_id, position_id, plist_id, memspace -! integer(SIZE_T) :: type_size - -! integer(pInt), dimension(:,:), allocatable :: arr - -! integer(HSIZE_T), dimension(1) :: counter -! integer(HSSIZE_T), dimension(1) :: fileOffset - -! character(len=64) :: phaseID - -! Nconstituents = size(phasememberat,1) -! NmatPoints = count(material_phase /=0)/Nconstituents - -! allocate(arr(2,NmatPoints*Nconstituents)) - -! do i=1, NmatPoints -! do j=Nconstituents-1, 0, -1 -! arr(1,Nconstituents*i-j) = i-1 -! enddo -! enddo -! arr(2,:) = pack(material_phase,material_phase/=0) - -! do i=1, size(phase_name) -! write(phaseID, '(i0)') i -! mapping_ID = results_openGroup('/current/constitutive/'//trim(phaseID)//'_'//phase_name(i)) -! NmatPoints = count(material_phase == i) - -!!-------------------------------------------------------------------------------------------------- -! ! create dataspace -! call h5screate_simple_f(1, int([dataspace_size(i)],HSIZE_T), space_id, hdferr, & -! int([dataspace_size(i)],HSIZE_T)) -! if (hdferr < 0) call IO_error(1,ext_msg='HDF5_writeBackwardMapping') - -!!-------------------------------------------------------------------------------------------------- -! ! compound type -! call h5tget_size_f(H5T_STD_I32LE, type_size, hdferr) -! call h5tcreate_f(H5T_COMPOUND_F, type_size, dtype_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='HDF5_writeBackwardMapping: h5tcreate_f dtype_id') - -! call h5tinsert_f(dtype_id, "Position", 0_SIZE_T, H5T_STD_I32LE, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5tinsert_f 0') - -!!-------------------------------------------------------------------------------------------------- -! ! create Dataset -! call h5dcreate_f(mapping_id, 'mapGeometry', dtype_id, space_id, dset_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase') - -!!-------------------------------------------------------------------------------------------------- -! ! Create memory types (one compound datatype for each member) -! call h5tcreate_f(H5T_COMPOUND_F, int(pInt,SIZE_T), position_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5tcreate_f position_id') -! call h5tinsert_f(position_id, "Position", 0_SIZE_T, H5T_STD_I32LE, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5tinsert_f position_id') - -!!-------------------------------------------------------------------------------------------------- -! ! Define and select hyperslabs -! counter = NmatPoints ! how big i am -! fileOffset = mpiOffset_phase(i) ! where i start to write my data - -! call h5screate_simple_f(1, counter, memspace, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5screate_simple_f') -! call h5dget_space_f(dset_id, space_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5dget_space_f') -! call h5sselect_hyperslab_f(space_id, H5S_SELECT_SET_F, fileOffset, counter, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5sselect_hyperslab_f') - -!!-------------------------------------------------------------------------------------------------- -! ! Create property list for collective dataset write -!#ifdef PETSc -! call h5pcreate_f(H5P_DATASET_XFER_F, plist_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5pcreate_f') -! call h5pset_dxpl_mpio_f(plist_id, H5FD_MPIO_COLLECTIVE_F, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5pset_dxpl_mpio_f') -!#endif - -!!-------------------------------------------------------------------------------------------------- -! ! write data by fields in the datatype. Fields order is not important. -! call h5dwrite_f(dset_id, position_id, pack(arr(1,:),arr(2,:)==i)+mpiOffset, int([dataspace_size(i)],HSIZE_T),& -! hdferr, file_space_id = space_id, mem_space_id = memspace, xfer_prp = plist_id) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5dwrite_f instance_id') - -!!-------------------------------------------------------------------------------------------------- -! !close types, dataspaces -! call h5tclose_f(dtype_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5tclose_f dtype_id') -! call h5tclose_f(position_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5tclose_f position_id') -! call h5dclose_f(dset_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5dclose_f') -! call h5sclose_f(space_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5sclose_f space_id') -! call h5sclose_f(memspace, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5sclose_f memspace') -! call h5pclose_f(plist_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingPhase: h5pclose_f') -! call HDF5_closeGroup(mapping_ID) - -! enddo - -!end subroutine HDF5_backwardMappingPhase - - -!!-------------------------------------------------------------------------------------------------- -!!> @brief adds the backward mapping from spatial position and constituent ID to results -!!-------------------------------------------------------------------------------------------------- -!subroutine HDF5_backwardMappingHomog(material_homog,homogmemberat,homogenization_name,dataspace_size,mpiOffset,mpiOffset_homog) - -! integer(pInt), intent(in), dimension(:,:) :: material_homog, homogmemberat -! character(len=*), intent(in), dimension(:) :: homogenization_name -! integer(pInt), intent(in), dimension(:) :: dataspace_size, mpiOffset_homog -! integer(pInt), intent(in) :: mpiOffset - -! integer(pInt) :: hdferr, NmatPoints, i -! integer(HID_T) :: mapping_id, dtype_id, dset_id, space_id, position_id, plist_id, memspace -! integer(SIZE_T) :: type_size - -! integer(pInt), dimension(:,:), allocatable :: arr - -! integer(HSIZE_T), dimension(1) :: counter -! integer(HSSIZE_T), dimension(1) :: fileOffset - -! character(len=64) :: homogID - -! NmatPoints = count(material_homog /=0) -! allocate(arr(2,NmatPoints)) - -! arr(1,:) = (/(i, i=0,NmatPoints-1)/) -! arr(2,:) = pack(material_homog,material_homog/=0) - -! do i=1, size(homogenization_name) -! write(homogID, '(i0)') i -! mapping_ID = results_openGroup('/current/homogenization/'//trim(homogID)//'_'//homogenization_name(i)) - -!!-------------------------------------------------------------------------------------------------- -! ! create dataspace -! call h5screate_simple_f(1, int([dataspace_size(i)],HSIZE_T), space_id, hdferr, & -! int([dataspace_size(i)],HSIZE_T)) -! if (hdferr < 0) call IO_error(1,ext_msg='HDF5_writeBackwardMapping') - -!!-------------------------------------------------------------------------------------------------- -! ! compound type -! call h5tget_size_f(H5T_STD_I32LE, type_size, hdferr) -! call h5tcreate_f(H5T_COMPOUND_F, type_size, dtype_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='HDF5_writeBackwardMapping: h5tcreate_f dtype_id') - -! call h5tinsert_f(dtype_id, "Position", 0_SIZE_T, H5T_STD_I32LE, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5tinsert_f 0') - -!!-------------------------------------------------------------------------------------------------- -! ! create Dataset -! call h5dcreate_f(mapping_id, 'mapGeometry', dtype_id, space_id, dset_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog') - -!!-------------------------------------------------------------------------------------------------- -! ! Create memory types (one compound datatype for each member) -! call h5tcreate_f(H5T_COMPOUND_F, int(pInt,SIZE_T), position_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5tcreate_f position_id') -! call h5tinsert_f(position_id, "Position", 0_SIZE_T, H5T_STD_I32LE, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5tinsert_f position_id') - -!!-------------------------------------------------------------------------------------------------- -! ! Define and select hyperslabs -! counter = NmatPoints ! how big i am -! fileOffset = mpiOffset_homog(i) ! where i start to write my data - -! call h5screate_simple_f(1, counter, memspace, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5screate_simple_f') -! call h5dget_space_f(dset_id, space_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5dget_space_f') -! call h5sselect_hyperslab_f(space_id, H5S_SELECT_SET_F, fileOffset, counter, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5sselect_hyperslab_f') - -!!-------------------------------------------------------------------------------------------------- -! ! Create property list for collective dataset write -!#ifdef PETSc -! call h5pcreate_f(H5P_DATASET_XFER_F, plist_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5pcreate_f') -! call h5pset_dxpl_mpio_f(plist_id, H5FD_MPIO_COLLECTIVE_F, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5pset_dxpl_mpio_f') -!#endif - -!!-------------------------------------------------------------------------------------------------- -! ! write data by fields in the datatype. Fields order is not important. -! call h5dwrite_f(dset_id, position_id, pack(arr(1,:),arr(2,:)==i)+mpiOffset,int([dataspace_size(i)],HSIZE_T),& -! hdferr, file_space_id = space_id, mem_space_id = memspace, xfer_prp = plist_id) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5dwrite_f instance_id') - -!!-------------------------------------------------------------------------------------------------- -! !close types, dataspaces -! call h5tclose_f(dtype_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5tclose_f dtype_id') -! call h5tclose_f(position_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5tclose_f position_id') -! call h5dclose_f(dset_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5dclose_f') -! call h5sclose_f(space_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5sclose_f space_id') -! call h5sclose_f(memspace, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5sclose_f memspace') -! call h5pclose_f(plist_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_backwardMappingHomog: h5pclose_f') -! call HDF5_closeGroup(mapping_ID) - -! enddo - -!end subroutine HDF5_backwardMappingHomog - - -!!-------------------------------------------------------------------------------------------------- -!!> @brief adds the unique cell to node mapping -!!-------------------------------------------------------------------------------------------------- -!subroutine HDF5_mappingCells(mapping) - -! integer(pInt), intent(in), dimension(:) :: mapping - -! integer :: hdferr, Nnodes -! integer(HID_T) :: mapping_id, dset_id, space_id - -! Nnodes=size(mapping) -! mapping_ID = results_openGroup("mapping") - -!!-------------------------------------------------------------------------------------------------- -!! create dataspace -! call h5screate_simple_f(1, int([Nnodes],HSIZE_T), space_id, hdferr, & -! int([Nnodes],HSIZE_T)) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_mappingCells: h5screate_simple_f') - -!!-------------------------------------------------------------------------------------------------- -!! create Dataset -! call h5dcreate_f(mapping_id, "Cell",H5T_NATIVE_INTEGER, space_id, dset_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_mappingCells') - -!!-------------------------------------------------------------------------------------------------- -!! write data by fields in the datatype. Fields order is not important. -! call h5dwrite_f(dset_id, H5T_NATIVE_INTEGER, mapping, int([Nnodes],HSIZE_T), hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_mappingCells: h5dwrite_f instance_id') - -!!-------------------------------------------------------------------------------------------------- -!!close types, dataspaces -! call h5dclose_f(dset_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_mappingConstitutive: h5dclose_f') -! call h5sclose_f(space_id, hdferr) -! if (hdferr < 0) call IO_error(1,ext_msg='IO_mappingConstitutive: h5sclose_f') -! call HDF5_closeGroup(mapping_ID) - -!end subroutine HDF5_mappingCells - end module results diff --git a/src/rotations.f90 b/src/rotations.f90 index ea4a8a9d8..888e73762 100644 --- a/src/rotations.f90 +++ b/src/rotations.f90 @@ -640,13 +640,13 @@ function om2ax(om) result(ax) ax(1:3) = [ 0.0_pReal, 0.0_pReal, 1.0_pReal ] else call dgeev('N','V',3,om_,3,Wr,Wi,devNull,3,VR,3,work,size(work,1),ierr) - if (ierr /= 0) call IO_error(401,ext_msg='Error in om2ax: DGEEV return not zero') + if (ierr /= 0) error stop 'LAPACK error' #if defined(__GFORTRAN__) && __GNUC__<9 || defined(__INTEL_COMPILER) && INTEL_COMPILER<1800 || defined(__PGI) i = maxloc(merge(1,0,cEq(cmplx(Wr,Wi,pReal),cmplx(1.0_pReal,0.0_pReal,pReal),tol=1.0e-14_pReal)),dim=1) #else i = findloc(cEq(cmplx(Wr,Wi,pReal),cmplx(1.0_pReal,0.0_pReal,pReal),tol=1.0e-14_pReal),.true.,dim=1) !find eigenvalue (1,0) #endif - if (i == 0) call IO_error(401,ext_msg='Error in om2ax Real: eigenvalue not found') + if (i == 0) error stop 'om2ax conversion failed' ax(1:3) = VR(1:3,i) where ( dNeq0([om(2,3)-om(3,2), om(3,1)-om(1,3), om(1,2)-om(2,1)])) & ax(1:3) = sign(ax(1:3),-P *[om(2,3)-om(3,2), om(3,1)-om(1,3), om(1,2)-om(2,1)]) diff --git a/src/source_damage_anisoBrittle.f90 b/src/source_damage_anisoBrittle.f90 index 1954e6ea3..ca8d6ec2b 100644 --- a/src/source_damage_anisoBrittle.f90 +++ b/src/source_damage_anisoBrittle.f90 @@ -25,7 +25,7 @@ submodule (constitutive:constitutive_damage) source_damage_anisoBrittle output end type tParameters - type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstance) + type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstances) contains @@ -45,19 +45,19 @@ module function source_damage_anisoBrittle_init(source_length) result(mySources) phase, & sources, & src - integer :: Ninstance,sourceOffset,NipcMyPhase,p + integer :: Ninstances,sourceOffset,Nconstituents,p integer, dimension(:), allocatable :: N_cl character(len=pStringLen) :: extmsg = '' print'(/,a)', ' <<<+- source_damage_anisoBrittle init -+>>>' mySources = source_active('damage_anisoBrittle',source_length) - Ninstance = count(mySources) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(mySources) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return phases => config_material%get('phase') - allocate(param(Ninstance)) + allocate(param(Ninstances)) allocate(source_damage_anisoBrittle_offset (phases%length), source=0) allocate(source_damage_anisoBrittle_instance(phases%length), source=0) @@ -100,8 +100,8 @@ module function source_damage_anisoBrittle_init(source_length) result(mySources) if (any(prm%g_crit < 0.0_pReal)) extmsg = trim(extmsg)//' g_crit' if (any(prm%s_crit < 0.0_pReal)) extmsg = trim(extmsg)//' s_crit' - NipcMyPhase = count(material_phaseAt==p) * discretization_nIP - call constitutive_allocateState(sourceState(p)%p(sourceOffset),NipcMyPhase,1,1,0) + Nconstituents = count(material_phaseAt==p) * discretization_nIPs + call constitutive_allocateState(sourceState(p)%p(sourceOffset),Nconstituents,1,1,0) sourceState(p)%p(sourceOffset)%atol = src%get_asFloat('anisobrittle_atol',defaultVal=1.0e-3_pReal) if(any(sourceState(p)%p(sourceOffset)%atol < 0.0_pReal)) extmsg = trim(extmsg)//' anisobrittle_atol' diff --git a/src/source_damage_anisoDuctile.f90 b/src/source_damage_anisoDuctile.f90 index 3b8eb2428..601ec2531 100644 --- a/src/source_damage_anisoDuctile.f90 +++ b/src/source_damage_anisoDuctile.f90 @@ -19,7 +19,7 @@ submodule(constitutive:constitutive_damage) source_damage_anisoDuctile output end type tParameters - type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstance) + type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstances) contains @@ -36,31 +36,33 @@ module function source_damage_anisoDuctile_init(source_length) result(mySources) class(tNode), pointer :: & phases, & phase, & + mech, & pl, & sources, & src - integer :: Ninstance,sourceOffset,NipcMyPhase,p + integer :: Ninstances,sourceOffset,Nconstituents,p integer, dimension(:), allocatable :: N_sl character(len=pStringLen) :: extmsg = '' print'(/,a)', ' <<<+- source_damage_anisoDuctile init -+>>>' mySources = source_active('damage_anisoDuctile',source_length) - Ninstance = count(mySources) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(mySources) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return phases => config_material%get('phase') - allocate(param(Ninstance)) + allocate(param(Ninstances)) allocate(source_damage_anisoDuctile_offset (phases%length), source=0) allocate(source_damage_anisoDuctile_instance(phases%length), source=0) do p = 1, phases%length - phase => phases%get(p) + phase => phases%get(p) if(any(mySources(:,p))) source_damage_anisoDuctile_instance(p) = count(mySources(:,1:p)) if(count(mySources(:,p)) == 0) cycle + mech => phase%get('mechanics') + pl => mech%get('plasticity') sources => phase%get('source') - pl => phase%get('plasticity') do sourceOffset = 1, sources%length if(mySources(sourceOffset,p)) then source_damage_anisoDuctile_offset(p) = sourceOffset @@ -84,8 +86,8 @@ module function source_damage_anisoDuctile_init(source_length) result(mySources) if (prm%q <= 0.0_pReal) extmsg = trim(extmsg)//' q' if (any(prm%gamma_crit < 0.0_pReal)) extmsg = trim(extmsg)//' gamma_crit' - NipcMyPhase=count(material_phaseAt==p) * discretization_nIP - call constitutive_allocateState(sourceState(p)%p(sourceOffset),NipcMyPhase,1,1,0) + Nconstituents=count(material_phaseAt==p) * discretization_nIPs + call constitutive_allocateState(sourceState(p)%p(sourceOffset),Nconstituents,1,1,0) sourceState(p)%p(sourceOffset)%atol = src%get_asFloat('anisoDuctile_atol',defaultVal=1.0e-3_pReal) if(any(sourceState(p)%p(sourceOffset)%atol < 0.0_pReal)) extmsg = trim(extmsg)//' anisoductile_atol' diff --git a/src/source_damage_isoBrittle.f90 b/src/source_damage_isoBrittle.f90 index 714e71ef1..7fcf17ee0 100644 --- a/src/source_damage_isoBrittle.f90 +++ b/src/source_damage_isoBrittle.f90 @@ -17,7 +17,7 @@ submodule(constitutive:constitutive_damage) source_damage_isoBrittle output end type tParameters - type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstance) + type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstances) contains @@ -36,18 +36,18 @@ module function source_damage_isoBrittle_init(source_length) result(mySources) phase, & sources, & src - integer :: Ninstance,sourceOffset,NipcMyPhase,p + integer :: Ninstances,sourceOffset,Nconstituents,p character(len=pStringLen) :: extmsg = '' print'(/,a)', ' <<<+- source_damage_isoBrittle init -+>>>' mySources = source_active('damage_isoBrittle',source_length) - Ninstance = count(mySources) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(mySources) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return phases => config_material%get('phase') - allocate(param(Ninstance)) + allocate(param(Ninstances)) allocate(source_damage_isoBrittle_offset (phases%length), source=0) allocate(source_damage_isoBrittle_instance(phases%length), source=0) @@ -73,8 +73,8 @@ module function source_damage_isoBrittle_init(source_length) result(mySources) ! sanity checks if (prm%W_crit <= 0.0_pReal) extmsg = trim(extmsg)//' W_crit' - NipcMyPhase = count(material_phaseAt==p) * discretization_nIP - call constitutive_allocateState(sourceState(p)%p(sourceOffset),NipcMyPhase,1,1,1) + Nconstituents = count(material_phaseAt==p) * discretization_nIPs + call constitutive_allocateState(sourceState(p)%p(sourceOffset),Nconstituents,1,1,1) sourceState(p)%p(sourceOffset)%atol = src%get_asFloat('isoBrittle_atol',defaultVal=1.0e-3_pReal) if(any(sourceState(p)%p(sourceOffset)%atol < 0.0_pReal)) extmsg = trim(extmsg)//' isobrittle_atol' diff --git a/src/source_damage_isoDuctile.f90 b/src/source_damage_isoDuctile.f90 index d958aed6a..1bff20570 100644 --- a/src/source_damage_isoDuctile.f90 +++ b/src/source_damage_isoDuctile.f90 @@ -18,7 +18,7 @@ submodule (constitutive:constitutive_damage) source_damage_isoDuctile output end type tParameters - type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstance) + type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstances) contains @@ -38,18 +38,18 @@ module function source_damage_isoDuctile_init(source_length) result(mySources) phase, & sources, & src - integer :: Ninstance,sourceOffset,NipcMyPhase,p + integer :: Ninstances,sourceOffset,Nconstituents,p character(len=pStringLen) :: extmsg = '' print'(/,a)', ' <<<+- source_damage_isoDuctile init -+>>>' mySources = source_active('damage_isoDuctile',source_length) - Ninstance = count(mySources) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(mySources) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return phases => config_material%get('phase') - allocate(param(Ninstance)) + allocate(param(Ninstances)) allocate(source_damage_isoDuctile_offset (phases%length), source=0) allocate(source_damage_isoDuctile_instance(phases%length), source=0) @@ -77,8 +77,8 @@ module function source_damage_isoDuctile_init(source_length) result(mySources) if (prm%q <= 0.0_pReal) extmsg = trim(extmsg)//' q' if (prm%gamma_crit <= 0.0_pReal) extmsg = trim(extmsg)//' gamma_crit' - NipcMyPhase=count(material_phaseAt==p) * discretization_nIP - call constitutive_allocateState(sourceState(p)%p(sourceOffset),NipcMyPhase,1,1,0) + Nconstituents=count(material_phaseAt==p) * discretization_nIPs + call constitutive_allocateState(sourceState(p)%p(sourceOffset),Nconstituents,1,1,0) sourceState(p)%p(sourceOffset)%atol = src%get_asFloat('isoDuctile_atol',defaultVal=1.0e-3_pReal) if(any(sourceState(p)%p(sourceOffset)%atol < 0.0_pReal)) extmsg = trim(extmsg)//' isoductile_atol' diff --git a/src/source_thermal_dissipation.f90 b/src/source_thermal_dissipation.f90 index 5cc740424..f28567aa7 100644 --- a/src/source_thermal_dissipation.f90 +++ b/src/source_thermal_dissipation.f90 @@ -15,7 +15,7 @@ submodule(constitutive:constitutive_thermal) source_thermal_dissipation kappa !< TAYLOR-QUINNEY factor end type tParameters - type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstance) + type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstances) contains @@ -35,17 +35,17 @@ module function source_thermal_dissipation_init(source_length) result(mySources) phase, & sources, & src - integer :: Ninstance,sourceOffset,NipcMyPhase,p + integer :: Ninstances,sourceOffset,Nconstituents,p print'(/,a)', ' <<<+- source_thermal_dissipation init -+>>>' mySources = source_active('thermal_dissipation',source_length) - Ninstance = count(mySources) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(mySources) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return phases => config_material%get('phase') - allocate(param(Ninstance)) + allocate(param(Ninstances)) allocate(source_thermal_dissipation_offset (phases%length), source=0) allocate(source_thermal_dissipation_instance(phases%length), source=0) @@ -61,8 +61,8 @@ module function source_thermal_dissipation_init(source_length) result(mySources) src => sources%get(sourceOffset) prm%kappa = src%get_asFloat('kappa') - NipcMyPhase = count(material_phaseAt==p) * discretization_nIP - call constitutive_allocateState(sourceState(p)%p(sourceOffset),NipcMyPhase,0,0,0) + Nconstituents = count(material_phaseAt==p) * discretization_nIPs + call constitutive_allocateState(sourceState(p)%p(sourceOffset),Nconstituents,0,0,0) end associate endif @@ -74,7 +74,7 @@ end function source_thermal_dissipation_init !-------------------------------------------------------------------------------------------------- -!> @brief Ninstances dissipation rate +!> @brief Ninstancess dissipation rate !-------------------------------------------------------------------------------------------------- module subroutine source_thermal_dissipation_getRateAndItsTangent(TDot, dTDot_dT, Tstar, Lp, phase) diff --git a/src/source_thermal_externalheat.f90 b/src/source_thermal_externalheat.f90 index 2eeeb47df..9ba4a051b 100644 --- a/src/source_thermal_externalheat.f90 +++ b/src/source_thermal_externalheat.f90 @@ -19,7 +19,7 @@ submodule(constitutive:constitutive_thermal) source_thermal_externalheat nIntervals end type tParameters - type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstance) + type(tParameters), dimension(:), allocatable :: param !< containers of constitutive parameters (len Ninstances) contains @@ -39,17 +39,17 @@ module function source_thermal_externalheat_init(source_length) result(mySources phase, & sources, & src - integer :: Ninstance,sourceOffset,NipcMyPhase,p + integer :: Ninstances,sourceOffset,Nconstituents,p print'(/,a)', ' <<<+- source_thermal_externalHeat init -+>>>' mySources = source_active('thermal_externalheat',source_length) - Ninstance = count(mySources) - print'(a,i2)', ' # instances: ',Ninstance; flush(IO_STDOUT) - if(Ninstance == 0) return + Ninstances = count(mySources) + print'(a,i2)', ' # instances: ',Ninstances; flush(IO_STDOUT) + if(Ninstances == 0) return phases => config_material%get('phase') - allocate(param(Ninstance)) + allocate(param(Ninstances)) allocate(source_thermal_externalheat_offset (phases%length), source=0) allocate(source_thermal_externalheat_instance(phases%length), source=0) @@ -69,8 +69,8 @@ module function source_thermal_externalheat_init(source_length) result(mySources prm%f_T = src%get_asFloats('f_T',requiredSize = size(prm%t_n)) - NipcMyPhase = count(material_phaseAt==p) * discretization_nIP - call constitutive_allocateState(sourceState(p)%p(sourceOffset),NipcMyPhase,1,1,0) + Nconstituents = count(material_phaseAt==p) * discretization_nIPs + call constitutive_allocateState(sourceState(p)%p(sourceOffset),Nconstituents,1,1,0) end associate endif diff --git a/src/system_routines.f90 b/src/system_routines.f90 index 9a2442163..309b96b7e 100644 --- a/src/system_routines.f90 +++ b/src/system_routines.f90 @@ -8,79 +8,65 @@ module system_routines use prec implicit none + private public :: & - signalterm_C, & - signalusr1_C, & - signalusr2_C, & - isDirectory, & + setCWD, & getCWD, & getHostName, & - setCWD + getUserName, & + signalterm_C, & + signalusr1_C, & + signalusr2_C + interface - function isDirectory_C(path) bind(C) - use, intrinsic :: ISO_C_Binding, only: & - C_INT, & - C_CHAR + function setCWD_C(cwd) bind(C) + use, intrinsic :: ISO_C_Binding, only: C_INT, C_CHAR + + integer(C_INT) :: setCWD_C + character(kind=C_CHAR), dimension(*), intent(in) :: cwd + end function setCWD_C + subroutine getCWD_C(cwd, stat) bind(C) + use, intrinsic :: ISO_C_Binding, only: C_INT, C_CHAR use prec - integer(C_INT) :: isDirectory_C - character(kind=C_CHAR), dimension(pPathLen), intent(in) :: path ! C string is an array - end function isDirectory_C - - subroutine getCurrentWorkDir_C(path, stat) bind(C) - use, intrinsic :: ISO_C_Binding, only: & - C_INT, & - C_CHAR - - use prec - - character(kind=C_CHAR), dimension(pPathLen), intent(out) :: path ! C string is an array - integer(C_INT), intent(out) :: stat - end subroutine getCurrentWorkDir_C - - subroutine getHostName_C(str, stat) bind(C) - use, intrinsic :: ISO_C_Binding, only: & - C_INT, & - C_CHAR - - use prec - - character(kind=C_CHAR), dimension(pStringLen), intent(out) :: str ! C string is an array + character(kind=C_CHAR), dimension(pPathLen+1), intent(out) :: cwd ! NULL-terminated array integer(C_INT), intent(out) :: stat + end subroutine getCWD_C + + subroutine getHostName_C(hostname, stat) bind(C) + use, intrinsic :: ISO_C_Binding, only: C_INT, C_CHAR + use prec + + character(kind=C_CHAR), dimension(pStringLen+1), intent(out) :: hostname ! NULL-terminated array + integer(C_INT), intent(out) :: stat end subroutine getHostName_C - function chdir_C(path) bind(C) - use, intrinsic :: ISO_C_Binding, only: & - C_INT, & - C_CHAR - + subroutine getUserName_C(username, stat) bind(C) + use, intrinsic :: ISO_C_Binding, only: C_INT, C_CHAR use prec - integer(C_INT) :: chdir_C - character(kind=C_CHAR), dimension(pPathLen), intent(in) :: path ! C string is an array - end function chdir_C + character(kind=C_CHAR), dimension(pStringLen+1), intent(out) :: username ! NULL-terminated array + integer(C_INT), intent(out) :: stat + end subroutine getUserName_C subroutine signalterm_C(handler) bind(C) - use, intrinsic :: ISO_C_Binding, only: & - C_FUNPTR + use, intrinsic :: ISO_C_Binding, only: C_FUNPTR type(C_FUNPTR), intent(in), value :: handler end subroutine signalterm_C subroutine signalusr1_C(handler) bind(C) - use, intrinsic :: ISO_C_Binding, only: & - C_FUNPTR + use, intrinsic :: ISO_C_Binding, only: C_FUNPTR type(C_FUNPTR), intent(in), value :: handler end subroutine signalusr1_C subroutine signalusr2_C(handler) bind(C) - use, intrinsic :: ISO_C_Binding, only: & - C_FUNPTR + use, intrinsic :: ISO_C_Binding, only: C_FUNPTR type(C_FUNPTR), intent(in), value :: handler end subroutine signalusr2_C @@ -89,45 +75,48 @@ module system_routines contains + !-------------------------------------------------------------------------------------------------- -!> @brief figures out if a given path is a directory (and not an ordinary file) +!> @brief set the current working directory !-------------------------------------------------------------------------------------------------- -logical function isDirectory(path) +logical function setCWD(path) character(len=*), intent(in) :: path - - isDirectory=merge(.True.,.False.,isDirectory_C(f_c_string(path)) /= 0_C_INT) -end function isDirectory + setCWD=merge(.True.,.False.,setCWD_C(f_c_string(path)) /= 0_C_INT) + +end function setCWD !-------------------------------------------------------------------------------------------------- -!> @brief gets the current working directory +!> @brief get the current working directory !-------------------------------------------------------------------------------------------------- function getCWD() - character(kind=C_CHAR), dimension(pPathLen) :: getCWD_Cstring - character(len=:), allocatable :: getCWD + character(len=:), allocatable :: getCWD + + character(kind=C_CHAR), dimension(pPathLen+1) :: getCWD_Cstring integer(C_INT) :: stat - call getCurrentWorkDir_C(getCWD_Cstring,stat) + call getCWD_C(getCWD_Cstring,stat) if(stat == 0) then getCWD = c_f_string(getCWD_Cstring) else - getCWD = 'Error occured when getting currend working directory' + error stop 'invalid working directory' endif end function getCWD !-------------------------------------------------------------------------------------------------- -!> @brief gets the current host name +!> @brief get the host name !-------------------------------------------------------------------------------------------------- function getHostName() - character(kind=C_CHAR), dimension(pPathLen) :: getHostName_Cstring - character(len=:), allocatable :: getHostName + character(len=:), allocatable :: getHostName + + character(kind=C_CHAR), dimension(pStringLen+1) :: getHostName_Cstring integer(C_INT) :: stat call getHostName_C(getHostName_Cstring,stat) @@ -135,22 +124,31 @@ function getHostName() if(stat == 0) then getHostName = c_f_string(getHostName_Cstring) else - getHostName = 'Error occured when getting host name' + getHostName = 'n/a (Error!)' endif end function getHostName !-------------------------------------------------------------------------------------------------- -!> @brief changes the current working directory +!> @brief get the user name !-------------------------------------------------------------------------------------------------- -logical function setCWD(path) +function getUserName() - character(len=*), intent(in) :: path + character(len=:), allocatable :: getUserName - setCWD=merge(.True.,.False.,chdir_C(f_c_string(path)) /= 0_C_INT) + character(kind=C_CHAR), dimension(pStringLen+1) :: getUserName_Cstring + integer(C_INT) :: stat -end function setCWD + call getUserName_C(getUserName_Cstring,stat) + + if(stat == 0) then + getUserName = c_f_string(getUserName_Cstring) + else + getUserName = 'n/a (Error!)' + endif + +end function getUserName !-------------------------------------------------------------------------------------------------- @@ -182,14 +180,14 @@ end function c_f_string !-------------------------------------------------------------------------------------------------- pure function f_c_string(f_string) result(c_string) - character(len=*), intent(in) :: f_string - character(kind=C_CHAR), dimension(len(f_string)+1) :: c_string + character(len=*), intent(in) :: f_string + character(kind=C_CHAR), dimension(len_trim(f_string)+1) :: c_string integer :: i - do i=1,len(f_string) + do i=1,len_trim(f_string) c_string(i)=f_string(i:i) enddo - c_string(i) = C_NULL_CHAR + c_string(len_trim(f_string)+1) = C_NULL_CHAR end function f_c_string diff --git a/src/thermal_adiabatic.f90 b/src/thermal_adiabatic.f90 index 63deb3cd5..aa807924c 100644 --- a/src/thermal_adiabatic.f90 +++ b/src/thermal_adiabatic.f90 @@ -40,7 +40,7 @@ contains !-------------------------------------------------------------------------------------------------- subroutine thermal_adiabatic_init - integer :: maxNinstance,h,NofMyHomog + integer :: maxNinstances,h,Nmaterialpoints class(tNode), pointer :: & material_homogenization, & homog, & @@ -48,13 +48,13 @@ subroutine thermal_adiabatic_init print'(/,a)', ' <<<+- thermal_adiabatic init -+>>>'; flush(6) - maxNinstance = count(thermal_type == THERMAL_adiabatic_ID) - if (maxNinstance == 0) return + maxNinstances = count(thermal_type == THERMAL_adiabatic_ID) + if (maxNinstances == 0) return - allocate(param(maxNinstance)) + allocate(param(maxNinstances)) material_homogenization => config_material%get('homogenization') - do h = 1, material_Nhomogenization + do h = 1, size(material_name_homogenization) if (thermal_type(h) /= THERMAL_adiabatic_ID) cycle homog => material_homogenization%get(h) homogThermal => homog%get('thermal') @@ -67,17 +67,17 @@ subroutine thermal_adiabatic_init prm%output = homogThermal%get_asStrings('output',defaultVal=emptyStringArray) #endif - NofMyHomog=count(material_homogenizationAt==h) + Nmaterialpoints=count(material_homogenizationAt==h) thermalState(h)%sizeState = 1 - allocate(thermalState(h)%state0 (1,NofMyHomog), source=thermal_initialT(h)) - allocate(thermalState(h)%subState0(1,NofMyHomog), source=thermal_initialT(h)) - allocate(thermalState(h)%state (1,NofMyHomog), source=thermal_initialT(h)) + allocate(thermalState(h)%state0 (1,Nmaterialpoints), source=thermal_initialT(h)) + allocate(thermalState(h)%subState0(1,Nmaterialpoints), source=thermal_initialT(h)) + allocate(thermalState(h)%state (1,Nmaterialpoints), source=thermal_initialT(h)) thermalMapping(h)%p => material_homogenizationMemberAt deallocate(temperature(h)%p) temperature(h)%p => thermalState(h)%state(1,:) deallocate(temperatureRate(h)%p) - allocate (temperatureRate(h)%p(NofMyHomog), source=0.0_pReal) + allocate (temperatureRate(h)%p(Nmaterialpoints), source=0.0_pReal) end associate enddo @@ -145,8 +145,8 @@ subroutine thermal_adiabatic_getSourceAndItsTangent(Tdot, dTdot_dT, T, ip, el) homog = material_homogenizationAt(el) call constitutive_thermal_getRateAndItsTangents(TDot, dTDot_dT, T, crystallite_S, crystallite_Lp, ip, el) - Tdot = Tdot/real(homogenization_Ngrains(homog),pReal) - dTdot_dT = dTdot_dT/real(homogenization_Ngrains(homog),pReal) + Tdot = Tdot/real(homogenization_Nconstituents(homog),pReal) + dTdot_dT = dTdot_dT/real(homogenization_Nconstituents(homog),pReal) end subroutine thermal_adiabatic_getSourceAndItsTangent @@ -167,13 +167,13 @@ function thermal_adiabatic_getSpecificHeat(ip,el) thermal_adiabatic_getSpecificHeat = 0.0_pReal - do grain = 1, homogenization_Ngrains(material_homogenizationAt(el)) + do grain = 1, homogenization_Nconstituents(material_homogenizationAt(el)) thermal_adiabatic_getSpecificHeat = thermal_adiabatic_getSpecificHeat & + lattice_c_p(material_phaseAt(grain,el)) enddo thermal_adiabatic_getSpecificHeat = thermal_adiabatic_getSpecificHeat & - / real(homogenization_Ngrains(material_homogenizationAt(el)),pReal) + / real(homogenization_Nconstituents(material_homogenizationAt(el)),pReal) end function thermal_adiabatic_getSpecificHeat @@ -193,13 +193,13 @@ function thermal_adiabatic_getMassDensity(ip,el) thermal_adiabatic_getMassDensity = 0.0_pReal - do grain = 1, homogenization_Ngrains(material_homogenizationAt(el)) + do grain = 1, homogenization_Nconstituents(material_homogenizationAt(el)) thermal_adiabatic_getMassDensity = thermal_adiabatic_getMassDensity & + lattice_rho(material_phaseAt(grain,el)) enddo thermal_adiabatic_getMassDensity = thermal_adiabatic_getMassDensity & - / real(homogenization_Ngrains(material_homogenizationAt(el)),pReal) + / real(homogenization_Nconstituents(material_homogenizationAt(el)),pReal) end function thermal_adiabatic_getMassDensity diff --git a/src/thermal_conduction.f90 b/src/thermal_conduction.f90 index 69ce8025e..daa7391a9 100644 --- a/src/thermal_conduction.f90 +++ b/src/thermal_conduction.f90 @@ -41,7 +41,7 @@ contains !-------------------------------------------------------------------------------------------------- subroutine thermal_conduction_init - integer :: Ninstance,NofMyHomog,h + integer :: Ninstances,Nmaterialpoints,h class(tNode), pointer :: & material_homogenization, & homog, & @@ -49,11 +49,11 @@ subroutine thermal_conduction_init print'(/,a)', ' <<<+- thermal_conduction init -+>>>'; flush(6) - Ninstance = count(thermal_type == THERMAL_conduction_ID) - allocate(param(Ninstance)) + Ninstances = count(thermal_type == THERMAL_conduction_ID) + allocate(param(Ninstances)) material_homogenization => config_material%get('homogenization') - do h = 1, material_Nhomogenization + do h = 1, size(material_name_homogenization) if (thermal_type(h) /= THERMAL_conduction_ID) cycle homog => material_homogenization%get(h) homogThermal => homog%get('thermal') @@ -65,17 +65,17 @@ subroutine thermal_conduction_init prm%output = homogThermal%get_asStrings('output',defaultVal=emptyStringArray) #endif - NofMyHomog=count(material_homogenizationAt==h) + Nmaterialpoints=count(material_homogenizationAt==h) thermalState(h)%sizeState = 0 - allocate(thermalState(h)%state0 (0,NofMyHomog)) - allocate(thermalState(h)%subState0(0,NofMyHomog)) - allocate(thermalState(h)%state (0,NofMyHomog)) + allocate(thermalState(h)%state0 (0,Nmaterialpoints)) + allocate(thermalState(h)%subState0(0,Nmaterialpoints)) + allocate(thermalState(h)%state (0,Nmaterialpoints)) thermalMapping(h)%p => material_homogenizationMemberAt deallocate(temperature (h)%p) - allocate (temperature (h)%p(NofMyHomog), source=thermal_initialT(h)) + allocate (temperature (h)%p(Nmaterialpoints), source=thermal_initialT(h)) deallocate(temperatureRate(h)%p) - allocate (temperatureRate(h)%p(NofMyHomog), source=0.0_pReal) + allocate (temperatureRate(h)%p(Nmaterialpoints), source=0.0_pReal) end associate enddo @@ -104,8 +104,8 @@ subroutine thermal_conduction_getSourceAndItsTangent(Tdot, dTdot_dT, T, ip, el) homog = material_homogenizationAt(el) call constitutive_thermal_getRateAndItsTangents(TDot, dTDot_dT, T, crystallite_S,crystallite_Lp ,ip, el) - Tdot = Tdot/real(homogenization_Ngrains(homog),pReal) - dTdot_dT = dTdot_dT/real(homogenization_Ngrains(homog),pReal) + Tdot = Tdot/real(homogenization_Nconstituents(homog),pReal) + dTdot_dT = dTdot_dT/real(homogenization_Nconstituents(homog),pReal) end subroutine thermal_conduction_getSourceAndItsTangent @@ -125,13 +125,13 @@ function thermal_conduction_getConductivity(ip,el) thermal_conduction_getConductivity = 0.0_pReal - do grain = 1, homogenization_Ngrains(material_homogenizationAt(el)) + do grain = 1, homogenization_Nconstituents(material_homogenizationAt(el)) thermal_conduction_getConductivity = thermal_conduction_getConductivity + & crystallite_push33ToRef(grain,ip,el,lattice_K(:,:,material_phaseAt(grain,el))) enddo thermal_conduction_getConductivity = thermal_conduction_getConductivity & - / real(homogenization_Ngrains(material_homogenizationAt(el)),pReal) + / real(homogenization_Nconstituents(material_homogenizationAt(el)),pReal) end function thermal_conduction_getConductivity @@ -151,13 +151,13 @@ function thermal_conduction_getSpecificHeat(ip,el) thermal_conduction_getSpecificHeat = 0.0_pReal - do grain = 1, homogenization_Ngrains(material_homogenizationAt(el)) + do grain = 1, homogenization_Nconstituents(material_homogenizationAt(el)) thermal_conduction_getSpecificHeat = thermal_conduction_getSpecificHeat & + lattice_c_p(material_phaseAt(grain,el)) enddo thermal_conduction_getSpecificHeat = thermal_conduction_getSpecificHeat & - / real(homogenization_Ngrains(material_homogenizationAt(el)),pReal) + / real(homogenization_Nconstituents(material_homogenizationAt(el)),pReal) end function thermal_conduction_getSpecificHeat @@ -178,13 +178,13 @@ function thermal_conduction_getMassDensity(ip,el) thermal_conduction_getMassDensity = 0.0_pReal - do grain = 1, homogenization_Ngrains(material_homogenizationAt(el)) + do grain = 1, homogenization_Nconstituents(material_homogenizationAt(el)) thermal_conduction_getMassDensity = thermal_conduction_getMassDensity & + lattice_rho(material_phaseAt(grain,el)) enddo thermal_conduction_getMassDensity = thermal_conduction_getMassDensity & - / real(homogenization_Ngrains(material_homogenizationAt(el)),pReal) + / real(homogenization_Nconstituents(material_homogenizationAt(el)),pReal) end function thermal_conduction_getMassDensity diff --git a/src/thermal_isothermal.f90 b/src/thermal_isothermal.f90 index 2dda358ac..39c8efe91 100644 --- a/src/thermal_isothermal.f90 +++ b/src/thermal_isothermal.f90 @@ -16,18 +16,18 @@ contains !-------------------------------------------------------------------------------------------------- subroutine thermal_isothermal_init - integer :: h,NofMyHomog + integer :: h,Nmaterialpoints print'(/,a)', ' <<<+- thermal_isothermal init -+>>>'; flush(6) - do h = 1, material_Nhomogenization + do h = 1, size(material_name_homogenization) if (thermal_type(h) /= THERMAL_isothermal_ID) cycle - NofMyHomog = count(material_homogenizationAt == h) + Nmaterialpoints = count(material_homogenizationAt == h) thermalState(h)%sizeState = 0 - allocate(thermalState(h)%state0 (0,NofMyHomog)) - allocate(thermalState(h)%subState0(0,NofMyHomog)) - allocate(thermalState(h)%state (0,NofMyHomog)) + allocate(thermalState(h)%state0 (0,Nmaterialpoints)) + allocate(thermalState(h)%subState0(0,Nmaterialpoints)) + allocate(thermalState(h)%state (0,Nmaterialpoints)) deallocate(temperature (h)%p) allocate (temperature (h)%p(1), source=thermal_initialT(h))