diff --git a/.gitignore b/.gitignore index 78283c1cc0d..e2970790b34 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ toolchain/abacus_env.sh .trae .codex compile_commands.json +tests/integrate/general_info.local diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index c53846664c5..e86dd959ca2 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -396,7 +396,7 @@ The integrate test is a test suite for testing the whole ABACUS package. The exa - For PW calculations, should set `pw_seed 1` in INPUT file to ensure the reproducibility of the test. 3. Generate the reference results for the new test. - Run the new test with GNU compiler and 4 MPI processes 2 OpenMP threads. The command is `OMP_NUM_THREADS=2 mpirun -np 4 abacus > log.txt`. - - Execute tests/integrate/tools/catch_properties.sh script to generate the reference results. At the new test directory, run `bash ../tools/catch_properties.sh result.ref`. + - Execute tests/integrate/validation_tools/catch_properties.sh script to generate the reference results. At the new test directory, run `bash ../../integrate/validation_tools/catch_properties.sh result.ref`. A `result.ref` file may be like: ```text etotref -3439.007931317310 @@ -410,7 +410,7 @@ The integrate test is a test suite for testing the whole ABACUS package. The exa if ! test -z "$has_band" && [ $has_band == 1 ]; then # if band is outputed, then check if the band is correct bandref=refBANDS_1.dat # this file should be prepared in new test directory bandcal=OUT.autotest/BANDS_1.dat # this file is generated by each run of test - python3 ../tools/CompareFile.py $bandref $bandcal 8 # compare the new band file with the reference file + python3 ../../integrate/validation_tools/CompareFile.py $bandref $bandcal 8 # compare the new band file with the reference file echo "CompareBand_pass $?" >>$1 # record the comparison result, $? is the return value of last command fi ``` diff --git a/source/source_base/test/mathzone_add1_test.cpp b/source/source_base/test/mathzone_add1_test.cpp index 91b8135d4dc..deec07de514 100644 --- a/source/source_base/test/mathzone_add1_test.cpp +++ b/source/source_base/test/mathzone_add1_test.cpp @@ -69,7 +69,7 @@ TEST_F(MathzoneAdd1Test, Constructor) /// first kind boundary condition: f'(0) = f'(n) = 0.0 TEST_F(MathzoneAdd1Test, CubicSplineBoundary1) { - // data from abacus/tests/integrate/tools/PP_ORB/Si_gga_8au_60Ry_2s2p1d.orb + // data from abacus/tests/PP_ORB/Si_gga_8au_60Ry_2s2p1d.orb // data for d orbital of Si : L = 2, N = 0 psi_in[0] = 0; psi_in[1] = -2.583946346740e-01; @@ -232,7 +232,7 @@ TEST_F(MathzoneAdd1Test, expx) /// second kind boundary condition: f''(0) = f''(n) = 0.0 TEST_F(MathzoneAdd1Test, CubicSplineBoundary2) { - // data from abacus/tests/integrate/tools/PP_ORB/Si_gga_8au_60Ry_2s2p1d.orb + // data from abacus/tests/PP_ORB/Si_gga_8au_60Ry_2s2p1d.orb // data for 1st p orbital of Si: L = 1, N= 0 psi_in[0] = 0; psi_in[1] = 2.023466616834e-01; @@ -279,7 +279,7 @@ TEST_F(MathzoneAdd1Test, CubicSplineBoundary2) TEST_F(MathzoneAdd1Test, UniRadialF) { - // data from abacus/tests/integrate/tools/PP_ORB/Si_gga_8au_60Ry_2s2p1d.orb + // data from abacus/tests/PP_ORB/Si_gga_8au_60Ry_2s2p1d.orb // data for 1st p orbital of Si: L = 1, N= 0 psi_in[0] = 0; psi_in[1] = 2.023466616834e-01; diff --git a/tests/README b/tests/README deleted file mode 100644 index 490cf91b182..00000000000 --- a/tests/README +++ /dev/null @@ -1,151 +0,0 @@ --------------------------------------- - Purposes for this test directory is: --------------------------------------- -1, cover most features of ABACUS -2, autotest script to check if the version is correct. - (reference result calculated by one core and saved in result.ref) - (user can change NUMBEROFPROCESS value in integrate/general_info to test by multi cores) - --------------------------------------- - Folders in this directory --------------------------------------- -01_PW: KSDFT calculations in PW basis with multple k-point setting. -02_NAO_Gamma: KSDFT calculations in NAO basis with gamma-only k-point setting. -03_NAO_multik: KSDFT calculations in NAO basis with multiple k-point setting. -04_FF: Force fields, including Lennard-Jones potentials, Deep Potentials and Neuroevolution Potential. -05_rtTDDFT: Real-time TDDFT tests. -06_SDFT: Stochastic DFT tests. -07_OFDFT: Orbital-free DFT tests. -08_RI: Resolution-of-identity (RI) based tests, including hybrid functionals, LR-TDDFT, RPA and BSE. -09_DeePKS: DeePKS tests. -10_others: Other tests such as LCAO in pw. -11_PW_GPU: KSDFT calculations in PW basis with multple k-point setting using GPU. -12_NAO_Gamma_GPU: KSDFT calculations in NAO basis with gamma-only k-point setting using GPU. -13_NAO_multik_GPU: KSDFT calculations in NAO basis with multiple k-point setting using GPU. -15_rtTDDFT_GPU: Real-time TDDFT tests using LCAO basis running on GPU. -16_SDFT_GPU: Integrate tests for stochastic DFT running on GPU. -CMakeLists.txt: Cmake file for this directory. -integrate: Store acripts for integrte tests. -libxc: Examples related to LibXC, will be refactored soon. -performance: Examples related to performance of ABACUS, will be refactored soon. -PP_ORB: Collection of all the used pseudopotentials and numerical atomic orbitals. -README: This file - - --------------------------------------- - How to run tests --------------------------------------- - -1. Modify the file '/integrate/general_info' and set appropriate parameters. -2. Set the appropriate 'abacus' in the Autotst.sh. -3. Enter each integrate test directory, try this script for autotests -../integrate/Autotest.sh -4. if you want to focus on No.xxx example, such as 101_PW_OU: -cd 101_PW_OU and then type -./../integrate/Single.sh $parameter -# you can choose $parameter among "" (empty), debug or ref -# 'ref': generate result.ref file (the answer you need) - - --------------------------------------- - How to modify an existing test case --------------------------------------- - -When merging or renaming a test case: - -1. Move/rename the directory: `git mv ` -2. Update the INPUT file with new parameters (keep the original style) -3. Update README to describe the new test purpose -4. Update `CASES_CPU.txt` and `CASES_GPU.txt`: remove old name, add new name -5. Remove the old directory: `git rm -rf ` -6. Regenerate `result.ref`: `OMP_NUM_THREADS=1 ../../integrate/Single_job.sh ref` -7. Commit the changes - -For smearing tests: insulators (e.g., H2) can accept any smearing method without changing the result. Metals require smearing for convergence. - -Marking unimplemented features: -- If a feature is not yet implemented (e.g., a certain code path, GPU backend, or functional lacks stress/force support), add the corresponding INPUT parameter but comment it out with an explanation, e.g.: - - `#cal_stress 1 # implicit solvation stress is not implemented yet` - - This documents the intended coverage, makes the gap visible to readers, and lets the test be enabled later by simply uncommenting once the feature lands. - --------------------------------------- - Some comman names appear in the tests --------------------------------------- -_PW plain wave bases -_NO numerical atoms orbitals bases -_NP expand numerical atoms orbitals in plane-wave basis - -_OU old upf pseudopotential file -_15 SG15 pseudopotential file -_VW vwr pseudopotential file -_DJ DOJO pseudopotential file - -_FD smearing methodL Fermi-dirac -_FX smearing method: Fixed occupations -_M2 smearing method: mp2 -_MP smearing method: Methfessel-Paxton (MP) -_MV smearing method: Marzari-Vanderbilt -_SG smearing method: Gaussian - -_SY turn on symmetry -_CG cg diagonalization method -_DA david diagonalization method - -_S1 one spin channel -_S2 two spin channels -_S4 four spin channels - -_GE genelpa diagonalization method -_SC scalapack diagonalization method - -_RE relax calculation -_CR cell-relax calculation -_CF calculate and output force -_CS calculate and output stress -_MD molecular dynamics -_TD real-time TDDFT -_LR linear response TDDFT - -_OH output Halmitonian matrix -_OB output bands file -_OD output DOS file -_OW output wave functions -_OC output charge density -_OK output kinetic energy density - -_GO gamma_only method -_KP all K-Points method - -_FM ferromagnetic nspin=2 -_AF anti-ferromagnetic nspin=2 anti initial magnetism - -_PU DFT plus U -_BS BSSE - -_PL mixing_type plain mixing -_KK mixing_type kerker mixing -_PU mixing_type pulay mixing -_PK mixing_type pulay-kerker mixing -_BD mixing_type broyden mixing - -_SO spin orbit coupling (SOC) - -_XX EXX -_VD VDW (both d2 or d3) - -_MG move ions method: cg -_MF move ions method: FIRE -_MB move ions method: bfgs -_MT move ions method: bfgs_traj - -_1O first-order charge extrapolation -_2O second-order charge extrapolation - -Orbital free DFT properties - -_OF orbital free density functional theory (OFDFT) -_OP optimization method used in OFDFT -_KE kinetic energy functional used in OFDFT -_CO convergence check diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000000..c46d9689e57 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,220 @@ +# ABACUS tests + +## Purpose + +The purposes for this test directory are: + +1. Cover most features of ABACUS. +2. Provide an autotest script to check if the version is correct. + (Reference results are calculated by one core and saved in `result.ref`. + You can change `NUMBEROFPROCESS` in `integrate/general_info` to test with + multiple cores, and set the executable once via + `export ABACUS_EXE=/path/to/abacus`.) + +## Folders in this directory + +| Folder | Description | +| --- | --- | +| `01_PW` | KSDFT calculations in PW basis with multiple k-point setting. | +| `02_NAO_Gamma` | KSDFT calculations in NAO basis with gamma-only k-point setting. | +| `03_NAO_multik` | KSDFT calculations in NAO basis with multiple k-point setting. | +| `04_FF` | Force fields, including Lennard-Jones potentials, Deep Potentials and Neuroevolution Potential. | +| `05_rtTDDFT` | Real-time TDDFT tests. | +| `06_SDFT` | Stochastic DFT tests. | +| `07_OFDFT` | Orbital-free DFT tests. | +| `08_RI` | Resolution-of-identity (RI) based tests, including hybrid functionals, LR-TDDFT, RPA and BSE. | +| `09_DeePKS` | DeePKS tests. | +| `10_others` | Other tests such as LCAO in pw. | +| `11_PW_GPU` | KSDFT calculations in PW basis with multiple k-point setting using GPU. | +| `12_NAO_Gamma_GPU` | KSDFT calculations in NAO basis with gamma-only k-point setting using GPU. | +| `13_NAO_multik_GPU` | KSDFT calculations in NAO basis with multiple k-point setting using GPU. | +| `15_rtTDDFT_GPU` | Real-time TDDFT tests using LCAO basis running on GPU. | +| `16_SDFT_GPU` | Integrate tests for stochastic DFT running on GPU. | +| `CMakeLists.txt` | CMake file for this directory. | +| `integrate` | Stores scripts for integrate tests. | +| `libxc` | Examples related to LibXC, will be refactored soon. | +| `performance` | Examples related to performance of ABACUS, will be refactored soon. | +| `PP_ORB` | Collection of all the used pseudopotentials and numerical atomic orbitals. | +| `README.md` | This file. | + +## `CASES_CPU.txt` / `CASES_GPU.txt` + +Each category directory (`01_PW`, `02_NAO_Gamma`, ...) contains its own +`CASES_CPU.txt` and/or `CASES_GPU.txt`. These are plain lists of the sub-case +names (the test directories) to run, one per line: + +- `CASES_CPU.txt`: cases run on CPU builds. +- `CASES_GPU.txt`: cases run on GPU builds. + +`Autotest.sh` reads the list from the file given by `-f ` (default +`CASES_CPU.txt` in the current working directory) and runs each named case. +Ctest invokes `Autotest.sh` per category directory with the appropriate `-f` +file, e.g. `tests/11_PW_GPU/CMakeLists.txt` uses `-f CASES_GPU.txt`. + +When you add/rename a test case, add/remove its directory name in the relevant +`CASES_*.txt` of its category, otherwise it will not be picked up. + +## How to run tests + +1. Set the ABACUS executable. The recommended way is a single environment + variable, which both `Autotest.sh` and `general_info` (via `run_check.sh`) + read: + + ```bash + export ABACUS_EXE=/path/to/abacus + ``` + + If `ABACUS_EXE` is not set, both fall back to the bare command name + `abacus`, which the shell resolves by searching the directories listed in + `$PATH` (run `which abacus` to see what it resolves to). This PATH fallback + is what CI uses after `cmake --install`. + + Ways to point at a specific executable, highest priority first: + + a) Command-line flag (`Autotest.sh` only): + + ```bash + ../integrate/Autotest.sh -a /path/to/abacus -n 4 + ``` + + Overrides everything for that invocation. `Single_job.sh`/`run_check.sh` + do not accept `-a`. + b) Environment variable (works for `Autotest.sh` and `run_check.sh`): + + ```bash + export ABACUS_EXE=/path/to/abacus + ``` + + Add it to `~/.bashrc` to make it persistent across sessions. + c) Local override file (`Autotest.sh` only; not tracked by git): + create `integrate/general_info.local` containing e.g. + + ```bash + abacus=/path/to/abacus + ``` + + `Autotest.sh` sources this file at startup when it exists, so the path + survives new shells while `git status` stays clean. + d) Edit the files directly (hard-coded; do NOT commit a local absolute path): + - `integrate/Autotest.sh`: set the `abacus` variable. + - `integrate/general_info`: set `EXEC` (supports `$VAR`/`${VAR}` + placeholders, expanded by `integrate/validation_tools/run_check.sh` + from the environment). + + Effective priority: `-a` flag > `ABACUS_EXE` > `general_info.local` > + hard-coded value in the files > `abacus` from PATH. + +2. Enter each integrate test directory, and run this script for autotests: + + ```bash + ../integrate/Autotest.sh + ``` + +3. If you want to focus on No.xxx example, such as `101_PW_OU`: + + ```bash + cd 101_PW_OU + ../../integrate/Single_job.sh $parameter + ``` + + You can choose `$parameter` among `""` (empty), `debug` or `ref`. + `ref`: generate `result.ref` file (the answer you need). + +## How to validate an integrate test case + +Each case directory contains a checked-in `result.ref`: a list of +"key value" lines (total energy, forces, stress, matrix-compare flags, ...). +Validation means running ABACUS in the case directory, collecting the same +lines from the fresh run into `result.out`, and comparing the two: + +- The collectors live in `integrate/validation_tools/`. The entry point is + `catch_properties.sh`; it sources `props_common.sh` (shared helpers and + INPUT parsing) and the per-category modules `props_basic.sh`, `props_mat.sh`, + `props_cube.sh`, `props_ml.sh`, `props_tddft.sh` and `props_deepks.sh`. Each + module defines `run__props()` hooks; add a new kind of check by + extending the matching module (or dropping in a new `props_.sh` and + calling its hook from `catch_properties.sh`). +- `CompareFile.py` compares numerical/text output files at a given digit + tolerance; `cube_tool.py` handles real-space `.cube` integration and + wave-function fingerprints. +- `totaltimeref` is the wall time of the run. It is recorded for information + only and is expected to differ between machines. + +Ways to validate, from a whole category down to one case: + +1. Whole category (what ctest/CI do), from the category directory + (e.g. `tests/01_PW`): + + ```bash + bash ../integrate/Autotest.sh -n 4 + ``` + + `Autotest.sh` runs every case in the category's `CASES_CPU.txt`, invokes the collectors to + build `result.out`, and compares it with `result.ref` using the thresholds + at the top of `Autotest.sh` (`threshold` / `force_threshold` / + `stress_threshold` / `descriptor_threshold`; a per-case `threshold` file + can override them). Useful flags: + + - `-r `: run only cases whose name matches `` + - `-f `: take the case list from `` (e.g. `CASES_GPU.txt`) + - `-n `: MPI ranks per case; `-o ` pins OpenMP threads + - `-j |auto`: number of cases to run concurrently + - `-g`: generate `result.ref` instead of checking + - `-a `: ABACUS executable for this invocation + + Example: + + ```bash + bash ../integrate/Autotest.sh -r 035_PW_15_SO + ``` + +2. One case by hand, from the case directory (e.g. `tests/01_PW/035_PW_15_SO`): + + ```bash + bash ../../integrate/Single_job.sh + ``` + + It reads `integrate/general_info` (`NUMBEROFPROCESS`, `CHECKACCURACY`, + `EXEC`), runs ABACUS, builds `result.out` through `run_check.sh` and prints + every line that disagrees with `result.ref`. With the `ref` argument it + regenerates `result.ref` instead; `debug` copies `run_check.sh` and the + whole `validation_tools/` directory into the case directory and collects + with that copy, so local edits to the collectors take effect. + +3. Collector only (ABACUS output already exists in `OUT.autotest/`): + + ```bash + bash ../../integrate/validation_tools/catch_properties.sh result.out + diff result.ref result.out + ``` + + A clean diff (apart from the `totaltimeref` line) means the case still + validates. This is the fastest check when you only changed the validation + scripts themselves. + +## How to modify an existing test case + +When merging or renaming a test case: + +1. Move/rename the directory: `git mv ` +2. Update the INPUT file with new parameters (keep the original style) +3. Update the case README to describe the new test purpose +4. Update `CASES_CPU.txt` and `CASES_GPU.txt`: remove old name, add new name +5. Remove the old directory: `git rm -rf ` +6. Regenerate `result.ref`: `OMP_NUM_THREADS=1 ../../integrate/Single_job.sh ref` +7. Commit the changes + +For smearing tests: insulators (e.g., H2) can accept any smearing method +without changing the result. Metals require smearing for convergence. + +Marking unimplemented features: +- If a feature is not yet implemented (e.g., a certain code path, GPU backend, + or functional lacks stress/force support), add the corresponding INPUT + parameter but comment it out with an explanation, e.g.: + + ``` + #cal_stress 1 # implicit solvation stress is not implemented yet + ``` + + This documents the intended coverage, makes the gap visible to readers, and + lets the test be enabled later by simply uncommenting once the feature lands. diff --git a/tests/integrate/Autotest.sh b/tests/integrate/Autotest.sh index 48b6d2308f7..728f7f0c670 100755 --- a/tests/integrate/Autotest.sh +++ b/tests/integrate/Autotest.sh @@ -1,7 +1,26 @@ #!/bin/bash -# ABACUS executable path -abacus=abacus +# Optional local override file, not tracked by git (see .gitignore). When +# present it is sourced first and may set the executable, e.g.: +# abacus=/home/me/abacus/build/abacus +# The -a command-line flag (applied later) always wins over this file, and an +# ABACUS_EXE exported in the environment wins over a path set here. +# Effective priority: +# -a flag > ABACUS_EXE environment > general_info.local > 'abacus' from PATH +# ABACUS_EXE is saved before sourcing so the local file cannot override it, +# and 'abacus' is cleared so only the local file can set it. +abacus_exe_env=${ABACUS_EXE:-} +abacus= +abacus_local_info="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/general_info.local" +if [ -f "$abacus_local_info" ]; then + . "$abacus_local_info" +fi + +# ABACUS executable path; override with the ABACUS_EXE environment variable +# so both this script and general_info (via ${ABACUS_EXE}) share one setting. +# A path set by general_info.local (above) is only used when ABACUS_EXE is +# not exported. +abacus=${abacus_exe_env:-${abacus:-abacus}} # number of MPI processes np=4 nt=$OMP_NUM_THREADS # number of OpenMP threads, default is $OMP_NUM_THREADS @@ -339,7 +358,7 @@ run_case() test -d OUT.autotest || (echo "No 'OUT.autotest' dir presented. Some errors may happened in ABACUS." && exit 1) if test -z $g then - bash -e ../../integrate/tools/catch_properties.sh result.out + bash -e ../../integrate/validation_tools/catch_properties.sh result.out if [ $? -ne 0 ]; then echo -e "\e[0;31m [ERROR ] Fatal Error in catch_properties.sh \e[0m" let fatal++ @@ -353,7 +372,7 @@ run_case() check_out result.out $my_threshold $my_force_threshold $my_stress_threshold $my_fatal_threshold $my_descriptor_threshold fi else - bash -e ../../integrate/tools/catch_properties.sh result.ref + bash -e ../../integrate/validation_tools/catch_properties.sh result.ref fi fi diff --git a/tests/integrate/Single_job.sh b/tests/integrate/Single_job.sh index 0769e03f5b6..12dcfddba66 100755 --- a/tests/integrate/Single_job.sh +++ b/tests/integrate/Single_job.sh @@ -1,6 +1,6 @@ #!/bin/bash -TOOLS_DIR="../../integrate/tools/" +TOOLS_DIR="../../integrate/validation_tools/" # The note for using the script. # input parameter: @@ -16,7 +16,10 @@ elif [ $1 == "debug" ] then echo "Begin debug!" cp $TOOLS_DIR/run_check.sh ./ - cp $TOOLS_DIR/catch_properties.sh ./ + # catch_properties.sh sources props_*.sh and the helper tools from its + # own directory, so copy the whole directory, not the entry script alone. + rm -rf ./validation_tools + cp -r $TOOLS_DIR ./validation_tools ./run_check.sh debug else echo "Generate file result.ref ." diff --git a/tests/integrate/general_info b/tests/integrate/general_info index c0d397efdc5..2eead2532e1 100644 --- a/tests/integrate/general_info +++ b/tests/integrate/general_info @@ -1,3 +1,3 @@ -EXEC abacus +EXEC ${ABACUS_EXE:-abacus} CHECKACCURACY 2 NUMBEROFPROCESS 4 diff --git a/tests/integrate/tools/catch_properties.sh b/tests/integrate/tools/catch_properties.sh deleted file mode 100755 index 94b4f526119..00000000000 --- a/tests/integrate/tools/catch_properties.sh +++ /dev/null @@ -1,1002 +0,0 @@ -#!/bin/bash - -# mohan add 2025-05-03 -# this compare script is used in different integrate tests -COMPARE_SCRIPT="../../integrate/tools/CompareFile.py" -#COMPARE_SCRIPT="../../integrate/tools/compare_file.py" -CUBE_TOOL="../../integrate/tools/cube_tool.py" -COLLECT_NPY_MEANS="../../integrate/tools/collect_npy_means.py" - - -sum_file(){ - line=`grep -vc '^$' $1` - inc=1 - if ! test -z $2; then - inc=$2 - fi - sum=0.0 - for (( num=1 ; num<=$line ; num+=$inc ));do - value_line=(` sed -n "$num p" $1 | head -n1 `) - colume=`echo ${#value_line[@]}` - for (( col=0 ; col<$colume ; col++ ));do - value=`echo ${value_line[$col]}` - sum=`awk 'BEGIN {x='$sum';y='$value';printf "%.6f\n",x+sqrt(y*y)}'` - done - done - echo $sum -} - - -get_input_key_value(){ - key=$1 - inputf=$2 - value=$(awk -v key=$key '{if($1==key) a=$2} END {print a}' $inputf) - echo $value -} - - -sanitize_result_key(){ - echo "$1" | sed 's/[^A-Za-z0-9_]/_/g' -} - - -record_compare_result(){ - result_file=$1 - result_key=$2 - ref_file=$3 - cal_file=$4 - accuracy=${5:-8} - use_abs=${6:-0} - - if [ ! -f "$ref_file" ] || [ ! -f "$cal_file" ]; then - echo "$result_key 1" >> "$result_file" - return - fi - - python3 $COMPARE_SCRIPT "$ref_file" "$cal_file" "$accuracy" -abs "$use_abs" - echo "$result_key $?" >> "$result_file" -} - - -file=$1 -#echo $1 - -# the command will ignore lines starting with # -calculation=`grep calculation INPUT | grep -v '^#' | awk '{print $2}' | sed s/[[:space:]]//g` - -# running_path=`echo "OUT.autotest/running_$calculation"".log"` -running_path=$(ls OUT.autotest/running_${calculation}*.log 2>/dev/null | head -1) -if [ -z "$running_path" ]; then - echo "Error: No running log file found for calculation=$calculation in OUT.autotest/" - exit 1 -fi -#echo $running_path - -natom=`grep -En '(^|[[:space:]])TOTAL ATOM NUMBER($|[[:space:]])' $running_path | tail -1 | awk '{print $6}'` -has_force=$(get_input_key_value "cal_force" "INPUT") -has_stress=$(get_input_key_value "cal_stress" "INPUT") -has_dftu=$(get_input_key_value "dft_plus_u" "INPUT") -has_band=$(get_input_key_value "out_band" "INPUT") -has_dos=$(get_input_key_value "out_dos" "INPUT") -has_cond=$(get_input_key_value "cal_cond" "INPUT") -out_hsk=$(get_input_key_value "out_hsk" "INPUT") -out_hsr=$(get_input_key_value "out_hsr" "INPUT") -has_hs=$(get_input_key_value "out_mat_hs" "INPUT") -has_hs2=$(get_input_key_value "out_mat_hs2" "INPUT") -out_hr_npz=$(get_input_key_value "out_hr_npz" "INPUT") -out_hsr_npz=$(get_input_key_value "out_hsr_npz" "INPUT") -out_dm_npz=$(get_input_key_value "out_dm_npz" "INPUT") -if ! test -z "$out_hsk"; then - has_hs=$out_hsk -fi -if ! test -z "$out_hsr"; then - has_hs2=$out_hsr -fi -has_xc=$(get_input_key_value "out_mat_xc" "INPUT") -has_xc2=$(get_input_key_value "out_mat_xc2" "INPUT") -has_eband_separate=$(get_input_key_value "out_eband_terms" "INPUT") -has_r=$(get_input_key_value "out_mat_r" "INPUT") -has_lowf=$(get_input_key_value "out_wfc_lcao" "INPUT") -out_app_flag=$(get_input_key_value "out_app_flag" "INPUT") -has_wfc_r=$(get_input_key_value "out_wfc_r" "INPUT") -has_wfc_pw=$(get_input_key_value "out_wfc_pw" "INPUT") -out_dm=$(get_input_key_value "out_dm" "INPUT") -out_mul=$(get_input_key_value "out_mul" "INPUT") -gamma_only=$(get_input_key_value "gamma_only" "INPUT") -imp_sol=$(get_input_key_value "imp_sol" "INPUT") -run_rpa=$(get_input_key_value "rpa" "INPUT") -out_pot=$(get_input_key_value "out_pot" "INPUT") -out_elf=$(get_input_key_value "out_elf" "INPUT") -out_dm1=$(get_input_key_value "out_dm1" "INPUT") -get_s=$(get_input_key_value "calculation" "INPUT") -out_pband=$(get_input_key_value "out_proj_band" "INPUT") -toW90=$(get_input_key_value "towannier90" "INPUT") -has_mat_r=$(get_input_key_value "out_mat_r" "INPUT") -has_mat_t=$(get_input_key_value "out_mat_t" "INPUT") -has_mat_syns=$(get_input_key_value "cal_syns" "INPUT") -has_mat_dh=$(get_input_key_value "out_mat_dh" "INPUT") -has_scan=$(get_input_key_value "dft_functional" "INPUT") -out_chg=$(get_input_key_value "out_chg" "INPUT") -has_ldos=$(get_input_key_value "out_ldos" "INPUT") -esolver_type=$(get_input_key_value "esolver_type" "INPUT") -rdmft=$(get_input_key_value "rdmft" "INPUT") -base=$(get_input_key_value "basis_type" "INPUT") -word_total_time="atomic_world" -symmetry=$(get_input_key_value "symmetry" "INPUT") -out_current=$(get_input_key_value "out_current" "INPUT") -out_efield=$(get_input_key_value "out_efield" "INPUT") -out_vecpot=$(get_input_key_value "out_vecpot" "INPUT") -nspin=$(get_input_key_value "nspin" "INPUT") -test -e $1 && rm $1 - -#------------------------------------------------------------ -# if NOT non-self-consistent calculations or linear response -#------------------------------------------------------------ -is_lr=0 -if [ ! -z $esolver_type ] && ([ $esolver_type == "lr" ] || [ $esolver_type == "ks-lr" ]); then - is_lr=1 -fi - -#---------------------------- -# total energy information -#---------------------------- -if [ $calculation != "get_wf" ]\ -&& [ $calculation != "get_pchg" ] && [ $calculation != "get_s" ]\ -&& [ $is_lr == 0 ]; then - etot=$(grep "ETOT_" "$running_path" | tail -1 | awk '{print $2}') - #echo "etot = $etot" - etotperatom=`awk 'BEGIN {x='$etot';y='$natom';printf "%.10f\n",x/y}'` - #echo "etotperatom = $etotperatom" - # put the results in file - echo "etotref $etot" >>$1 - echo "etotperatomref $etotperatom" >>$1 -fi - -# Opt-in collinear magnetic-state check. Comparing both moments distinguishes -# compensated AFM from NM; the reference tolerance is 1e-3 mu_B per cell. -if [ "$nspin" = "2" ] && [ -f magnetism.ref ]; then - awk ' - /Total magnetism \(Bohr mag\/cell\)/ {total = $NF; have_total = 1} - /Absolute magnetism \(Bohr mag\/cell\)/ {absolute = $NF; have_absolute = 1} - END { - if (!have_total || !have_absolute) exit 1 - print total, absolute - } - ' "$running_path" > magnetism.out - record_compare_result "$1" "CompareMagnetism_pass" "magnetism.ref" "magnetism.out" 3 -fi - -#---------------------------- -# force information -# echo "hasforce:"$has_force -#---------------------------- -if ! test -z "$has_force" && [ $has_force == 1 ]; then - nn3=`echo "$natom + 3" |bc` - # echo "nn3=$nn3" - # check the last step result - grep -A$nn3 "TOTAL-FORCE" $running_path |awk 'NF==4{print $2,$3,$4}' | tail -$natom > force.txt - total_force=`sum_file force.txt` - rm force.txt - echo "totalforceref $total_force" >>$1 -fi - -#------------------------------- -# stress information -# echo "has_stress:"$has_stress -#------------------------------- -if ! test -z "$has_stress" && [ $has_stress == 1 ]; then - grep -A6 "TOTAL-STRESS" $running_path| awk 'NF==3' | tail -3> stress.txt - total_stress=`sum_file stress.txt` - rm stress.txt - echo "totalstressref $total_stress" >>$1 -fi - - -#------------------------------- -# DOS information -# echo $total_charge -#------------------------------- -if ! test -z "$has_dos" && [ $has_dos == 1 ]; then - total_dos=`cat OUT.autotest/dos*.txt | awk 'END {print}' | awk '{print $3}'` - echo "totaldosref $total_dos" >> $1 -fi - -#------------------------------- -# Onsager coefficiency -#------------------------------- -if ! test -z "$has_cond" && [ $has_cond == 1 ]; then - onref=refOnsager.txt - oncal=OUT.autotest/Onsager.txt - python3 $COMPARE_SCRIPT $onref $oncal 3 -com_type 0 - echo "CompareH_Failed $?" >>$1 - rm -f je-je.txt Chebycoef -fi - -#------------------------------- -# echo $out_dm1 -#------------------------------- -if ! test -z "$out_dm1" && [ $out_dm1 == 1 ]; then - dm1ref=dmr_nao.csr.ref - dm1cal=OUT.autotest/dmr_nao.csr - python3 $COMPARE_SCRIPT $dm1ref $dm1cal 8 - echo "CompareDM1_pass $?" >>$1 -fi - -#------------------------------- -# echo $out_pot1 -#------------------------------- -if ! test -z "$out_pot" && [ $out_pot == 1 ]; then - pot1ref=pot.cube.ref - pot1cal=OUT.autotest/pot.cube - python3 $COMPARE_SCRIPT $pot1ref $pot1cal 3 - echo "ComparePot1_pass $?" >>$1 -fi - -#------------------------------- -#echo $out_pot2 -#------------------------------- -if ! test -z "$out_pot" && [ $out_pot == 2 ]; then - pot1ref=potes.cube.ref - pot1cal=OUT.autotest/potes.cube - python3 $COMPARE_SCRIPT $pot1ref $pot1cal 8 - echo "ComparePot_pass $?" >>$1 -fi - -#------------------------------- -# Electron localized function -# echo $out_elf -#------------------------------- -if ! test -z "$out_elf" && [ $out_elf == 1 ]; then - elf1ref=elftot.cube.ref - elf1cal=OUT.autotest/elftot.cube - python3 $COMPARE_SCRIPT $elf1ref $elf1cal 3 - echo "ComparePot1_pass $?" >>$1 -fi - -#------------------------------- -# Overlap matrix -# echo $get_s -#------------------------------- -if ! test -z "$get_s" && [ $get_s == "get_s" ]; then - sref=sr_nao.csr.ref - scal=OUT.autotest/sr_nao.csr - python3 $COMPARE_SCRIPT $sref $scal 8 - echo "CompareS_pass $?" >>$1 -fi - -#------------------------------- -# Partial band structure -# echo $out_pband -#------------------------------- -if ! test -z "$out_pband" && [ $out_pband == 1 ]; then - orbref=refOrbital - orbcal=OUT.autotest/Orbital - python3 $COMPARE_SCRIPT $orbref $orbcal 8 - echo "CompareOrb_pass $?" >>$1 -fi - -#------------------------------- -# Wannier90 information -# echo $toW90 -#------------------------------- -if ! test -z "$toW90" && [ $toW90 == 1 ]; then - amnref=diamond.amn - amncal=OUT.autotest/diamond.amn - mmnref=diamond.mmn - mmncal=OUT.autotest/diamond.mmn - eigref=diamond.eig - eigcal=OUT.autotest/diamond.eig - sed -i '1d' $amncal - sed -i '1d' $mmncal - python3 $COMPARE_SCRIPT $amnref $amncal 1 -abs 8 - echo "CompareAMN_pass $?" >>$1 - python3 $COMPARE_SCRIPT $mmnref $mmncal 1 -abs 8 - echo "CompareMMN_pass $?" >>$1 - python3 $COMPARE_SCRIPT $eigref $eigcal 8 - echo "CompareEIG_pass $?" >>$1 -fi - -#------------------------------- -# Total DOS -# echo total_dos -# echo $has_band -#------------------------------- -if ! test -z "$has_band" && [ $has_band == 1 ]; then - bandref=band.txt.ref - bandcal=OUT.autotest/band.txt - python3 $COMPARE_SCRIPT $bandref $bandcal 8 - echo "CompareBand_pass $?" >>$1 -fi - - - -#-------------------------------- -# Hamiltonian and overlap matrix -# echo $has_hs -#-------------------------------- -if ! test -z "$has_hs" && [ $has_hs == 1 ]; then - if ! test -z "$gamma_only" && [ $gamma_only == 1 ]; then - # ========== Γ-point (single k-point) calculation ========== - if ! test -z "$nspin" && [ $nspin == 2 ]; then - # nspin=2 (spin-polarized): compare hks1 + hks2 Hamiltonian + sk overlap matrix - h1ref=hks1_nao.txt.ref - h1cal=OUT.autotest/hks1_nao.txt - h2ref=hks2_nao.txt.ref - h2cal=OUT.autotest/hks2_nao.txt - sref=sk_nao.txt.ref - scal=OUT.autotest/sk_nao.txt - # Compare Hamiltonian matrix for spin 1 - python3 $COMPARE_SCRIPT $h1ref $h1cal 6 - echo "CompareH1_pass $?" >>$1 - # Compare Hamiltonian matrix for spin 2 - python3 $COMPARE_SCRIPT $h2ref $h2cal 6 - echo "CompareH2_pass $?" >>$1 - # Compare overlap matrix - python3 $COMPARE_SCRIPT $sref $scal 8 - echo "CompareS_pass $?" >>$1 - elif ! test -z "$nspin" && [ $nspin == 4 ]; then - # nspin=4 : do nothing, only matching condition without any operation - true - else - # nspin=1 (non-spin-polarized, default case): compare single hk + sk matrix set - href=hk_nao.txt.ref - hcal=OUT.autotest/hk_nao.txt - sref=sk_nao.txt.ref - scal=OUT.autotest/sk_nao.txt - # Compare Hamiltonian matrix - python3 $COMPARE_SCRIPT $href $hcal 6 - echo "CompareH_pass $?" >>$1 - # Compare overlap matrix - python3 $COMPARE_SCRIPT $sref $scal 8 - echo "CompareS_pass $?" >>$1 - fi - else - # ========== Multiple k-points calculation ========== - if ! test -z "$nspin" && [ $nspin == 2 ]; then - # nspin=2 (spin-polarized): compare spin-up/spin-down H(k) and S(k) at the second k-point - h1ref=hk2s1_nao.txt.ref - h1cal=OUT.autotest/hk2s1_nao.txt - h2ref=hk2s2_nao.txt.ref - h2cal=OUT.autotest/hk2s2_nao.txt - sref=sk2_nao.txt.ref - scal=OUT.autotest/sk2_nao.txt - # Compare Hamiltonian matrix for spin 1 - python3 $COMPARE_SCRIPT $h1ref $h1cal 6 - echo "CompareH1_pass $?" >>$1 - # Compare Hamiltonian matrix for spin 2 - python3 $COMPARE_SCRIPT $h2ref $h2cal 6 - echo "CompareH2_pass $?" >>$1 - # Compare overlap matrix - python3 $COMPARE_SCRIPT $sref $scal 8 - echo "CompareS_pass $?" >>$1 - elif ! test -z "$nspin" && [ $nspin == 4 ]; then - # nspin=4 : do nothing, only matching condition without any operation - true - else - # nspin=1 (non-spin-polarized, default case): compare single hk2 + sk2 matrix set - href=hk2_nao.txt.ref - hcal=OUT.autotest/hk2_nao.txt - sref=sk2_nao.txt.ref - scal=OUT.autotest/sk2_nao.txt - # Compare Hamiltonian matrix - python3 $COMPARE_SCRIPT $href $hcal 6 - echo "CompareH_pass $?" >>$1 - # Compare overlap matrix - python3 $COMPARE_SCRIPT $sref $scal 8 - echo "CompareS_pass $?" >>$1 - fi - fi -elif ! test -z "$has_hs" && [ $has_hs == 2 ]; then - HSK_BINARY_COMPARE="../../integrate/tools/compare_hsk_binary.py" - if ! test -z "$gamma_only" && [ $gamma_only == 1 ]; then - HSK_TEXT_REFERENCE_DIR="../scf_out_hk" - python3 $HSK_BINARY_COMPARE OUT.autotest/hk_nao.dat "$HSK_TEXT_REFERENCE_DIR/hk_nao.txt.ref" real 3 - echo "CompareH_pass $?" >>$1 - python3 $HSK_BINARY_COMPARE OUT.autotest/sk_nao.dat "$HSK_TEXT_REFERENCE_DIR/sk_nao.txt.ref" real 3 - echo "CompareS_pass $?" >>$1 - else - HSK_TEXT_REFERENCE_DIR="../scf_out_hsk" - python3 $HSK_BINARY_COMPARE OUT.autotest/hk2_nao.dat "$HSK_TEXT_REFERENCE_DIR/hk2_nao.txt.ref" complex 3 - echo "CompareH_pass $?" >>$1 - python3 $HSK_BINARY_COMPARE OUT.autotest/sk2_nao.dat "$HSK_TEXT_REFERENCE_DIR/sk2_nao.txt.ref" complex 3 - echo "CompareS_pass $?" >>$1 - fi -fi - -#-------------------------------- -# exchange-correlation potential -#-------------------------------- -if ! test -z "$has_xc" && [ $has_xc == 1 ]; then - if ! test -z "$gamma_only" && [ $gamma_only == 1 ]; then - xcref=vxc_nao.txt.ref - xccal=OUT.autotest/vxc_nao.txt - else - xcref=vxck2_nao.txt.ref - xccal=OUT.autotest/vxck2_nao.txt - fi - oeref=vxc_out.ref - oecal=OUT.autotest/vxc_out.dat - python3 $COMPARE_SCRIPT $xcref $xccal 4 - echo "CompareVXC_pass $?" >>$1 - python3 $COMPARE_SCRIPT $oeref $oecal 5 - echo "CompareOrbXC_pass $?" >>$1 -fi - -#-------------------------------- -# exchange-correlation potential -#-------------------------------- -if ! test -z "$has_xc2" && [ $has_xc2 == 1 ]; then - xc2ref=Vxc_R_spin0.ref - xc2cal=OUT.autotest/Vxc_R_spin0.csr - python3 $COMPARE_SCRIPT $xc2ref $xc2cal 8 - echo "CompareVXC_R_pass $?" >>$1 -fi - -#-------------------------------- -# separate terms in band enegy -#-------------------------------- -if ! test -z "$has_eband_separate" && [ $has_eband_separate == 1 ]; then - ekref=kinetic_out.ref - ekcal=OUT.autotest/kinetic_out.dat - python3 $COMPARE_SCRIPT $ekref $ekcal 4 - echo "CompareOrbKinetic_pass $?" >>$1 - vlref=vpp_local_out.ref - vlcal=OUT.autotest/vpp_local_out.dat - python3 $COMPARE_SCRIPT $vlref $vlcal 4 - echo "CompareOrbVL_pass $?" >>$1 - vnlref=vpp_nonlocal_out.ref - vnlcal=OUT.autotest/vpp_nonlocal_out.dat - python3 $COMPARE_SCRIPT $vnlref $vnlcal 4 - echo "CompareOrbVNL_pass $?" >>$1 - vhref=vhartree_out.ref - vhcal=OUT.autotest/vhartree_out.dat - python3 $COMPARE_SCRIPT $vhref $vhcal 4 - echo "CompareOrbVHartree_pass $?" >>$1 -fi - -#----------------------------------- -# Hamiltonian and overlap matrices -#----------------------------------- -#echo $has_hs2 -if ! test -z "$has_hs2" && [ $has_hs2 == 1 ]; then - python3 $COMPARE_SCRIPT hrs1_nao.csr.ref OUT.autotest/hrs1_nao.csr 8 - echo "CompareHR_pass $?" >>$1 - if ! test -z "$nspin" && [ "$nspin" -eq 2 ]; then - python3 $COMPARE_SCRIPT hrs2_nao.csr.ref OUT.autotest/hrs2_nao.csr 8 - echo "CompareHR2_pass $?" >>$1 - fi - python3 $COMPARE_SCRIPT sr_nao.csr.ref OUT.autotest/sr_nao.csr 8 - echo "CompareSR_pass $?" >>$1 -elif ! test -z "$has_hs2" && [ "$has_hs2" == 2 ]; then - HSR_BINARY_COMPARE="../../integrate/tools/compare_hsr_binary.py" - python3 $HSR_BINARY_COMPARE OUT.autotest/hrs1_nao.dat hrs1_nao.csr.ref real 4 - echo "CompareHR_pass $?" >>$1 - if ! test -z "$nspin" && [ "$nspin" -eq 2 ]; then - python3 $HSR_BINARY_COMPARE OUT.autotest/hrs2_nao.dat hrs2_nao.csr.ref real 4 - echo "CompareHR2_pass $?" >>$1 - fi - python3 $HSR_BINARY_COMPARE OUT.autotest/sr_nao.dat sr_nao.csr.ref real 4 - echo "CompareSR_pass $?" >>$1 -fi - -#----------------------------------- -# H(R), S(R), and DM(R) matrices in NPZ format -#----------------------------------- -if { ! test -z "$out_hsr" && [ "$out_hsr" == 3 ]; } || { ! test -z "$out_hsr_npz" && [ "$out_hsr_npz" == 1 ]; }; then - test -f OUT.autotest/sr_nao.npz - echo "OutputSRNPZ_pass $?" >>$1 -fi - -if { ! test -z "$out_hr_npz" && [ "$out_hr_npz" == 1 ]; } \ - || { ! test -z "$out_hsr" && [ "$out_hsr" == 3 ]; } \ - || { ! test -z "$out_hsr_npz" && [ "$out_hsr_npz" == 1 ]; }; then - test -f OUT.autotest/hrs1_nao.npz - echo "OutputHRNPZ_pass $?" >>$1 -fi - -if ! test -z "$out_dm_npz" && [ "$out_dm_npz" == 1 ]; then - test -f OUT.autotest/output_DM0.npz - echo "OutputDMNPZ_pass $?" >>$1 -fi - -#----------------------------------- -# matrix -#----------------------------------- -#echo $has_mat_r -if ! test -z "$has_mat_r" && [ $has_mat_r == 1 ]; then - python3 $COMPARE_SCRIPT rr_nao.txt.ref OUT.autotest/rr_nao.txt 8 - echo "ComparerR_pass $?" >>$1 -fi - -#----------------------------------- -# matrix -#----------------------------------- -#echo $has_mat_t -if ! test -z "$has_mat_t" && [ $has_mat_t == 1 ]; then - python3 $COMPARE_SCRIPT tr_nao.csr.ref OUT.autotest/tr_nao.csr 8 - echo "ComparerTR_pass $?" >>$1 -fi - -#----------------------------------- -# Asynchronous overlap matrix for Hefei-NAMD -#----------------------------------- -#echo $has_mat_syns -if ! test -z "$has_mat_syns" && [ $has_mat_syns == 1 ]; then - python3 $COMPARE_SCRIPT syns_nao.csr.ref OUT.autotest/syns_nao.csr 8 - echo "CompareSYNS_pass $?" >>$1 -fi - -#----------------------------------- -# matrix -#----------------------------------- -#echo $has_mat_dh -if ! test -z "$has_mat_dh" && [ $has_mat_dh == 1 ] && [ $gamma_only != 1 ]; then - python3 $COMPARE_SCRIPT dhrxs1_nao.csr.ref OUT.autotest/dhrxs1_nao.csr 8 - echo "ComparerdHRx_pass $?" >>$1 - python3 $COMPARE_SCRIPT dhrys1_nao.csr.ref OUT.autotest/dhrys1_nao.csr 8 - echo "ComparerdHRy_pass $?" >>$1 - python3 $COMPARE_SCRIPT dhrzs1_nao.csr.ref OUT.autotest/dhrzs1_nao.csr 8 - echo "ComparerdHRz_pass $?" >>$1 -fi - -#----------------------------------- -# d (k) matrix -#----------------------------------- -#echo $has_mat_dh_terms -if ! test -z "$has_mat_dh" && [ $has_mat_dh == 1 ]; then - shopt -s nullglob - for reffile in dhk_ref/*.txt; do - fname=$(basename "$reffile") - key=$(sanitize_result_key "Compare_${fname%.txt}") - record_compare_result "$1" "${key}_pass" "$reffile" "OUT.autotest/$fname" 8 - done - shopt -u nullglob -fi - -#--------------------------------------- -# Charge density -#--------------------------------------- -#echo $out_chg -if ! test -z "$out_chg" && [ $out_chg -ge 1 ]; then - record_compare_result "$1" "chg.cube_pass" "chg.cube.ref" "OUT.autotest/chg.cube" 6 -fi - - -#--------------------------------------- -# SCAN exchange-correlation information -#echo $has_scan -#--------------------------------------- -if ! test -z "$has_scan" && [ $has_scan == "scan" ] && \ - ! test -z "$out_chg" && [ $out_chg == 1 ]; then - python3 $COMPARE_SCRIPT tau.cube.ref OUT.autotest/tau.cube 8 - echo "tau.cube_pass $?" >>$1 -fi - -#--------------------------------------- -# local density of states -# echo $has_ldos -#--------------------------------------- -if ! test -z "$has_ldos" && [ $has_ldos == 1 ]; then - stm_bias=$(get_input_key_value "stm_bias" "OUT.autotest/INPUT.info") - python3 $COMPARE_SCRIPT LDOS.cube.ref OUT.autotest/LDOS_"$stm_bias"eV.cube 8 - echo "LDOS.cube_pass $?" >> $1 -fi - -#--------------------------------------- -# wave functions in real space -# echo "$has_wfc_r" ## test out_wfc_r > 0 -#--------------------------------------- -if ! test -z "$has_wfc_r" && [ $has_wfc_r == 1 ]; then - if [[ ! -f OUT.autotest/running_scf.log ]];then - echo "Can't find file OUT.autotest/running_scf.log" - exit 1 - fi - nband=$(grep NBANDS OUT.autotest/running_scf.log|awk '{print $3}') - allgrid=$(grep "fft grid for wave functions" OUT.autotest/running_scf.log | awk -F "[=,\\\[\\\]]" '{print $3*$4*$5}') - for((band=0;band<$nband;band++));do - if [[ -f "OUT.autotest/wfc_realspace/wfc_realspace_0_$band" ]];then - variance_wfc_r=`sed -n "13,$"p OUT.autotest/wfc_realspace/wfc_realspace_0_$band | \ - awk -v all=$allgrid 'BEGIN {sumall=0} {for(i=1;i<=NF;i++) {sumall+=($i-1)*($i-1)}}\ - END {printf"%.5f",(sumall/all)}'` - echo "variance_wfc_r_0_$band $variance_wfc_r" >>$1 - else - echo "Can't find file OUT.autotest/wfc_realspace/wfc_realspace_0_$band" - exit 1 - fi - done -fi - -#-------------------------------------------- -# wave functions in plane wave basis -# echo "$has_wfc_pw" ## test out_wfc_pw > 0 -#-------------------------------------------- -if ! test -z "$has_wfc_pw" && [ $has_wfc_pw == 1 ]; then - - # according to nspin value - if [ "$nspin" -eq 1 ]; then - filename="wfk1_pw.txt" - elif [ "$nspin" -eq 2 ]; then - filename="wfk1s1_pw.txt" - else - # other nspin cases - echo "Unsupported nspin value: $nspin" - exit 1 - fi - - full_path="OUT.autotest/$filename" - - if [[ ! -f "$full_path" ]];then - echo "Can't find file $full_path" - exit 1 - fi - awk 'BEGIN {max=0;read=0;band=1} - { - if(read==0 && $2 == "Band" && $3 == band){read=1} - else if(read==1 && $2 == "Band" && $3 == band) - {printf"Max_wfc_%d %.4f\n",band,max;read =0;band+=1;max=0} - else if(read==1) - { - for(i=1;i<=NF;i++) - { - if(sqrt($i*$i)>max) {max=sqrt($i*$i)} - } - } - }' "$full_path" >> "$1" -fi - - -#-------------------------------------------- -# wave functions in LCAO basis -# echo "$has_lowf" # test out_wfc_lcao > 0 -#-------------------------------------------- -if ! test -z "$has_lowf" && [ $has_lowf == 1 ]; then - if ! test -z "$gamma_only" && [ $gamma_only == 1 ]; then - wfc_cal=OUT.autotest/wf_nao.txt - wfc_ref=wf_nao.txt.ref - else # multi-k point case - if ! test -z "$out_app_flag" && [ $out_app_flag == 0 ]; then - wfc_name=wfk1g3_nao - input_file=WFC/wfk1g3_nao - else - wfc_name=wfk2_nao - input_file=wfk2_nao - fi - awk 'BEGIN {flag=999} - { - if($2 == "(band)") {flag=2;print $0} - else if(flag>0) {flag-=1;print $0} - else if(flag==0) - { - for(i=1;i<=NF/2;i++) - {printf "%.10e ",sqrt( $(2*i)*$(2*i)+$(2*i-1)*$(2*i-1) )}; - printf "\n" - } - else {print $0} - }' OUT.autotest/"$input_file".txt > OUT.autotest/"$wfc_name"_mod.txt - wfc_cal=OUT.autotest/"$wfc_name"_mod.txt - wfc_ref="$wfc_name"_mod.txt.ref - fi - - python3 $COMPARE_SCRIPT $wfc_cal $wfc_ref 8 -abs 1 - echo "Compare_wfc_lcao_pass $?" >>$1 -fi - -#-------------------------------------------- -# density matrix information -#-------------------------------------------- -if ! test -z "$out_dm" && [ $out_dm == 1 ]; then - dmfile=OUT.autotest/dm_nao.txt - dmref=dm_nao.txt.ref - if test -z "$dmfile"; then - echo "Can't find DM files" - exit 1 - else - python3 $COMPARE_SCRIPT $dmref $dmfile 5 - echo "DM_different $?" >>$1 - fi -fi - -#-------------------------------------------- -# mulliken charge -#-------------------------------------------- -if ! test -z "$out_mul" && [ $out_mul == 1 ]; then - python3 $COMPARE_SCRIPT mulliken.txt.ref OUT.autotest/mulliken.txt 3 - echo "Compare_mulliken_pass $?" >>$1 -fi - -#-------------------------------------------- -# Process .cube files for: -# 1. get_wf/get_pchg calculation tag (LCAO) -# 2. out_wfc_norm/out_wfc_re_im/out_pchg (PW) -#-------------------------------------------- -shopt -s nullglob -for pchg_ref in pchgi*.cube.ref; do - pchg_cube=${pchg_ref%.ref} - pchg_key=$(sanitize_result_key "${pchg_cube}_compare") - record_compare_result "$1" "$pchg_key" "$pchg_ref" "OUT.autotest/$pchg_cube" 6 -done -shopt -u nullglob - -need_process_cube=false -# Check if this is a LCAO calculation with get_wf/get_pchg -if [ $calculation == "get_wf" ] || [ $calculation == "get_pchg" ]; then - need_process_cube=true -fi -# Check if this is a PW calculation with out_wfc_norm/out_wfc_re_im -out_wfc_norm=$(get_input_key_value "out_wfc_norm" "INPUT") -out_wfc_re_im=$(get_input_key_value "out_wfc_re_im" "INPUT") -out_pchg=$(get_input_key_value "out_pchg" "INPUT") -if [ -n "$out_wfc_norm" ] || [ -n "$out_wfc_re_im" ] || [ -n "$out_pchg" ]; then - need_process_cube=true -fi -# Process .cube files if needed -if [ "$need_process_cube" = true ]; then - cubefiles=$(ls OUT.autotest/ | grep -E '.cube$') - wavefunction_re_files=() - - if [ -z "$cubefiles" ]; then - echo "Error: No .cube files found in OUT.autotest/" - exit 1 - else - for cube in $cubefiles; do - if [[ "$cube" =~ ^wfi[0-9]+s[0-9]+(k[0-9]+)?re[.]cube$ ]]; then - wavefunction_re_files+=("$cube") - continue - fi - if [[ "$cube" =~ ^wfi[0-9]+s[0-9]+(k[0-9]+)?im[.]cube$ ]]; then - continue - fi - total_chg=$(python3 "$CUBE_TOOL" integrate "OUT.autotest/$cube") - echo "$cube $total_chg" >> $1 - done - fi - - for cube in "${wavefunction_re_files[@]}"; do - if [[ "$cube" =~ ^wfi([0-9]+)s([0-9]+)(k[0-9]+)?re[.]cube$ ]]; then - band=${BASH_REMATCH[1]} - spin=${BASH_REMATCH[2]} - kpoint=${BASH_REMATCH[3]} - state_prefix=${cube%re.cube} - fingerprint_args=( - "OUT.autotest/$cube" - "OUT.autotest/${state_prefix}im.cube" - ) - if [ "$nspin" = "4" ]; then - if [ "$spin" != "1" ]; then - continue - fi - lower_prefix="wfi${band}s2${kpoint}" - fingerprint_args+=( - "OUT.autotest/${lower_prefix}re.cube" - "OUT.autotest/${lower_prefix}im.cube" - ) - result_prefix="wfi${band}${kpoint}_spinor_wfc_fp" - else - result_prefix="${state_prefix}_wfc_fp" - fi - - if fingerprint=$(python3 "$CUBE_TOOL" fingerprint-wfc "${fingerprint_args[@]}"); then - while read -r metric value; do - echo "${result_prefix}_${metric} $value" >> "$1" - done <<< "$fingerprint" - else - echo "Error: Failed to generate wavefunction fingerprint for $state_prefix" - exit 1 - fi - fi - done -fi - -# Check the pointwise Pauli identities when all PW nspin=4 spinor outputs are available. -nspin=$(get_input_key_value "nspin" "INPUT") -if_separate_k=$(get_input_key_value "if_separate_k" "INPUT") -if [ "$nspin" = "4" ] && { [ "$if_separate_k" = "1" ] || [ "$if_separate_k" = "true" ]; } \ - && [ -n "$out_wfc_norm" ] && [ -n "$out_wfc_re_im" ] && [ -n "$out_pchg" ]; then - if python3 "$CUBE_TOOL" check-spinor OUT.autotest; then - echo "pw_spinor_cube_identity 0" >> "$1" - else - echo "pw_spinor_cube_identity 1" >> "$1" - fi -fi - -#-------------------------------------------- -# ML gene data descriptors (.npy) -#-------------------------------------------- -descriptor_dir="OUT.autotest/MLKEDF_Descriptors" -if [ -d "$descriptor_dir" ]; then - python3 $COLLECT_NPY_MEANS "$descriptor_dir" >> "$1" -fi - -#-------------------------------------------- -# implicit solvation model -#-------------------------------------------- -if ! test -z "$imp_sol" && [ $imp_sol == 1 ]; then - esol_el=`grep E_sol_el $running_path | awk '{print $3}'` - esol_cav=`grep E_sol_cav $running_path | awk '{print $3}'` - echo "esolelref $esol_el" >>$1 - echo "esolcavref $esol_cav" >>$1 -fi - -#-------------------------------------------- -# random phase approximation -#-------------------------------------------- -if ! test -z "$run_rpa" && [ $run_rpa == 1 ]; then - Etot_without_rpa=`grep Etot_without_rpa log.txt | awk 'BEGIN{FS=":"} {print $2}' ` - echo "Etot_without_rpa $Etot_without_rpa" >> $1 - rpa_outdir=$(get_input_key_value "rpa_outdir" "INPUT") - if [ -z "$rpa_outdir" ]; then - rpa_outdir="./OUT.librpa" - fi - rpa_outdir=${rpa_outdir%/} - shopt -s nullglob - rpa_ref_files=(refcoulomb_*.txt refCs_*.txt refshrink_sinvS_*.txt) - if [ ${#rpa_ref_files[@]} -gt 0 ]; then - IFS=$'\n' rpa_ref_files=($(printf '%s\n' "${rpa_ref_files[@]}" | LC_ALL=C sort)) - unset IFS - for onref in "${rpa_ref_files[@]}"; do - oncal_name=${onref#ref} - oncal="$rpa_outdir/$oncal_name" - compare_key="CompareRPA_$(sanitize_result_key "$oncal_name")_pass" - record_compare_result "$1" "$compare_key" "$onref" "$oncal" 8 1 - done - fi - shopt -u nullglob -fi - -#-------------------------------------------- -# deepks -#-------------------------------------------- -script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -bash ${script_dir}/catch_deepks_properties.sh $1 - -#-------------------------------------------- -# check symmetry -#-------------------------------------------- -if ! test -z "$symmetry" && [ $symmetry == 1 ]; then - # exclude the nspin=4 MAGNETIC POINT/SPACE GROUP lines so they do not interfere - # with the crystallographic point-group / space-group detection below - pointgroup=`grep 'POINT GROUP =' $running_path | grep -v 'MAGNETIC' | grep -v 'BvK' | awk '{print $4}'` - spacegroup=`grep 'SPACE GROUP =' $running_path | grep -v 'MAGNETIC' | grep -v 'BvK' | awk '{print $7}'` - nksibz=`grep 'Number of irreducible k-points' $running_path | awk '{print $6}'` - echo "pointgroupref $pointgroup" >>$1 - echo "spacegroupref $spacegroup" >>$1 - echo "nksibzref $nksibz" >>$1 - # (nspin=4) magnetic (Shubnikov) group analysis: capture the space-group-consistent - # magnetic point group. Only printed when the group is actually reduced (magnetic); - # non-magnetic nspin=4 does not print it, so the capture is skipped when empty. - if ! test -z "$nspin" && [ $nspin == 4 ]; then - magpointgroup=`grep 'MAGNETIC POINT GROUP IN SPACE GROUP' $running_path | awk '{print $NF}'` - if ! test -z "$magpointgroup"; then - echo "magpointgroupref $magpointgroup" >>$1 - fi - fi -fi - -#-------------------------------------------- -# check currents in rt-TDDFT -#-------------------------------------------- -if ! test -z "$out_current" && [ $out_current ]; then - current1ref=current_tot.txt.ref - current1cal=OUT.autotest/current_tot.txt - python3 $COMPARE_SCRIPT $current1ref $current1cal 10 - echo "CompareCurrent_pass $?" >>$1 -fi - -#-------------------------------------------- -# Check electric fields in rt-TDDFT -#-------------------------------------------- -if ! test -z "$out_efield" && [ "$out_efield" == 1 ]; then - efield_refs=(efield_*.txt.ref) - if [ ! -e "${efield_refs[0]}" ]; then - echo "CompareEfieldReference_pass 1" >>$1 - else - for efield_ref in "${efield_refs[@]}"; do - efield_name=${efield_ref%.ref} - efield_key=$(sanitize_result_key "$efield_name") - record_compare_result "$1" "Compare${efield_key}_pass" "$efield_ref" "OUT.autotest/$efield_name" 8 - done - fi -fi - -#-------------------------------------------- -# Check vector potential in rt-TDDFT -#-------------------------------------------- -if ! test -z "$out_vecpot" && [ "$out_vecpot" == 1 ]; then - record_compare_result "$1" "CompareVectorPot_pass" "vector_pot.txt.ref" "OUT.autotest/vector_pot.txt" 8 -fi - -#-------------------------------------------- -# Linear response function -#-------------------------------------------- -if [ $is_lr == 1 ]; then - shopt -s nullglob - lr_files=(OUT.autotest/trans_analysis_*_tda.dat) - if [ ${#lr_files[@]} -gt 0 ]; then - cat "${lr_files[@]}" | awk '/Excitation Energy/{p=1; next} p && /^[[:space:]]*[0-9]+[[:space:]]/{printf "excitationenergyref%d %.6f\n", ++n, $2} /Occupied orbital/{p=0}' >>$1 - fi - shopt -u nullglob -fi -#-------------------------------------------- -# Check RDMFT method -#-------------------------------------------- -if ! test -z "$rdmft" && [[ $rdmft == 1 ]]; then - echo "" >>$1 - echo "The following energy units are in Rydberg:" >>$1 - - E_TV_RDMFT=$(grep "E_TV_RDMFT" "$running_path" | tail -1 | awk '{print $2}') - echo "E_TV_RDMFT_ref $E_TV_RDMFT" >>$1 - - E_hartree_RDMFT=$(grep "E_hartree_RDMFT" "$running_path" | tail -1 | awk '{print $2}') - echo "E_hartree_RDMFT_ref $E_hartree_RDMFT" >>$1 - - Exc_cwp22_RDMFT=$(grep "Exc_cwp22_RDMFT" "$running_path" | tail -1 | awk '{print $2}') - echo "Exc_cwp22_RDMFT_ref $Exc_cwp22_RDMFT" >>$1 - - E_Ewald=$(grep "E_Ewald" "$running_path" | tail -1 | awk '{print $2}') - echo "E_Ewald_ref $E_Ewald" >>$1 - - E_entropy=$(grep "E_entropy(-TS)" "$running_path" | tail -1 | awk '{print $2}') - echo "E_entropy_ref $E_entropy" >>$1 - - E_descf=$(grep "E_descf" "$running_path" | tail -1 | awk '{print $2}') - echo "E_descf_ref $E_descf" >>$1 - - Etotal_RDMFT=$(grep "Etotal_RDMFT" "$running_path" | tail -1 | awk '{print $2}') - echo "Etotal_RDMFT_ref $Etotal_RDMFT" >>$1 - - Exc_ksdft=$(grep "Exc_ksdft" "$running_path" | tail -1 | awk '{print $2}') - echo "Exc_ksdft_ref $Exc_ksdft" >>$1 - - E_exx_ksdft=$(grep "E_exx_ksdft" "$running_path" | tail -1 | awk '{print $2}') - echo "E_exx_ksdft_ref $E_exx_ksdft" >>$1 - - echo "" >>$1 -fi - -#-------------------------------------------- -# Check if out_alllog is set to 1 -# and verify running*.log filenames -#-------------------------------------------- -out_alllog=$(get_input_key_value "out_alllog" "INPUT") -if ! test -z "$out_alllog" && [ $out_alllog -eq 1 ]; then - calculation=$(get_input_key_value "calculation" "INPUT") - - if [ -z "$calculation" ]; then - echo "Error: calculation parameter not found in INPUT" - exit 1 - fi - - # Find all running*.log files in OUT.autotest directory - log_files=$(ls OUT.autotest/running*.log 2>/dev/null) - - if [ -z "$log_files" ]; then - echo "Error: No running*.log files found in OUT.autotest/" - exit 1 - fi - - # Check each log file name contains the calculation parameter - all_valid=true - for log_file in $log_files; do - filename=$(basename "$log_file") - if [[ ! "$filename" =~ running_${calculation}_ ]]; then - echo "Error: Invalid log filename $filename - should contain 'running_${calculation}_'" - all_valid=false - fi - done - - if $all_valid; then - echo "All log filenames contain 'running_${calculation}_' - validation passed" - echo "log_filename_validation 1" >>$1 - else - echo "Error: Some log filenames do not contain 'running_${calculation}_'" - echo "log_filename_validation 0" >>$1 - exit 1 - fi -fi - -#-------------------------------------------- -# Check time information -#-------------------------------------------- -#echo $total_band -ttot=`grep $word_total_time $running_path | awk '{print $3}'` -echo "totaltimeref $ttot" >>$1 diff --git a/tests/integrate/tools/CompareFile.py b/tests/integrate/validation_tools/CompareFile.py similarity index 100% rename from tests/integrate/tools/CompareFile.py rename to tests/integrate/validation_tools/CompareFile.py diff --git a/tests/integrate/validation_tools/catch_deepks_properties.sh b/tests/integrate/validation_tools/catch_deepks_properties.sh new file mode 100644 index 00000000000..0fbde171308 --- /dev/null +++ b/tests/integrate/validation_tools/catch_deepks_properties.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Standalone entry for the DeePKS property collector. +# +# catch_properties.sh sources props_deepks.sh directly; this thin wrapper is +# kept for manual invocation, e.g. `bash catch_deepks_properties.sh result`. +# props_init() is intentionally NOT called: standalone mode must append to +# the caller-supplied result file without truncating it, and +# run_deepks_props() reads the few INPUT switches it needs itself. +PROPS_SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +source "$PROPS_SCRIPT_DIR/props_common.sh" +props_result_file=$1 +source "$PROPS_SCRIPT_DIR/props_deepks.sh" +run_deepks_props diff --git a/tests/integrate/validation_tools/catch_properties.sh b/tests/integrate/validation_tools/catch_properties.sh new file mode 100755 index 00000000000..22ef71b9e32 --- /dev/null +++ b/tests/integrate/validation_tools/catch_properties.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +# mohan add 2025-05-03 +# this compare script is used in different integrate tests +# +# Entry point: sources the shared library (tool paths, helper functions, +# one-time INPUT switch parsing) and then runs each property block below. +PROPS_SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +source "$PROPS_SCRIPT_DIR/props_common.sh" +props_init "$1" +source "$PROPS_SCRIPT_DIR/props_basic.sh" +source "$PROPS_SCRIPT_DIR/props_mat.sh" +source "$PROPS_SCRIPT_DIR/props_cube.sh" +source "$PROPS_SCRIPT_DIR/props_ml.sh" +source "$PROPS_SCRIPT_DIR/props_tddft.sh" +source "$PROPS_SCRIPT_DIR/props_deepks.sh" + +# Property collectors run in the same order as the original monolithic +# script so result files stay byte-identical; props_finalize() writes the +# trailing totaltimeref entry. Add a new category by dropping a +# props_.sh next to the others and calling run__props here. +run_basic_props + +#------------------------------- +# matrix collectors: out_dm1 (props_mat.sh) +#------------------------------- +run_mat_dm1_props + +#------------------------------- +# cube collectors: out_pot/out_elf (props_cube.sh) +#------------------------------- +run_cube_pot_props + +#------------------------------- +# matrix collectors (props_mat.sh): S/H(k)/XC/eband/H(R)/NPZ/r/T/dH +#------------------------------- +run_mat_props + +#--------------------------------------- +# cube collectors (props_cube.sh): chg/tau/LDOS/wfc real-space/PW/LCAO +#--------------------------------------- +run_cube_props + +#-------------------------------------------- +# matrix collector: out_dm (props_mat.sh) +#-------------------------------------------- +run_mat_dm_props + +#-------------------------------------------- +# cube collectors (props_cube.sh): mulliken/pchg/fingerprints/spinor +#-------------------------------------------- +run_cube_tail_props + +#-------------------------------------------- +# ML collectors (props_ml.sh): MLKEDF descriptors +#-------------------------------------------- +run_ml_descriptor_props + +#-------------------------------------------- +# basic collectors that run after ml: imp_sol +#-------------------------------------------- +run_basic_props_post_ml + +#-------------------------------------------- +# ML collectors (props_ml.sh): RPA +#-------------------------------------------- +run_ml_rpa_props + +#-------------------------------------------- +# DeePKS collectors (props_deepks.sh) +#-------------------------------------------- +# Before the split this block ran as a separate `bash` process without -e, +# so a failing helper (get_sum_*.py, a missing deepks_desc.dat) left an +# empty value for the threshold check instead of aborting the collection. +# Keep that behavior with a subshell that disables errexit. +( set +e; run_deepks_props ) + +#-------------------------------------------- +# basic collectors that run after deepks: symmetry +#-------------------------------------------- +run_basic_props_post_deepks + +#-------------------------------------------- +# rt-TDDFT collectors (props_tddft.sh): current/efield/vecpot +#-------------------------------------------- +run_tddft_props + +#-------------------------------------------- +# ML collectors (props_ml.sh): linear response excitations +#-------------------------------------------- +run_ml_lr_props +#-------------------------------------------- +# ML collectors (props_ml.sh): RDMFT energy terms +#-------------------------------------------- +run_ml_rdmft_props + +#-------------------------------------------- +# basic collectors that run after rdmft: alllog +#-------------------------------------------- +run_basic_props_post_rdmft + +#-------------------------------------------- +# trailing total-time entry +#-------------------------------------------- +props_finalize "$1" diff --git a/tests/integrate/tools/collect_npy_means.py b/tests/integrate/validation_tools/collect_npy_means.py similarity index 100% rename from tests/integrate/tools/collect_npy_means.py rename to tests/integrate/validation_tools/collect_npy_means.py diff --git a/tests/integrate/tools/compare_hsk_binary.py b/tests/integrate/validation_tools/compare_hsk_binary.py similarity index 100% rename from tests/integrate/tools/compare_hsk_binary.py rename to tests/integrate/validation_tools/compare_hsk_binary.py diff --git a/tests/integrate/tools/compare_hsr_binary.py b/tests/integrate/validation_tools/compare_hsr_binary.py similarity index 100% rename from tests/integrate/tools/compare_hsr_binary.py rename to tests/integrate/validation_tools/compare_hsr_binary.py diff --git a/tests/integrate/tools/cube_tool.py b/tests/integrate/validation_tools/cube_tool.py similarity index 78% rename from tests/integrate/tools/cube_tool.py rename to tests/integrate/validation_tools/cube_tool.py index 833e8443423..52ea0e64ac9 100755 --- a/tests/integrate/tools/cube_tool.py +++ b/tests/integrate/validation_tools/cube_tool.py @@ -9,14 +9,6 @@ density, partial charge, wavefunction modulus, potential, and other scalar cube outputs. The command prints one number: ``sum(values) * voxel_volume``. -``compare-wfc CAL_RE CAL_IM REF_RE REF_IM [CAL_RE CAL_IM REF_RE REF_IM ...]`` - Compare one complex wavefunction state after removing its arbitrary global - U(1) phase. Pass one group of four files for a scalar wavefunction or for an - independently aligned spin channel. Pass two groups for an nspin=4 spinor; - all groups then share the same fitted phase. The command prints the maximum - pointwise complex error after alignment. It does not perform unitary - alignment within a degenerate subspace. - ``fingerprint-wfc RE IM [RE IM ...]`` Generate a compact, deterministic fingerprint of one complex wavefunction state. One real/imaginary pair represents a scalar component; two pairs in @@ -45,7 +37,6 @@ Examples: python3 cube_tool.py integrate OUT.autotest/charge.cube - python3 cube_tool.py compare-wfc cal_re.cube cal_im.cube ref_re.cube ref_im.cube python3 cube_tool.py fingerprint-wfc state_re.cube state_im.cube python3 cube_tool.py check-spinor OUT.autotest --tolerance 1e-8 @@ -213,76 +204,6 @@ def format_metric(value): return f"{value:.12e}" -def read_wfc_component(calculated_re, calculated_im, reference_re, reference_im): - paths = (calculated_re, calculated_im, reference_re, reference_im) - cubes = [read_cube(path) for path in paths] - shape = cubes[0][0] - for path, cube in zip(paths[1:], cubes[1:]): - if cube[0] != shape: - raise ValueError(f"grid shape mismatch in {path}: expected {shape}, found {cube[0]}") - - calculated = [complex(real, imag) for real, imag in zip(cubes[0][2], cubes[1][2])] - reference = [complex(real, imag) for real, imag in zip(cubes[2][2], cubes[3][2])] - return shape, calculated, reference - - -def phase_aligned_error(file_groups): - calculated_components = [] - reference_components = [] - common_shape = None - for group in file_groups: - shape, calculated, reference = read_wfc_component(*group) - if common_shape is None: - common_shape = shape - elif shape != common_shape: - raise ValueError( - f"spinor component grid shape mismatch in {group[0]}: " - f"expected {common_shape}, found {shape}" - ) - calculated_components.append(calculated) - reference_components.append(reference) - - calculated_norm = sum( - abs(value) ** 2 for component in calculated_components for value in component - ) - reference_norm = sum( - abs(value) ** 2 for component in reference_components for value in component - ) - if not math.isfinite(calculated_norm) or calculated_norm == 0.0: - raise ValueError("calculated wavefunction has zero or non-finite norm") - if not math.isfinite(reference_norm) or reference_norm == 0.0: - raise ValueError("reference wavefunction has zero or non-finite norm") - - overlap = sum( - reference.conjugate() * calculated - for calculated_component, reference_component in zip( - calculated_components, reference_components - ) - for calculated, reference in zip(calculated_component, reference_component) - ) - if ( - not math.isfinite(overlap.real) - or not math.isfinite(overlap.imag) - or abs(overlap) == 0.0 - ): - raise ValueError("wavefunction overlap is zero or non-finite; the global phase is undefined") - - phase = overlap.conjugate() / abs(overlap) - return max( - abs(phase * calculated - reference) - for calculated_component, reference_component in zip( - calculated_components, reference_components - ) - for calculated, reference in zip(calculated_component, reference_component) - ) - - -def parse_wfc_groups(paths): - if len(paths) % 4 != 0: - raise ValueError("expected four cube paths per component: CAL_RE CAL_IM REF_RE REF_IM") - return [paths[index : index + 4] for index in range(0, len(paths), 4)] - - def read_dense_shape(directory): pattern = re.compile( r"fft grid for dense charge/potential\s*=\s*\[\s*([0-9]+),\s*([0-9]+),\s*([0-9]+)\s*\]" @@ -391,10 +312,6 @@ def run_integrate(args): print(f"{integrate_cube(args.cube):.10g}") -def run_compare_wfc(args): - print(f"{phase_aligned_error(parse_wfc_groups(args.cube_paths)):.10g}") - - def run_fingerprint_wfc(args): for name, value in wfc_fingerprint(args.cube_paths): print(f"{name} {format_metric(value)}") @@ -416,16 +333,6 @@ def build_parser(): integrate_parser.add_argument("cube", type=Path) integrate_parser.set_defaults(handler=run_integrate) - compare_parser = subparsers.add_parser( - "compare-wfc", help="compare one complex state after global phase alignment" - ) - compare_parser.add_argument( - "cube_paths", - nargs="+", - help="CAL_RE CAL_IM REF_RE REF_IM, repeated for spinor components", - ) - compare_parser.set_defaults(handler=run_compare_wfc) - fingerprint_parser = subparsers.add_parser( "fingerprint-wfc", help="generate a deterministic global-phase-invariant wavefunction fingerprint", diff --git a/tests/integrate/validation_tools/props_basic.sh b/tests/integrate/validation_tools/props_basic.sh new file mode 100644 index 00000000000..50b0ca88822 --- /dev/null +++ b/tests/integrate/validation_tools/props_basic.sh @@ -0,0 +1,173 @@ +#!/bin/bash + +# Basic (energy/force/stress/DOS/Onsager/solvation/symmetry/alllog) +# property collectors. +# +# Sourced by catch_properties.sh after props_common.sh; relies on the global +# variables set by props_init() ($calculation, $running_path, $natom, +# $nspin, $is_lr, $has_*, $symmetry, $imp_sol, ...). +# +# The original monolithic script interleaved these checks with blocks that +# now live in other modules (ML descriptors, DeePKS, RDMFT). To preserve the +# exact emission order of result lines, this module exposes four hooks that +# the entry point calls at the matching positions: +# run_basic_props() - before the matrix/cube collectors +# run_basic_props_post_ml() - after the ML descriptor block +# run_basic_props_post_deepks() - after the DeePKS block +# run_basic_props_post_rdmft() - after the RDMFT block +# +# All result lines are appended to $props_result_file. + +run_basic_props(){ + +#---------------------------- +# total energy information +#---------------------------- +if [ $calculation != "get_wf" ]\ +&& [ $calculation != "get_pchg" ] && [ $calculation != "get_s" ]\ +&& [ $is_lr == 0 ]; then + etot=$(grep "ETOT_" "$running_path" | tail -1 | awk '{print $2}') + etotperatom=`awk 'BEGIN {x='$etot';y='$natom';printf "%.10f\n",x/y}'` + echo "etotref $etot" >>$props_result_file + echo "etotperatomref $etotperatom" >>$props_result_file +fi + +# Opt-in collinear magnetic-state check. Comparing both moments distinguishes +# compensated AFM from NM; the reference tolerance is 1e-3 mu_B per cell. +if [ "$nspin" = "2" ] && [ -f magnetism.ref ]; then + awk ' + /Total magnetism \(Bohr mag\/cell\)/ {total = $NF; have_total = 1} + /Absolute magnetism \(Bohr mag\/cell\)/ {absolute = $NF; have_absolute = 1} + END { + if (!have_total || !have_absolute) exit 1 + print total, absolute + } + ' "$running_path" > magnetism.out + record_compare_result "$props_result_file" "CompareMagnetism_pass" "magnetism.ref" "magnetism.out" 3 +fi + +#---------------------------- +# force information +#---------------------------- +if ! test -z "$has_force" && [ $has_force == 1 ]; then + nn3=`echo "$natom + 3" |bc` + # check the last step result + grep -A$nn3 "TOTAL-FORCE" $running_path |awk 'NF==4{print $2,$3,$4}' | tail -$natom > force.txt + total_force=`sum_file force.txt` + rm force.txt + echo "totalforceref $total_force" >>$props_result_file +fi + +#------------------------------- +# stress information +#------------------------------- +if ! test -z "$has_stress" && [ $has_stress == 1 ]; then + grep -A6 "TOTAL-STRESS" $running_path| awk 'NF==3' | tail -3> stress.txt + total_stress=`sum_file stress.txt` + rm stress.txt + echo "totalstressref $total_stress" >>$props_result_file +fi + +#------------------------------- +# DOS information +#------------------------------- +if ! test -z "$has_dos" && [ $has_dos == 1 ]; then + total_dos=`cat OUT.autotest/dos*.txt | awk 'END {print}' | awk '{print $3}'` + echo "totaldosref $total_dos" >> $props_result_file +fi + +#------------------------------- +# Onsager coefficiency +#------------------------------- +if ! test -z "$has_cond" && [ $has_cond == 1 ]; then + onref=refOnsager.txt + oncal=OUT.autotest/Onsager.txt + python3 $COMPARE_SCRIPT $onref $oncal 3 -com_type 0 + echo "CompareH_Failed $?" >>$props_result_file + rm -f je-je.txt Chebycoef +fi + +} + +run_basic_props_post_ml(){ + +#-------------------------------------------- +# implicit solvation model +#-------------------------------------------- +if ! test -z "$imp_sol" && [ $imp_sol == 1 ]; then + esol_el=`grep E_sol_el $running_path | awk '{print $3}'` + esol_cav=`grep E_sol_cav $running_path | awk '{print $3}'` + echo "esolelref $esol_el" >>$props_result_file + echo "esolcavref $esol_cav" >>$props_result_file +fi + +} + +run_basic_props_post_deepks(){ + +#-------------------------------------------- +# check symmetry +#-------------------------------------------- +if ! test -z "$symmetry" && [ $symmetry == 1 ]; then + # exclude the nspin=4 MAGNETIC POINT/SPACE GROUP lines so they do not interfere + # with the crystallographic point-group / space-group detection below + pointgroup=`grep 'POINT GROUP =' $running_path | grep -v 'MAGNETIC' | grep -v 'BvK' | awk '{print $4}'` + spacegroup=`grep 'SPACE GROUP =' $running_path | grep -v 'MAGNETIC' | grep -v 'BvK' | awk '{print $7}'` + nksibz=`grep 'Number of irreducible k-points' $running_path | awk '{print $6}'` + echo "pointgroupref $pointgroup" >>$props_result_file + echo "spacegroupref $spacegroup" >>$props_result_file + echo "nksibzref $nksibz" >>$props_result_file + # (nspin=4) magnetic (Shubnikov) group analysis: capture the space-group-consistent + # magnetic point group. Only printed when the group is actually reduced (magnetic); + # non-magnetic nspin=4 does not print it, so the capture is skipped when empty. + if ! test -z "$nspin" && [ $nspin == 4 ]; then + magpointgroup=`grep 'MAGNETIC POINT GROUP IN SPACE GROUP' $running_path | awk '{print $NF}'` + if ! test -z "$magpointgroup"; then + echo "magpointgroupref $magpointgroup" >>$props_result_file + fi + fi +fi + +} + +run_basic_props_post_rdmft(){ + +#-------------------------------------------- +# Check if out_alllog is set to 1 +# and verify running*.log filenames +#-------------------------------------------- +if ! test -z "$out_alllog" && [ $out_alllog -eq 1 ]; then + if [ -z "$calculation" ]; then + echo "Error: calculation parameter not found in INPUT" + exit 1 + fi + + # Find all running*.log files in OUT.autotest directory + log_files=$(ls OUT.autotest/running*.log 2>/dev/null) + + if [ -z "$log_files" ]; then + echo "Error: No running*.log files found in OUT.autotest/" + exit 1 + fi + + # Check each log file name contains the calculation parameter + all_valid=true + for log_file in $log_files; do + filename=$(basename "$log_file") + if [[ ! "$filename" =~ running_${calculation}_ ]]; then + echo "Error: Invalid log filename $filename - should contain 'running_${calculation}_'" + all_valid=false + fi + done + + if $all_valid; then + echo "All log filenames contain 'running_${calculation}_' - validation passed" + echo "log_filename_validation 1" >>$props_result_file + else + echo "Error: Some log filenames do not contain 'running_${calculation}_'" + echo "log_filename_validation 0" >>$props_result_file + exit 1 + fi +fi + +} diff --git a/tests/integrate/validation_tools/props_common.sh b/tests/integrate/validation_tools/props_common.sh new file mode 100644 index 00000000000..4068370ebb3 --- /dev/null +++ b/tests/integrate/validation_tools/props_common.sh @@ -0,0 +1,168 @@ +#!/bin/bash + +# Common library for the integrate-test property collectors. +# +# This file is meant to be *sourced*, not executed. It provides: +# - absolute paths to the helper tools (CompareFile.py, cube_tool.py, ...), +# resolved from this file's own location so callers no longer depend on +# the hard-coded "../../integrate/validation_tools" relative path; +# - the shared helper functions (sum_file, get_input_key_value, +# sanitize_result_key, record_compare_result); +# - props_init(): one-time parsing of all INPUT switch keys into global +# shell variables, plus clearing of the result file; +# - props_finalize(): the always-emitted total-time entry. +# +# Extension contract: the entry script (catch_properties.sh) sources this +# file and then each props_.sh module. A module defines +# run__props() that reads the global switch variables set by +# props_init() and appends "key value" lines to the result file. Keys used +# by a single module only (e.g. deepks_*) may be read by that module itself. + +# Absolute path of the directory containing this file +# (i.e. integrate/validation_tools). +PROPS_TOOLS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +COMPARE_SCRIPT="$PROPS_TOOLS_DIR/CompareFile.py" +CUBE_TOOL="$PROPS_TOOLS_DIR/cube_tool.py" +COLLECT_NPY_MEANS="$PROPS_TOOLS_DIR/collect_npy_means.py" + + +sum_file(){ + line=`grep -vc '^$' $1` + inc=1 + if ! test -z $2; then + inc=$2 + fi + sum=0.0 + for (( num=1 ; num<=$line ; num+=$inc ));do + value_line=(` sed -n "$num p" $1 | head -n1 `) + colume=`echo ${#value_line[@]}` + for (( col=0 ; col<$colume ; col++ ));do + value=`echo ${value_line[$col]}` + sum=`awk 'BEGIN {x='$sum';y='$value';printf "%.6f\n",x+sqrt(y*y)}'` + done + done + echo $sum +} + + +get_input_key_value(){ + key=$1 + inputf=$2 + value=$(awk -v key=$key '{if($1==key) a=$2} END {print a}' $inputf) + echo $value +} + + +sanitize_result_key(){ + echo "$1" | sed 's/[^A-Za-z0-9_]/_/g' +} + + +record_compare_result(){ + result_file=$1 + result_key=$2 + ref_file=$3 + cal_file=$4 + accuracy=${5:-8} + use_abs=${6:-0} + + if [ ! -f "$ref_file" ] || [ ! -f "$cal_file" ]; then + echo "$result_key 1" >> "$result_file" + return + fi + + python3 $COMPARE_SCRIPT "$ref_file" "$cal_file" "$accuracy" -abs "$use_abs" + echo "$result_key $?" >> "$result_file" +} + + +# props_init RESULT_FILE +# +# Reads the shared INPUT switch keys once into global variables, locates the +# running log, derives shared values (natom, is_lr, ...), and truncates the +# result file. Must be called from the test-case directory (where INPUT and +# OUT.autotest live) before any run__props function. +props_init(){ + props_result_file=$1 + + # the command will ignore lines starting with # + calculation=`grep calculation INPUT | grep -v '^#' | awk '{print $2}' | sed s/[[:space:]]//g` + + running_path=$(ls OUT.autotest/running_${calculation}*.log 2>/dev/null | head -1) + if [ -z "$running_path" ]; then + echo "Error: No running log file found for calculation=$calculation in OUT.autotest/" + exit 1 + fi + + natom=`grep -En '(^|[[:space:]])TOTAL ATOM NUMBER($|[[:space:]])' $running_path | tail -1 | awk '{print $6}'` + has_force=$(get_input_key_value "cal_force" "INPUT") + has_stress=$(get_input_key_value "cal_stress" "INPUT") + has_band=$(get_input_key_value "out_band" "INPUT") + has_dos=$(get_input_key_value "out_dos" "INPUT") + has_cond=$(get_input_key_value "cal_cond" "INPUT") + out_hsk=$(get_input_key_value "out_hsk" "INPUT") + out_hsr=$(get_input_key_value "out_hsr" "INPUT") + has_hs=$(get_input_key_value "out_mat_hs" "INPUT") + has_hs2=$(get_input_key_value "out_mat_hs2" "INPUT") + out_hr_npz=$(get_input_key_value "out_hr_npz" "INPUT") + out_hsr_npz=$(get_input_key_value "out_hsr_npz" "INPUT") + out_dm_npz=$(get_input_key_value "out_dm_npz" "INPUT") + if ! test -z "$out_hsk"; then + has_hs=$out_hsk + fi + if ! test -z "$out_hsr"; then + has_hs2=$out_hsr + fi + has_xc=$(get_input_key_value "out_mat_xc" "INPUT") + has_xc2=$(get_input_key_value "out_mat_xc2" "INPUT") + has_eband_separate=$(get_input_key_value "out_eband_terms" "INPUT") + has_lowf=$(get_input_key_value "out_wfc_lcao" "INPUT") + out_app_flag=$(get_input_key_value "out_app_flag" "INPUT") + has_wfc_r=$(get_input_key_value "out_wfc_r" "INPUT") + has_wfc_pw=$(get_input_key_value "out_wfc_pw" "INPUT") + out_dm=$(get_input_key_value "out_dm" "INPUT") + out_mul=$(get_input_key_value "out_mul" "INPUT") + gamma_only=$(get_input_key_value "gamma_only" "INPUT") + imp_sol=$(get_input_key_value "imp_sol" "INPUT") + run_rpa=$(get_input_key_value "rpa" "INPUT") + out_pot=$(get_input_key_value "out_pot" "INPUT") + out_elf=$(get_input_key_value "out_elf" "INPUT") + out_dm1=$(get_input_key_value "out_dm1" "INPUT") + out_pband=$(get_input_key_value "out_proj_band" "INPUT") + toW90=$(get_input_key_value "towannier90" "INPUT") + has_mat_r=$(get_input_key_value "out_mat_r" "INPUT") + has_mat_t=$(get_input_key_value "out_mat_t" "INPUT") + has_mat_syns=$(get_input_key_value "cal_syns" "INPUT") + has_mat_dh=$(get_input_key_value "out_mat_dh" "INPUT") + has_scan=$(get_input_key_value "dft_functional" "INPUT") + out_chg=$(get_input_key_value "out_chg" "INPUT") + has_ldos=$(get_input_key_value "out_ldos" "INPUT") + esolver_type=$(get_input_key_value "esolver_type" "INPUT") + rdmft=$(get_input_key_value "rdmft" "INPUT") + word_total_time="atomic_world" + symmetry=$(get_input_key_value "symmetry" "INPUT") + out_current=$(get_input_key_value "out_current" "INPUT") + out_efield=$(get_input_key_value "out_efield" "INPUT") + out_vecpot=$(get_input_key_value "out_vecpot" "INPUT") + nspin=$(get_input_key_value "nspin" "INPUT") + out_alllog=$(get_input_key_value "out_alllog" "INPUT") + test -e $props_result_file && rm $props_result_file + + # if NOT non-self-consistent calculations or linear response + is_lr=0 + if [ ! -z $esolver_type ] && ([ $esolver_type == "lr" ] || [ $esolver_type == "ks-lr" ]); then + is_lr=1 + fi +} + + +# props_finalize RESULT_FILE +# +# Appends the always-present total-time entry. Kept as the single trailing +# step so new modules can be inserted before it without touching the ending. +props_finalize(){ + props_result_file=$1 + ttot=`grep $word_total_time $running_path | awk '{print $3}'` + echo "totaltimeref $ttot" >> $props_result_file +} diff --git a/tests/integrate/validation_tools/props_cube.sh b/tests/integrate/validation_tools/props_cube.sh new file mode 100644 index 00000000000..593dc2d469a --- /dev/null +++ b/tests/integrate/validation_tools/props_cube.sh @@ -0,0 +1,289 @@ +#!/bin/bash + +# Real-space cube / wave-function property collectors. +# +# Sourced by catch_properties.sh after props_common.sh; relies on the global +# switch variables set by props_init() and appends result lines to +# $props_result_file. +# +# The cube collectors originally wrapped around the out_dm matrix block +# (props_mat.sh), so this module exposes three hooks called at the matching +# positions to preserve result-line order: +# run_cube_pot_props() - out_pot (1/2) and out_elf cubes +# run_cube_props() - chg/SAN tau/LDOS and wfc real-space/PW/LCAO checks +# run_cube_tail_props() - mulliken, pchg cubes, get_wf/get_pchg cube +# integration/fingerprints, nspin=4 spinor identity + +run_cube_pot_props(){ + +#------------------------------- +# echo $out_pot1 +#------------------------------- +if ! test -z "$out_pot" && [ $out_pot == 1 ]; then + pot1ref=pot.cube.ref + pot1cal=OUT.autotest/pot.cube + python3 $COMPARE_SCRIPT $pot1ref $pot1cal 3 + echo "ComparePot1_pass $?" >>$props_result_file +fi + +#------------------------------- +#echo $out_pot2 +#------------------------------- +if ! test -z "$out_pot" && [ $out_pot == 2 ]; then + pot1ref=potes.cube.ref + pot1cal=OUT.autotest/potes.cube + python3 $COMPARE_SCRIPT $pot1ref $pot1cal 8 + echo "ComparePot_pass $?" >>$props_result_file +fi + +#------------------------------- +# Electron localized function +# echo $out_elf +#------------------------------- +if ! test -z "$out_elf" && [ $out_elf == 1 ]; then + elf1ref=elftot.cube.ref + elf1cal=OUT.autotest/elftot.cube + python3 $COMPARE_SCRIPT $elf1ref $elf1cal 3 + echo "ComparePot1_pass $?" >>$props_result_file +fi + +} + +run_cube_props(){ + +#--------------------------------------- +# Charge density +#--------------------------------------- +#echo $out_chg +if ! test -z "$out_chg" && [ $out_chg -ge 1 ]; then + record_compare_result "$props_result_file" "chg.cube_pass" "chg.cube.ref" "OUT.autotest/chg.cube" 6 +fi + + +#--------------------------------------- +# SCAN exchange-correlation information +#echo $has_scan +#--------------------------------------- +if ! test -z "$has_scan" && [ $has_scan == "scan" ] && \ + ! test -z "$out_chg" && [ $out_chg == 1 ]; then + python3 $COMPARE_SCRIPT tau.cube.ref OUT.autotest/tau.cube 8 + echo "tau.cube_pass $?" >>$props_result_file +fi + +#--------------------------------------- +# local density of states +# echo $has_ldos +#--------------------------------------- +if ! test -z "$has_ldos" && [ $has_ldos == 1 ]; then + stm_bias=$(get_input_key_value "stm_bias" "OUT.autotest/INPUT.info") + python3 $COMPARE_SCRIPT LDOS.cube.ref OUT.autotest/LDOS_"$stm_bias"eV.cube 8 + echo "LDOS.cube_pass $?" >> $props_result_file +fi + +#--------------------------------------- +# wave functions in real space +# echo "$has_wfc_r" ## test out_wfc_r > 0 +#--------------------------------------- +if ! test -z "$has_wfc_r" && [ $has_wfc_r == 1 ]; then + if [[ ! -f OUT.autotest/running_scf.log ]];then + echo "Can't find file OUT.autotest/running_scf.log" + exit 1 + fi + nband=$(grep NBANDS OUT.autotest/running_scf.log|awk '{print $3}') + allgrid=$(grep "fft grid for wave functions" OUT.autotest/running_scf.log | awk -F "[=,\\\[\\\]]" '{print $3*$4*$5}') + for((band=0;band<$nband;band++));do + if [[ -f "OUT.autotest/wfc_realspace/wfc_realspace_0_$band" ]];then + variance_wfc_r=`sed -n "13,$"p OUT.autotest/wfc_realspace/wfc_realspace_0_$band | \ + awk -v all=$allgrid 'BEGIN {sumall=0} {for(i=1;i<=NF;i++) {sumall+=($i-1)*($i-1)}}\ + END {printf"%.5f",(sumall/all)}'` + echo "variance_wfc_r_0_$band $variance_wfc_r" >>$props_result_file + else + echo "Can't find file OUT.autotest/wfc_realspace/wfc_realspace_0_$band" + exit 1 + fi + done +fi + +#-------------------------------------------- +# wave functions in plane wave basis +# echo "$has_wfc_pw" ## test out_wfc_pw > 0 +#-------------------------------------------- +if ! test -z "$has_wfc_pw" && [ $has_wfc_pw == 1 ]; then + + # according to nspin value + if [ "$nspin" -eq 1 ]; then + filename="wfk1_pw.txt" + elif [ "$nspin" -eq 2 ]; then + filename="wfk1s1_pw.txt" + else + # other nspin cases + echo "Unsupported nspin value: $nspin" + exit 1 + fi + + full_path="OUT.autotest/$filename" + + if [[ ! -f "$full_path" ]];then + echo "Can't find file $full_path" + exit 1 + fi + awk 'BEGIN {max=0;read=0;band=1} + { + if(read==0 && $2 == "Band" && $3 == band){read=1} + else if(read==1 && $2 == "Band" && $3 == band) + {printf"Max_wfc_%d %.4f\n",band,max;read =0;band+=1;max=0} + else if(read==1) + { + for(i=1;i<=NF;i++) + { + if(sqrt($i*$i)>max) {max=sqrt($i*$i)} + } + } + }' "$full_path" >> "$props_result_file" +fi + + +#-------------------------------------------- +# wave functions in LCAO basis +# echo "$has_lowf" # test out_wfc_lcao > 0 +#-------------------------------------------- +if ! test -z "$has_lowf" && [ $has_lowf == 1 ]; then + if ! test -z "$gamma_only" && [ $gamma_only == 1 ]; then + wfc_cal=OUT.autotest/wf_nao.txt + wfc_ref=wf_nao.txt.ref + else # multi-k point case + if ! test -z "$out_app_flag" && [ $out_app_flag == 0 ]; then + wfc_name=wfk1g3_nao + input_file=WFC/wfk1g3_nao + else + wfc_name=wfk2_nao + input_file=wfk2_nao + fi + awk 'BEGIN {flag=999} + { + if($2 == "(band)") {flag=2;print $0} + else if(flag>0) {flag-=1;print $0} + else if(flag==0) + { + for(i=1;i<=NF/2;i++) + {printf "%.10e ",sqrt( $(2*i)*$(2*i)+$(2*i-1)*$(2*i-1) )}; + printf "\n" + } + else {print $0} + }' OUT.autotest/"$input_file".txt > OUT.autotest/"$wfc_name"_mod.txt + wfc_cal=OUT.autotest/"$wfc_name"_mod.txt + wfc_ref="$wfc_name"_mod.txt.ref + fi + + python3 $COMPARE_SCRIPT $wfc_cal $wfc_ref 8 -abs 1 + echo "Compare_wfc_lcao_pass $?" >>$props_result_file +fi + +} + +run_cube_tail_props(){ + +#-------------------------------------------- +# mulliken charge +#-------------------------------------------- +if ! test -z "$out_mul" && [ $out_mul == 1 ]; then + python3 $COMPARE_SCRIPT mulliken.txt.ref OUT.autotest/mulliken.txt 3 + echo "Compare_mulliken_pass $?" >>$props_result_file +fi + +#-------------------------------------------- +# Process .cube files for: +# 1. get_wf/get_pchg calculation tag (LCAO) +# 2. out_wfc_norm/out_wfc_re_im/out_pchg (PW) +#-------------------------------------------- +shopt -s nullglob +for pchg_ref in pchgi*.cube.ref; do + pchg_cube=${pchg_ref%.ref} + pchg_key=$(sanitize_result_key "${pchg_cube}_compare") + record_compare_result "$props_result_file" "$pchg_key" "$pchg_ref" "OUT.autotest/$pchg_cube" 6 +done +shopt -u nullglob + +need_process_cube=false +# Check if this is a LCAO calculation with get_wf/get_pchg +if [ $calculation == "get_wf" ] || [ $calculation == "get_pchg" ]; then + need_process_cube=true +fi +# Check if this is a PW calculation with out_wfc_norm/out_wfc_re_im +out_wfc_norm=$(get_input_key_value "out_wfc_norm" "INPUT") +out_wfc_re_im=$(get_input_key_value "out_wfc_re_im" "INPUT") +out_pchg=$(get_input_key_value "out_pchg" "INPUT") +if [ -n "$out_wfc_norm" ] || [ -n "$out_wfc_re_im" ] || [ -n "$out_pchg" ]; then + need_process_cube=true +fi +# Process .cube files if needed +if [ "$need_process_cube" = true ]; then + cubefiles=$(ls OUT.autotest/ | grep -E '.cube$') + wavefunction_re_files=() + + if [ -z "$cubefiles" ]; then + echo "Error: No .cube files found in OUT.autotest/" + exit 1 + else + for cube in $cubefiles; do + if [[ "$cube" =~ ^wfi[0-9]+s[0-9]+(k[0-9]+)?re[.]cube$ ]]; then + wavefunction_re_files+=("$cube") + continue + fi + if [[ "$cube" =~ ^wfi[0-9]+s[0-9]+(k[0-9]+)?im[.]cube$ ]]; then + continue + fi + total_chg=$(python3 "$CUBE_TOOL" integrate "OUT.autotest/$cube") + echo "$cube $total_chg" >> $props_result_file + done + fi + + for cube in "${wavefunction_re_files[@]}"; do + if [[ "$cube" =~ ^wfi([0-9]+)s([0-9]+)(k[0-9]+)?re[.]cube$ ]]; then + band=${BASH_REMATCH[1]} + spin=${BASH_REMATCH[2]} + kpoint=${BASH_REMATCH[3]} + state_prefix=${cube%re.cube} + fingerprint_args=( + "OUT.autotest/$cube" + "OUT.autotest/${state_prefix}im.cube" + ) + if [ "$nspin" = "4" ]; then + if [ "$spin" != "1" ]; then + continue + fi + lower_prefix="wfi${band}s2${kpoint}" + fingerprint_args+=( + "OUT.autotest/${lower_prefix}re.cube" + "OUT.autotest/${lower_prefix}im.cube" + ) + result_prefix="wfi${band}${kpoint}_spinor_wfc_fp" + else + result_prefix="${state_prefix}_wfc_fp" + fi + + if fingerprint=$(python3 "$CUBE_TOOL" fingerprint-wfc "${fingerprint_args[@]}"); then + while read -r metric value; do + echo "${result_prefix}_${metric} $value" >> "$props_result_file" + done <<< "$fingerprint" + else + echo "Error: Failed to generate wavefunction fingerprint for $state_prefix" + exit 1 + fi + fi + done +fi + +# Check the pointwise Pauli identities when all PW nspin=4 spinor outputs are available. +nspin=$(get_input_key_value "nspin" "INPUT") +if_separate_k=$(get_input_key_value "if_separate_k" "INPUT") +if [ "$nspin" = "4" ] && { [ "$if_separate_k" = "1" ] || [ "$if_separate_k" = "true" ]; } \ + && [ -n "$out_wfc_norm" ] && [ -n "$out_wfc_re_im" ] && [ -n "$out_pchg" ]; then + if python3 "$CUBE_TOOL" check-spinor OUT.autotest; then + echo "pw_spinor_cube_identity 0" >> "$props_result_file" + else + echo "pw_spinor_cube_identity 1" >> "$props_result_file" + fi +fi + +} diff --git a/tests/integrate/tools/catch_deepks_properties.sh b/tests/integrate/validation_tools/props_deepks.sh similarity index 75% rename from tests/integrate/tools/catch_deepks_properties.sh rename to tests/integrate/validation_tools/props_deepks.sh index d9039d261e9..918eee161ac 100644 --- a/tests/integrate/tools/catch_deepks_properties.sh +++ b/tests/integrate/validation_tools/props_deepks.sh @@ -1,30 +1,15 @@ #!/bin/bash -COMPARE_SCRIPT="../../integrate/tools/CompareFile.py" - -sum_file(){ - line=`grep -vc '^$' $1` - inc=1 - if ! test -z $2; then - inc=$2 - fi - sum=0.0 - for (( num=1 ; num<=$line ; num+=$inc ));do - value_line=(` sed -n "$num p" $1 | head -n1 `) - colume=`echo ${#value_line[@]}` - for (( col=0 ; col<$colume ; col++ ));do - value=`echo ${value_line[$col]}` - sum=`awk 'BEGIN {x='$sum';y='$value';printf "%.6f\n",x+sqrt(y*y)}'` - done - done - echo $sum -} -get_input_key_value(){ - key=$1 - inputf=$2 - value=$(awk -v key=$key '{if($1==key) a=$2} END {print a}' $inputf) - echo $value -} +# DeePKS label / descriptor property collectors. +# +# Sourced by catch_properties.sh after props_common.sh; can also be sourced +# by the catch_deepks_properties.sh standalone entry. Relies on the shared +# helpers from props_common.sh ($COMPARE_SCRIPT, sum_file, +# get_input_key_value) and appends result lines to $props_result_file. +# +# The get_sum_*.py helpers below are invoked with a "../tools/" path that is +# relative to the test-case CWD (tests/09_DeePKS/), where it resolves +# to tests/09_DeePKS/tools/; this is intentional and must stay CWD-relative. # General function to process npy files process_npy() { @@ -33,7 +18,7 @@ process_npy() { local file_prefix=$3 local base_prefix=$4 local output_name=$5 - + local total="0" local file_pattern="" local base_pattern="" @@ -43,7 +28,7 @@ process_npy() { if [ "$file_prefix" = "ftot" ] || [ "$file_prefix" = "stot" ]; then is_force_or_stress=1 fi - + # Determine file pattern based on mode if [ "$mode" = "multi" ]; then # multi mode: multiple files with _e* suffix from different electronic steps, unless for force and stress @@ -59,19 +44,23 @@ process_npy() { file_pattern="OUT.autotest/deepks_${file_prefix}.npy" base_pattern="OUT.autotest/deepks_${base_prefix}.npy" fi - + # Process files for file in $file_pattern; do if [ ! -f "$file" ]; then continue fi - + # Get step number for multi mode + # Force/stress multi-mode files (ftot.npy, stot.npy) carry no e + # suffix, so grep legitimately finds no match and returns 1. Tolerate + # the empty match explicitly so the step extraction is also safe when + # this module is sourced under `bash -e`. local step="" if [ "$mode" = "multi" ]; then - step=$(basename "$file" | grep -oP 'e\d+') + step=$(basename "$file" | grep -oP 'e\d+' || true) fi - + # Get corresponding base file local base_file="" if [ "$mode" = "multi" ] && [ "$is_force_or_stress" -ne 1 ]; then @@ -79,7 +68,7 @@ process_npy() { else base_file=$base_pattern fi - + # Calculate value based on operation if [ "$op" = "abs" ]; then val=$(python3 ../tools/get_sum_abs.py "$file") @@ -90,7 +79,7 @@ process_npy() { else val=0 fi - + if [ "$mode" = "multi" ]; then #echo "total: $total, val: $val" total=$(echo "$total + $val" | bc) @@ -98,7 +87,7 @@ process_npy() { total=$val fi done - + echo "$output_name $total" >>$6 } @@ -107,8 +96,8 @@ process_many_npys() { local mode=$1 local suffix=$2 local output_file=$3 - - # energy + + # energy process_npy "$mode" "abs" "etot" "" "deepks_e_label$suffix" "$output_file" process_npy "$mode" "delta" "etot" "ebase" "deepks_edelta$suffix" "$output_file" @@ -118,12 +107,12 @@ process_many_npys() { process_npy "$mode" "delta" "otot" "obase" "deepks_odelta$suffix" "$output_file" process_npy "$mode" "numpy" "orbpre" "" "deepks_oprec$suffix" "$output_file" fi - + # For deepks_v_delta > 0 if ! test -z "$deepks_v_delta" && [ $deepks_v_delta -gt 0 ]; then process_npy "$mode" "abs" "htot" "" "deepks_h_label$suffix" "$output_file" process_npy "$mode" "delta" "htot" "hbase" "deepks_vdelta$suffix" "$output_file" - + if [ $deepks_v_delta == 1 ]; then process_npy "$mode" "abs" "vdpre" "" "deepks_vdp$suffix" "$output_file" elif [ $deepks_v_delta == 2 ]; then @@ -136,7 +125,7 @@ process_many_npys() { process_npy "$mode" "abs" "ftot" "" "deepks_f_label$suffix" "$output_file" process_npy "$mode" "delta" "ftot" "fbase" "deepks_fdelta$suffix" "$output_file" fi - + # For cal_stress = 1 if ! test -z "$has_stress" && [ $has_stress == 1 ]; then process_npy "$mode" "abs" "stot" "" "deepks_s_label$suffix" "$output_file" @@ -144,7 +133,11 @@ process_many_npys() { fi } +run_deepks_props(){ + # Main script +# cal_force/cal_stress are also parsed by props_init(); they are re-read here +# because the standalone catch_deepks_properties.sh entry skips props_init(). has_force=$(get_input_key_value "cal_force" "INPUT") has_stress=$(get_input_key_value "cal_stress" "INPUT") deepks_out_labels=$(get_input_key_value "deepks_out_labels" "INPUT") @@ -161,52 +154,56 @@ if ! test -z "$deepks_scf" && [ $deepks_scf == 1 ]; then sed '/n_des/d' OUT.autotest/deepks_desc.dat > des_tmp.txt total_des=$(sum_file des_tmp.txt 5) rm des_tmp.txt - echo "deepks_desc $total_des" >>$1 - - process_npy "single" "abs" "dm_eig" "" "deepks_dm_eig" "$1" + echo "deepks_desc $total_des" >>$props_result_file + + process_npy "single" "abs" "dm_eig" "" "deepks_dm_eig" "$props_result_file" fi #--------------------------------------------------------------------------- # Test for deepks_out_labels = 1 and deepks_out_freq_elec > 0 #--------------------------------------------------------------------------- if ! test -z "$deepks_out_labels" && [ $deepks_out_labels == 1 ]; then - process_many_npys "single" "" "$1" + process_many_npys "single" "" "$props_result_file" # gradvx and gvepsl not considered in deepks_out_freq_elec > 0, so not in process_many_npys # For cal_force = 1 if ! test -z "$has_force" && [ $has_force == 1 ]; then - process_npy "single" "abs" "gradvx" "" "deepks_fpre" "$1" + process_npy "single" "abs" "gradvx" "" "deepks_fpre" "$props_result_file" fi - + # For cal_stress = 1 if ! test -z "$has_stress" && [ $has_stress == 1 ]; then - process_npy "single" "abs" "gvepsl" "" "deepks_spre" "$1" + process_npy "single" "abs" "gvepsl" "" "deepks_spre" "$props_result_file" fi # For deepks_v_delta < 0 + # CompareFile.py exits 1 when the files differ; capture the status in a + # named variable so it is recorded even when sourced under `bash -e`. if ! test -z "$deepks_v_delta" && [ $deepks_v_delta -lt 0 ]; then - python3 $COMPARE_SCRIPT "deepks_hrtot.csr.ref" "OUT.autotest/deepks_hrtot.csr" 8 - echo "deepks_hr_label_pass $?" >>$1 - python3 $COMPARE_SCRIPT "deepks_hrdelta.csr.ref" "OUT.autotest/deepks_hrdelta.csr" 8 - echo "deepks_vdelta_r_pass $?" >>$1 + hr_pass=0 + python3 $COMPARE_SCRIPT "deepks_hrtot.csr.ref" "OUT.autotest/deepks_hrtot.csr" 8 || hr_pass=$? + echo "deepks_hr_label_pass $hr_pass" >>$props_result_file + hrdelta_pass=0 + python3 $COMPARE_SCRIPT "deepks_hrdelta.csr.ref" "OUT.autotest/deepks_hrdelta.csr" 8 || hrdelta_pass=$? + echo "deepks_vdelta_r_pass $hrdelta_pass" >>$props_result_file # For deepks_v_delta = -1 if [ $deepks_v_delta -eq -1 ]; then - process_npy "single" "abs" "vdrpre" "" "deepks_vdrp" "$1" + process_npy "single" "abs" "vdrpre" "" "deepks_vdrp" "$props_result_file" fi # For deepks_v_delta = -2 if [ $deepks_v_delta -eq -2 ]; then - process_npy "single" "abs" "phialpha_r" "" "deepks_phialpha_r" "$1" - process_npy "single" "numpy" "gevdm" "" "deepks_gevdm" "$1" + process_npy "single" "abs" "phialpha_r" "" "deepks_phialpha_r" "$props_result_file" + process_npy "single" "numpy" "gevdm" "" "deepks_gevdm" "$props_result_file" fi fi - + # Process deepks_out_freq_elec > 0 if [ ! -z "$deepks_out_freq_elec" ] && [ $deepks_out_freq_elec -gt 0 ]; then - process_many_npys "multi" "_elec" "$1" + process_many_npys "multi" "_elec" "$props_result_file" if ! test -z "$deepks_v_delta" && [[ $deepks_v_delta -gt 0 ]]; then - process_npy "single" "abs" "overlap" "" "deepks_overlap" "$1" - process_npy "multi" "abs" "overlap" "" "deepks_overlap_elec" "$1" - + process_npy "single" "abs" "overlap" "" "deepks_overlap" "$props_result_file" + process_npy "multi" "abs" "overlap" "" "deepks_overlap_elec" "$props_result_file" + fi fi fi @@ -215,24 +212,26 @@ fi # Test for deepks_out_labels = 2 #--------------------------------------------------------------------------- if ! test -z "$deepks_out_labels" && [ $deepks_out_labels == 2 ]; then - process_npy "single" "numpy" "atom" "" "deepks_atom" "$1" - process_npy "single" "numpy" "box" "" "deepks_box" "$1" - process_npy "single" "numpy" "energy" "" "deepks_energy" "$1" - + process_npy "single" "numpy" "atom" "" "deepks_atom" "$props_result_file" + process_npy "single" "numpy" "box" "" "deepks_box" "$props_result_file" + process_npy "single" "numpy" "energy" "" "deepks_energy" "$props_result_file" + if ! test -z "$has_force" && [ $has_force == 1 ]; then - process_npy "single" "numpy" "force" "" "deepks_force" "$1" + process_npy "single" "numpy" "force" "" "deepks_force" "$props_result_file" fi - + if ! test -z "$has_stress" && [ $has_stress == 1 ]; then - process_npy "single" "numpy" "stress" "" "deepks_stress" "$1" + process_npy "single" "numpy" "stress" "" "deepks_stress" "$props_result_file" fi - + if ! test -z "$deepks_bandgap" && [ $deepks_bandgap == 1 ]; then - process_npy "single" "numpy" "orbital" "" "deepks_orbital" "$1" + process_npy "single" "numpy" "orbital" "" "deepks_orbital" "$props_result_file" fi - + if ! test -z "$deepks_v_delta" && [[ $deepks_v_delta -gt 0 ]]; then - process_npy "single" "numpy" "hamiltonian" "" "deepks_hamiltonian" "$1" - process_npy "single" "numpy" "overlap" "" "deepks_overlap" "$1" + process_npy "single" "numpy" "hamiltonian" "" "deepks_hamiltonian" "$props_result_file" + process_npy "single" "numpy" "overlap" "" "deepks_overlap" "$props_result_file" fi -fi \ No newline at end of file +fi + +} diff --git a/tests/integrate/validation_tools/props_mat.sh b/tests/integrate/validation_tools/props_mat.sh new file mode 100644 index 00000000000..f114b5bbbc4 --- /dev/null +++ b/tests/integrate/validation_tools/props_mat.sh @@ -0,0 +1,345 @@ +#!/bin/bash + +# Matrix/operator property collectors (S, H(k), H(R), XC, r/T/dH(R), DM, ...). +# +# Sourced by catch_properties.sh after props_common.sh; relies on the global +# switch variables set by props_init() and appends result lines to +# $props_result_file. +# +# The original monolithic script interleaved these checks with the cube +# collectors (props_cube.sh). To preserve the exact emission order of result +# lines this module exposes three hooks, called by the entry point at the +# matching positions: +# run_mat_dm1_props() - out_dm1 block (before the pot/ELF cube blocks) +# run_mat_props() - everything from S(k) through dH(k) term matrices +# run_mat_dm_props() - out_dm block (between wfc_lcao and mulliken) + +run_mat_dm1_props(){ + +#------------------------------- +# echo $out_dm1 +#------------------------------- +if ! test -z "$out_dm1" && [ $out_dm1 == 1 ]; then + dm1ref=dmr_nao.csr.ref + dm1cal=OUT.autotest/dmr_nao.csr + python3 $COMPARE_SCRIPT $dm1ref $dm1cal 8 + echo "CompareDM1_pass $?" >>$props_result_file +fi + +} + +run_mat_props(){ + +#------------------------------- +# Overlap matrix +# calculation == get_s +#------------------------------- +if [ "$calculation" == "get_s" ]; then + sref=sr_nao.csr.ref + scal=OUT.autotest/sr_nao.csr + python3 $COMPARE_SCRIPT $sref $scal 8 + echo "CompareS_pass $?" >>$props_result_file +fi + +#------------------------------- +# Partial band structure +# echo $out_pband +#------------------------------- +if ! test -z "$out_pband" && [ $out_pband == 1 ]; then + orbref=refOrbital + orbcal=OUT.autotest/Orbital + python3 $COMPARE_SCRIPT $orbref $orbcal 8 + echo "CompareOrb_pass $?" >>$props_result_file +fi + +#------------------------------- +# Wannier90 information +# echo $toW90 +#------------------------------- +if ! test -z "$toW90" && [ $toW90 == 1 ]; then + amnref=diamond.amn + amncal=OUT.autotest/diamond.amn + mmnref=diamond.mmn + mmncal=OUT.autotest/diamond.mmn + eigref=diamond.eig + eigcal=OUT.autotest/diamond.eig + sed -i '1d' $amncal + sed -i '1d' $mmncal + python3 $COMPARE_SCRIPT $amnref $amncal 1 -abs 8 + echo "CompareAMN_pass $?" >>$props_result_file + python3 $COMPARE_SCRIPT $mmnref $mmncal 1 -abs 8 + echo "CompareMMN_pass $?" >>$props_result_file + python3 $COMPARE_SCRIPT $eigref $eigcal 8 + echo "CompareEIG_pass $?" >>$props_result_file +fi + +#------------------------------- +# Total DOS +# echo total_dos +# echo $has_band +#------------------------------- +if ! test -z "$has_band" && [ $has_band == 1 ]; then + bandref=band.txt.ref + bandcal=OUT.autotest/band.txt + python3 $COMPARE_SCRIPT $bandref $bandcal 8 + echo "CompareBand_pass $?" >>$props_result_file +fi + +#-------------------------------- +# Hamiltonian and overlap matrix +# echo $has_hs +#-------------------------------- +if ! test -z "$has_hs" && [ $has_hs == 1 ]; then + if ! test -z "$gamma_only" && [ $gamma_only == 1 ]; then + # ========== Γ-point (single k-point) calculation ========== + if ! test -z "$nspin" && [ $nspin == 2 ]; then + # nspin=2 (spin-polarized): compare hks1 + hks2 Hamiltonian + sk overlap matrix + h1ref=hks1_nao.txt.ref + h1cal=OUT.autotest/hks1_nao.txt + h2ref=hks2_nao.txt.ref + h2cal=OUT.autotest/hks2_nao.txt + sref=sk_nao.txt.ref + scal=OUT.autotest/sk_nao.txt + # Compare Hamiltonian matrix for spin 1 + python3 $COMPARE_SCRIPT $h1ref $h1cal 6 + echo "CompareH1_pass $?" >>$props_result_file + # Compare Hamiltonian matrix for spin 2 + python3 $COMPARE_SCRIPT $h2ref $h2cal 6 + echo "CompareH2_pass $?" >>$props_result_file + # Compare overlap matrix + python3 $COMPARE_SCRIPT $sref $scal 8 + echo "CompareS_pass $?" >>$props_result_file + elif ! test -z "$nspin" && [ $nspin == 4 ]; then + # nspin=4 : do nothing, only matching condition without any operation + true + else + # nspin=1 (non-spin-polarized, default case): compare single hk + sk matrix set + href=hk_nao.txt.ref + hcal=OUT.autotest/hk_nao.txt + sref=sk_nao.txt.ref + scal=OUT.autotest/sk_nao.txt + # Compare Hamiltonian matrix + python3 $COMPARE_SCRIPT $href $hcal 6 + echo "CompareH_pass $?" >>$props_result_file + # Compare overlap matrix + python3 $COMPARE_SCRIPT $sref $scal 8 + echo "CompareS_pass $?" >>$props_result_file + fi + else + # ========== Multiple k-points calculation ========== + if ! test -z "$nspin" && [ $nspin == 2 ]; then + # nspin=2 (spin-polarized): compare spin-up/spin-down H(k) and S(k) at the second k-point + h1ref=hk2s1_nao.txt.ref + h1cal=OUT.autotest/hk2s1_nao.txt + h2ref=hk2s2_nao.txt.ref + h2cal=OUT.autotest/hk2s2_nao.txt + sref=sk2_nao.txt.ref + scal=OUT.autotest/sk2_nao.txt + # Compare Hamiltonian matrix for spin 1 + python3 $COMPARE_SCRIPT $h1ref $h1cal 6 + echo "CompareH1_pass $?" >>$props_result_file + # Compare Hamiltonian matrix for spin 2 + python3 $COMPARE_SCRIPT $h2ref $h2cal 6 + echo "CompareH2_pass $?" >>$props_result_file + # Compare overlap matrix + python3 $COMPARE_SCRIPT $sref $scal 8 + echo "CompareS_pass $?" >>$props_result_file + elif ! test -z "$nspin" && [ $nspin == 4 ]; then + # nspin=4 : do nothing, only matching condition without any operation + true + else + # nspin=1 (non-spin-polarized, default case): compare single hk2 + sk2 matrix set + href=hk2_nao.txt.ref + hcal=OUT.autotest/hk2_nao.txt + sref=sk2_nao.txt.ref + scal=OUT.autotest/sk2_nao.txt + # Compare Hamiltonian matrix + python3 $COMPARE_SCRIPT $href $hcal 6 + echo "CompareH_pass $?" >>$props_result_file + # Compare overlap matrix + python3 $COMPARE_SCRIPT $sref $scal 8 + echo "CompareS_pass $?" >>$props_result_file + fi + fi +elif ! test -z "$has_hs" && [ $has_hs == 2 ]; then + HSK_BINARY_COMPARE="$PROPS_TOOLS_DIR/compare_hsk_binary.py" + if ! test -z "$gamma_only" && [ $gamma_only == 1 ]; then + HSK_TEXT_REFERENCE_DIR="../scf_out_hk" + python3 $HSK_BINARY_COMPARE OUT.autotest/hk_nao.dat "$HSK_TEXT_REFERENCE_DIR/hk_nao.txt.ref" real 3 + echo "CompareH_pass $?" >>$props_result_file + python3 $HSK_BINARY_COMPARE OUT.autotest/sk_nao.dat "$HSK_TEXT_REFERENCE_DIR/sk_nao.txt.ref" real 3 + echo "CompareS_pass $?" >>$props_result_file + else + HSK_TEXT_REFERENCE_DIR="../scf_out_hsk" + python3 $HSK_BINARY_COMPARE OUT.autotest/hk2_nao.dat "$HSK_TEXT_REFERENCE_DIR/hk2_nao.txt.ref" complex 3 + echo "CompareH_pass $?" >>$props_result_file + python3 $HSK_BINARY_COMPARE OUT.autotest/sk2_nao.dat "$HSK_TEXT_REFERENCE_DIR/sk2_nao.txt.ref" complex 3 + echo "CompareS_pass $?" >>$props_result_file + fi +fi + +#-------------------------------- +# exchange-correlation potential +#-------------------------------- +if ! test -z "$has_xc" && [ $has_xc == 1 ]; then + if ! test -z "$gamma_only" && [ $gamma_only == 1 ]; then + xcref=vxc_nao.txt.ref + xccal=OUT.autotest/vxc_nao.txt + else + xcref=vxck2_nao.txt.ref + xccal=OUT.autotest/vxck2_nao.txt + fi + oeref=vxc_out.ref + oecal=OUT.autotest/vxc_out.dat + python3 $COMPARE_SCRIPT $xcref $xccal 4 + echo "CompareVXC_pass $?" >>$props_result_file + python3 $COMPARE_SCRIPT $oeref $oecal 5 + echo "CompareOrbXC_pass $?" >>$props_result_file +fi + +#-------------------------------- +# exchange-correlation potential +#-------------------------------- +if ! test -z "$has_xc2" && [ $has_xc2 == 1 ]; then + xc2ref=Vxc_R_spin0.ref + xc2cal=OUT.autotest/Vxc_R_spin0.csr + python3 $COMPARE_SCRIPT $xc2ref $xc2cal 8 + echo "CompareVXC_R_pass $?" >>$props_result_file +fi + +#-------------------------------- +# separate terms in band enegy +#-------------------------------- +if ! test -z "$has_eband_separate" && [ $has_eband_separate == 1 ]; then + ekref=kinetic_out.ref + ekcal=OUT.autotest/kinetic_out.dat + python3 $COMPARE_SCRIPT $ekref $ekcal 4 + echo "CompareOrbKinetic_pass $?" >>$props_result_file + vlref=vpp_local_out.ref + vlcal=OUT.autotest/vpp_local_out.dat + python3 $COMPARE_SCRIPT $vlref $vlcal 4 + echo "CompareOrbVL_pass $?" >>$props_result_file + vnlref=vpp_nonlocal_out.ref + vnlcal=OUT.autotest/vpp_nonlocal_out.dat + python3 $COMPARE_SCRIPT $vnlref $vnlcal 4 + echo "CompareOrbVNL_pass $?" >>$props_result_file + vhref=vhartree_out.ref + vhcal=OUT.autotest/vhartree_out.dat + python3 $COMPARE_SCRIPT $vhref $vhcal 4 + echo "CompareOrbVHartree_pass $?" >>$props_result_file +fi + +#----------------------------------- +# Hamiltonian and overlap matrices +#----------------------------------- +#echo $has_hs2 +if ! test -z "$has_hs2" && [ $has_hs2 == 1 ]; then + python3 $COMPARE_SCRIPT hrs1_nao.csr.ref OUT.autotest/hrs1_nao.csr 8 + echo "CompareHR_pass $?" >>$props_result_file + if ! test -z "$nspin" && [ "$nspin" -eq 2 ]; then + python3 $COMPARE_SCRIPT hrs2_nao.csr.ref OUT.autotest/hrs2_nao.csr 8 + echo "CompareHR2_pass $?" >>$props_result_file + fi + python3 $COMPARE_SCRIPT sr_nao.csr.ref OUT.autotest/sr_nao.csr 8 + echo "CompareSR_pass $?" >>$props_result_file +elif ! test -z "$has_hs2" && [ "$has_hs2" == 2 ]; then + HSR_BINARY_COMPARE="$PROPS_TOOLS_DIR/compare_hsr_binary.py" + python3 $HSR_BINARY_COMPARE OUT.autotest/hrs1_nao.dat hrs1_nao.csr.ref real 4 + echo "CompareHR_pass $?" >>$props_result_file + if ! test -z "$nspin" && [ "$nspin" -eq 2 ]; then + python3 $HSR_BINARY_COMPARE OUT.autotest/hrs2_nao.dat hrs2_nao.csr.ref real 4 + echo "CompareHR2_pass $?" >>$props_result_file + fi + python3 $HSR_BINARY_COMPARE OUT.autotest/sr_nao.dat sr_nao.csr.ref real 4 + echo "CompareSR_pass $?" >>$props_result_file +fi + +#----------------------------------- +# H(R), S(R), and DM(R) matrices in NPZ format +#----------------------------------- +if { ! test -z "$out_hsr" && [ "$out_hsr" == 3 ]; } || { ! test -z "$out_hsr_npz" && [ "$out_hsr_npz" == 1 ]; }; then + test -f OUT.autotest/sr_nao.npz + echo "OutputSRNPZ_pass $?" >>$props_result_file +fi + +if { ! test -z "$out_hr_npz" && [ "$out_hr_npz" == 1 ]; } \ + || { ! test -z "$out_hsr" && [ "$out_hsr" == 3 ]; } \ + || { ! test -z "$out_hsr_npz" && [ "$out_hsr_npz" == 1 ]; }; then + test -f OUT.autotest/hrs1_nao.npz + echo "OutputHRNPZ_pass $?" >>$props_result_file +fi + +if ! test -z "$out_dm_npz" && [ "$out_dm_npz" == 1 ]; then + test -f OUT.autotest/output_DM0.npz + echo "OutputDMNPZ_pass $?" >>$props_result_file +fi + +#----------------------------------- +# matrix +#----------------------------------- +#echo $has_mat_r +if ! test -z "$has_mat_r" && [ $has_mat_r == 1 ]; then + python3 $COMPARE_SCRIPT rr_nao.txt.ref OUT.autotest/rr_nao.txt 8 + echo "ComparerR_pass $?" >>$props_result_file +fi + +#----------------------------------- +# matrix +#----------------------------------- +#echo $has_mat_t +if ! test -z "$has_mat_t" && [ $has_mat_t == 1 ]; then + python3 $COMPARE_SCRIPT tr_nao.csr.ref OUT.autotest/tr_nao.csr 8 + echo "ComparerTR_pass $?" >>$props_result_file +fi + +#----------------------------------- +# Asynchronous overlap matrix for Hefei-NAMD +#----------------------------------- +#echo $has_mat_syns +if ! test -z "$has_mat_syns" && [ $has_mat_syns == 1 ]; then + python3 $COMPARE_SCRIPT syns_nao.csr.ref OUT.autotest/syns_nao.csr 8 + echo "CompareSYNS_pass $?" >>$props_result_file +fi + +#----------------------------------- +# matrix +#----------------------------------- +#echo $has_mat_dh +if ! test -z "$has_mat_dh" && [ $has_mat_dh == 1 ] && [ $gamma_only != 1 ]; then + python3 $COMPARE_SCRIPT dhrxs1_nao.csr.ref OUT.autotest/dhrxs1_nao.csr 8 + echo "ComparerdHRx_pass $?" >>$props_result_file + python3 $COMPARE_SCRIPT dhrys1_nao.csr.ref OUT.autotest/dhrys1_nao.csr 8 + echo "ComparerdHRy_pass $?" >>$props_result_file + python3 $COMPARE_SCRIPT dhrzs1_nao.csr.ref OUT.autotest/dhrzs1_nao.csr 8 + echo "ComparerdHRz_pass $?" >>$props_result_file +fi + +#----------------------------------- +# d (k) matrix +#----------------------------------- +#echo $has_mat_dh_terms +if ! test -z "$has_mat_dh" && [ $has_mat_dh == 1 ]; then + shopt -s nullglob + for reffile in dhk_ref/*.txt; do + fname=$(basename "$reffile") + key=$(sanitize_result_key "Compare_${fname%.txt}") + record_compare_result "$props_result_file" "${key}_pass" "$reffile" "OUT.autotest/$fname" 8 + done + shopt -u nullglob +fi + +} + +run_mat_dm_props(){ + +#-------------------------------------------- +# density matrix information +#-------------------------------------------- +if ! test -z "$out_dm" && [ $out_dm == 1 ]; then + dmfile=OUT.autotest/dm_nao.txt + dmref=dm_nao.txt.ref + python3 $COMPARE_SCRIPT $dmref $dmfile 5 + echo "DM_different $?" >>$props_result_file +fi + +} diff --git a/tests/integrate/validation_tools/props_ml.sh b/tests/integrate/validation_tools/props_ml.sh new file mode 100644 index 00000000000..dd949f065bf --- /dev/null +++ b/tests/integrate/validation_tools/props_ml.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# Machine-learning / advanced-method property collectors. +# +# Sourced by catch_properties.sh after props_common.sh; relies on the global +# switch variables set by props_init() and appends result lines to +# $props_result_file. +# +# Hooks (called by the entry point at the matching positions): +# run_ml_descriptor_props() - MLKEDF .npy descriptor means +# run_ml_rpa_props() - RPA total energy and librpa output compare +# run_ml_lr_props() - linear-response excitation energies +# run_ml_rdmft_props() - RDMFT energy-term extraction + +run_ml_descriptor_props(){ + +#-------------------------------------------- +# ML gene data descriptors (.npy) +#-------------------------------------------- +descriptor_dir="OUT.autotest/MLKEDF_Descriptors" +if [ -d "$descriptor_dir" ]; then + python3 $COLLECT_NPY_MEANS "$descriptor_dir" >> "$props_result_file" +fi + +} + +run_ml_rpa_props(){ + +#-------------------------------------------- +# random phase approximation +#-------------------------------------------- +if ! test -z "$run_rpa" && [ $run_rpa == 1 ]; then + Etot_without_rpa=`grep Etot_without_rpa log.txt | awk 'BEGIN{FS=":"} {print $2}' ` + echo "Etot_without_rpa $Etot_without_rpa" >> $props_result_file + rpa_outdir=$(get_input_key_value "rpa_outdir" "INPUT") + if [ -z "$rpa_outdir" ]; then + rpa_outdir="./OUT.librpa" + fi + rpa_outdir=${rpa_outdir%/} + shopt -s nullglob + rpa_ref_files=(refcoulomb_*.txt refCs_*.txt refshrink_sinvS_*.txt) + if [ ${#rpa_ref_files[@]} -gt 0 ]; then + IFS=$'\n' rpa_ref_files=($(printf '%s\n' "${rpa_ref_files[@]}" | LC_ALL=C sort)) + unset IFS + for onref in "${rpa_ref_files[@]}"; do + oncal_name=${onref#ref} + oncal="$rpa_outdir/$oncal_name" + compare_key="CompareRPA_$(sanitize_result_key "$oncal_name")_pass" + record_compare_result "$props_result_file" "$compare_key" "$onref" "$oncal" 8 1 + done + fi + shopt -u nullglob +fi + +} + +run_ml_lr_props(){ + +#-------------------------------------------- +# Linear response function +#-------------------------------------------- +if [ $is_lr == 1 ]; then + shopt -s nullglob + lr_files=(OUT.autotest/trans_analysis_*_tda.dat) + if [ ${#lr_files[@]} -gt 0 ]; then + cat "${lr_files[@]}" | awk '/Excitation Energy/{p=1; next} p && /^[[:space:]]*[0-9]+[[:space:]]/{printf "excitationenergyref%d %.6f\n", ++n, $2} /Occupied orbital/{p=0}' >>$props_result_file + fi + shopt -u nullglob +fi + +} + +run_ml_rdmft_props(){ + +#-------------------------------------------- +# Check RDMFT method +#-------------------------------------------- +if ! test -z "$rdmft" && [[ $rdmft == 1 ]]; then + echo "" >>$props_result_file + echo "The following energy units are in Rydberg:" >>$props_result_file + + E_TV_RDMFT=$(grep "E_TV_RDMFT" "$running_path" | tail -1 | awk '{print $2}') + echo "E_TV_RDMFT_ref $E_TV_RDMFT" >>$props_result_file + + E_hartree_RDMFT=$(grep "E_hartree_RDMFT" "$running_path" | tail -1 | awk '{print $2}') + echo "E_hartree_RDMFT_ref $E_hartree_RDMFT" >>$props_result_file + + Exc_cwp22_RDMFT=$(grep "Exc_cwp22_RDMFT" "$running_path" | tail -1 | awk '{print $2}') + echo "Exc_cwp22_RDMFT_ref $Exc_cwp22_RDMFT" >>$props_result_file + + E_Ewald=$(grep "E_Ewald" "$running_path" | tail -1 | awk '{print $2}') + echo "E_Ewald_ref $E_Ewald" >>$props_result_file + + E_entropy=$(grep "E_entropy(-TS)" "$running_path" | tail -1 | awk '{print $2}') + echo "E_entropy_ref $E_entropy" >>$props_result_file + + E_descf=$(grep "E_descf" "$running_path" | tail -1 | awk '{print $2}') + echo "E_descf_ref $E_descf" >>$props_result_file + + Etotal_RDMFT=$(grep "Etotal_RDMFT" "$running_path" | tail -1 | awk '{print $2}') + echo "Etotal_RDMFT_ref $Etotal_RDMFT" >>$props_result_file + + Exc_ksdft=$(grep "Exc_ksdft" "$running_path" | tail -1 | awk '{print $2}') + echo "Exc_ksdft_ref $Exc_ksdft" >>$props_result_file + + E_exx_ksdft=$(grep "E_exx_ksdft" "$running_path" | tail -1 | awk '{print $2}') + echo "E_exx_ksdft_ref $E_exx_ksdft" >>$props_result_file + + echo "" >>$props_result_file +fi + +} diff --git a/tests/integrate/validation_tools/props_tddft.sh b/tests/integrate/validation_tools/props_tddft.sh new file mode 100644 index 00000000000..5d3f7d1eace --- /dev/null +++ b/tests/integrate/validation_tools/props_tddft.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Real-time TDDFT property collectors. +# +# Sourced by catch_properties.sh after props_common.sh; relies on the global +# switch variables set by props_init() and appends result lines to +# $props_result_file. + +run_tddft_props(){ + +#-------------------------------------------- +# check currents in rt-TDDFT +#-------------------------------------------- +if ! test -z "$out_current" && [ $out_current ]; then + current1ref=current_tot.txt.ref + current1cal=OUT.autotest/current_tot.txt + python3 $COMPARE_SCRIPT $current1ref $current1cal 10 + echo "CompareCurrent_pass $?" >>$props_result_file +fi + +#-------------------------------------------- +# Check electric fields in rt-TDDFT +#-------------------------------------------- +if ! test -z "$out_efield" && [ "$out_efield" == 1 ]; then + efield_refs=(efield_*.txt.ref) + if [ ! -e "${efield_refs[0]}" ]; then + echo "CompareEfieldReference_pass 1" >>$props_result_file + else + for efield_ref in "${efield_refs[@]}"; do + efield_name=${efield_ref%.ref} + efield_key=$(sanitize_result_key "$efield_name") + record_compare_result "$props_result_file" "Compare${efield_key}_pass" "$efield_ref" "OUT.autotest/$efield_name" 8 + done + fi +fi + +#-------------------------------------------- +# Check vector potential in rt-TDDFT +#-------------------------------------------- +if ! test -z "$out_vecpot" && [ "$out_vecpot" == 1 ]; then + record_compare_result "$props_result_file" "CompareVectorPot_pass" "vector_pot.txt.ref" "OUT.autotest/vector_pot.txt" 8 +fi + +} diff --git a/tests/integrate/tools/run_check.sh b/tests/integrate/validation_tools/run_check.sh similarity index 65% rename from tests/integrate/tools/run_check.sh rename to tests/integrate/validation_tools/run_check.sh index db83b93701d..a1d390ab2c7 100755 --- a/tests/integrate/tools/run_check.sh +++ b/tests/integrate/validation_tools/run_check.sh @@ -1,6 +1,6 @@ #!/bin/bash -CATCH_SCRIPT="../../integrate/tools/catch_properties.sh" +CATCH_SCRIPT="../../integrate/validation_tools/catch_properties.sh" GENERAL_INFO_FILE="../../integrate/general_info" # check_out: checking the output information @@ -37,11 +37,20 @@ test -e $GENERAL_INFO_FILE|| echo "current dir:`pwd`, plese prepare the general_ test -e $GENERAL_INFO_FILE|| exit 0 +# EXEC may be a literal path, or one of the ABACUS_EXE placeholders +# ($ABACUS_EXE, ${ABACUS_EXE}, ${ABACUS_EXE:-abacus}), so a single env var can +# override the executable without editing general_info. Only these +# placeholders are substituted; EXEC is never evaluated as shell code. exec_path=`grep EXEC $GENERAL_INFO_FILE | awk '{printf $2}'` +abacus_exe_default=${ABACUS_EXE:-abacus} +exec_path=${exec_path//'${ABACUS_EXE:-abacus}'/$abacus_exe_default} +exec_path=${exec_path//'${ABACUS_EXE}'/$ABACUS_EXE} +exec_path=${exec_path//'$ABACUS_EXE'/$ABACUS_EXE} -test -e $exec_path || echo "Error! ABACUS path was wrong!!" +# command -v accepts both a path and a bare name looked up in PATH. +command -v "$exec_path" >/dev/null || echo "Error! ABACUS path was wrong!!" -test -e $exec_path || exit 0 +command -v "$exec_path" >/dev/null || exit 0 CA=`grep CHECKACCURACY $GENERAL_INFO_FILE | awk '{printf $2}'` @@ -53,7 +62,7 @@ echo "Test in $path_here" echo "Begin testing the example with $NP cores" #parallel test -mpirun -np $NP $exec_path > log.txt +mpirun -np $NP "$exec_path" > log.txt test -d OUT.autotest || echo "Some errors occured in ABACUS!" @@ -66,9 +75,11 @@ if test -z $1 then $CATCH_SCRIPT result.out check_out result.out -elif [ $1 == "debug" ] +elif [ $1 == "debug" ] then -$CATCH_SCRIPT result.out +# Single_job.sh debug copies validation_tools/ into the case directory; +# use that copy so local edits to the collectors take effect. +./validation_tools/catch_properties.sh result.out check_out result.out else $CATCH_SCRIPT result.ref diff --git a/toolchain/toolchain_windows.sh b/toolchain/toolchain_windows.sh index 84b575da232..7fcc1a88cdb 100644 --- a/toolchain/toolchain_windows.sh +++ b/toolchain/toolchain_windows.sh @@ -42,7 +42,7 @@ pacman -S --needed --noconfirm \ # * scalapack : distributed eigensolver used by the LCAO MPI build (no ELPA). # 'bc' (a base MSYS tool, not a MinGW package) is used by the integration-test -# harness tests/integrate/tools/catch_properties.sh; install it so the existing +# harness tests/integrate/validation_tools/catch_properties.sh; install it so the existing # serial test flow (Autotest.sh -n 0) works out of the box. pacman -S --needed --noconfirm bc