From d14c90b442f091518e4e1228fc7f65fe1d8dcd1b Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 07:57:25 +0800 Subject: [PATCH 01/14] tests: select ABACUS executable via ABACUS_EXE and document test workflow Let integrate tests read the ABACUS executable from a single ABACUS_EXE environment variable instead of editing two files: - Autotest.sh defaults 'abacus' to ${ABACUS_EXE:-abacus}; '-a' still overrides. - general_info uses EXEC ${ABACUS_EXE:-abacus}; run_check.sh expands $VAR/${VAR} in the EXEC value from the environment. Both fall back to 'abacus' from PATH when ABACUS_EXE is unset, matching the existing CI behavior (ctest '-a' and container-installed abacus are unaffected). Also document the test workflow in tests/README: how to point at an executable (priority: -a flag > ABACUS_EXE > hard-coded value > PATH), and the role of the per-category CASES_CPU.txt / CASES_GPU.txt lists. Remove the two empty placeholder CASES_*.txt under integrate/, which are unused. Verified: run_check.sh EXEC expansion resolves to 'abacus' when ABACUS_EXE is unset and to the given path when set. --- tests/README | 132 ++++++++++------------------- tests/integrate/Autotest.sh | 5 +- tests/integrate/CASES_CPU.txt | 0 tests/integrate/CASES_GPU.txt | 0 tests/integrate/general_info | 2 +- tests/integrate/tools/run_check.sh | 4 + 6 files changed, 55 insertions(+), 88 deletions(-) delete mode 100644 tests/integrate/CASES_CPU.txt delete mode 100644 tests/integrate/CASES_GPU.txt diff --git a/tests/README b/tests/README index 490cf91b182..93052f1c8c0 100644 --- a/tests/README +++ b/tests/README @@ -4,7 +4,8 @@ 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) + (user can change NUMBEROFPROCESS value in integrate/general_info to test by multi cores; + set the executable once via 'export ABACUS_EXE=/path/to/abacus') -------------------------------------- Folders in this directory @@ -31,16 +32,57 @@ 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 +-------------------------------------- + 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. 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 +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: + + 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): + ../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): + export ABACUS_EXE=/path/to/abacus + Add it to ~/.bashrc to make it persistent across sessions. + c) 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/tools/run_check.sh from the environment). + + Effective priority: '-a' flag > ABACUS_EXE > hard-coded value in the files + > 'abacus' from PATH. +2. 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: +3. 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 @@ -69,83 +111,3 @@ Marking unimplemented features: `#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/integrate/Autotest.sh b/tests/integrate/Autotest.sh index 48b6d2308f7..2ff3044ab21 100755 --- a/tests/integrate/Autotest.sh +++ b/tests/integrate/Autotest.sh @@ -1,7 +1,8 @@ #!/bin/bash -# ABACUS executable path -abacus=abacus +# ABACUS executable path; override with the ABACUS_EXE environment variable +# so both this script and general_info (via ${ABACUS_EXE}) share one setting. +abacus=${ABACUS_EXE:-abacus} # number of MPI processes np=4 nt=$OMP_NUM_THREADS # number of OpenMP threads, default is $OMP_NUM_THREADS diff --git a/tests/integrate/CASES_CPU.txt b/tests/integrate/CASES_CPU.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/integrate/CASES_GPU.txt b/tests/integrate/CASES_GPU.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/integrate/general_info b/tests/integrate/general_info index c0d397efdc5..4da3bbae950 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/run_check.sh b/tests/integrate/tools/run_check.sh index db83b93701d..44f5b3af351 100755 --- a/tests/integrate/tools/run_check.sh +++ b/tests/integrate/tools/run_check.sh @@ -37,7 +37,11 @@ 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 contain $VAR / ${VAR} placeholders that are +# expanded from the environment (e.g. "EXEC ${ABACUS_EXE}"), so a single +# env var can override the executable without editing general_info. exec_path=`grep EXEC $GENERAL_INFO_FILE | awk '{printf $2}'` +exec_path=$(eval echo "$exec_path") test -e $exec_path || echo "Error! ABACUS path was wrong!!" From 2d970d0e8f87335f402086e339cb2fb55e5bddb2 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 08:43:48 +0800 Subject: [PATCH 02/14] tests: drop unused compare-wfc subcommand from cube_tool.py The compare-wfc subcommand and its exclusive helpers (read_wfc_component, phase_aligned_error, parse_wfc_groups) are not called anywhere in the repo; fingerprint-wfc uses the separate read_wfc_components. Remove the dead code (~90 lines). integrate / fingerprint-wfc / check-spinor are unaffected. Verified: python3 -m ast parse OK; --help lists only {integrate, fingerprint-wfc, check-spinor}; 'compare-wfc' now errors as an invalid choice. --- tests/integrate/tools/cube_tool.py | 93 ------------------------------ 1 file changed, 93 deletions(-) diff --git a/tests/integrate/tools/cube_tool.py b/tests/integrate/tools/cube_tool.py index 833e8443423..52ea0e64ac9 100755 --- a/tests/integrate/tools/cube_tool.py +++ b/tests/integrate/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", From cc4a937e0404e5f1c2cc4ecf3a3cee955fc67a7b Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 09:09:27 +0800 Subject: [PATCH 03/14] tests: extract props_common.sh shared library (step 1/7) Begin splitting the ~1000-line catch_properties.sh into per-category modules. This first step extracts the shared infrastructure into props_common.sh and turns the entry point into a thin wrapper: - PROPS_TOOLS_DIR resolves the tools dir via BASH_SOURCE so modules no longer depend on the caller's CWD - shared constants COMPARE_SCRIPT / CUBE_TOOL / COLLECT_NPY_MEANS - shared helpers sum_file / get_input_key_value / sanitize_result_key / record_compare_result - props_init(): one-time INPUT switch parsing (has_*/out_*/nspin/...) plus result file truncation; the dead vars file/has_dftu/has_r/base are not carried over - props_finalize(): writes the trailing totaltimeref line catch_properties.sh sources the library, calls props_init "$1", and keeps all property blocks inline for now; they move out in steps 2-6. Verified: bash -n passes; regenerated result output identical to pre-split baseline (modulo totaltimeref) for 01_PW/057_PW_SO_IW, 01_PW/035_PW_15_SO, 09_DeePKS/09_NO_GO_deepks_basic. --- tests/integrate/tools/catch_properties.sh | 140 +----------------- tests/integrate/tools/props_common.sh | 165 ++++++++++++++++++++++ 2 files changed, 171 insertions(+), 134 deletions(-) create mode 100644 tests/integrate/tools/props_common.sh diff --git a/tests/integrate/tools/catch_properties.sh b/tests/integrate/tools/catch_properties.sh index 94b4f526119..77f7beded41 100755 --- a/tests/integrate/tools/catch_properties.sh +++ b/tests/integrate/tools/catch_properties.sh @@ -2,140 +2,12 @@ # 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 +# +# 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" #---------------------------- # total energy information diff --git a/tests/integrate/tools/props_common.sh b/tests/integrate/tools/props_common.sh new file mode 100644 index 00000000000..395c0994818 --- /dev/null +++ b/tests/integrate/tools/props_common.sh @@ -0,0 +1,165 @@ +#!/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/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: each props_.sh module sources this file, +# then defines run__props() that reads the global switch variables +# set by props_init() and appends "key value" lines to the result file. + +# Absolute path of the directory containing this file (i.e. integrate/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 every INPUT switch key 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") + 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") + 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 $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 +} From 9d9a8d19480f20ca9c24423fe707902978358b28 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 19:27:40 +0800 Subject: [PATCH 04/14] tests: extract props_basic.sh collectors (step 2/7) Move the basic property blocks (total energy, collinear magnetism, force, stress, DOS, Onsager) out of catch_properties.sh into run_basic_props() in props_basic.sh. Three basic blocks originally sat between blocks destined for other modules, so they become position-preserving hooks: - run_basic_props_post_ml(): imp_sol energies - run_basic_props_post_deepks(): point/space/magnetic group + nkibz - run_basic_props_post_rdmft(): running__*.log names The inline trailing totaltimeref block is replaced by the existing props_finalize() helper. Block bodies are moved verbatim and keep appending to the result file in the original order. Verified: bash -n passes; output identical to step-1 baseline on 01_PW/057_PW_SO_IW, 01_PW/035_PW_15_SO, 09_DeePKS/09_NO_GO_deepks_basic, plus a synthetic case exercising imp_sol/symmetry/alllog branches. --- tests/integrate/tools/catch_properties.sh | 159 ++------------------ tests/integrate/tools/props_basic.sh | 174 ++++++++++++++++++++++ 2 files changed, 189 insertions(+), 144 deletions(-) create mode 100644 tests/integrate/tools/props_basic.sh diff --git a/tests/integrate/tools/catch_properties.sh b/tests/integrate/tools/catch_properties.sh index 77f7beded41..1f5e3849471 100755 --- a/tests/integrate/tools/catch_properties.sh +++ b/tests/integrate/tools/catch_properties.sh @@ -8,81 +8,13 @@ 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" -#---------------------------- -# 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 +# 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 #------------------------------- # echo $out_dm1 @@ -680,14 +612,9 @@ if [ -d "$descriptor_dir" ]; then fi #-------------------------------------------- -# implicit solvation model +# basic collectors that run after ml: imp_sol #-------------------------------------------- -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 +run_basic_props_post_ml #-------------------------------------------- # random phase approximation @@ -722,27 +649,9 @@ 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 +# basic collectors that run after deepks: symmetry +#-------------------------------------------- +run_basic_props_post_deepks #-------------------------------------------- # check currents in rt-TDDFT @@ -826,49 +735,11 @@ if ! test -z "$rdmft" && [[ $rdmft == 1 ]]; then fi #-------------------------------------------- -# Check if out_alllog is set to 1 -# and verify running*.log filenames +# basic collectors that run after rdmft: alllog #-------------------------------------------- -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 +run_basic_props_post_rdmft #-------------------------------------------- -# Check time information +# trailing total-time entry #-------------------------------------------- -#echo $total_band -ttot=`grep $word_total_time $running_path | awk '{print $3}'` -echo "totaltimeref $ttot" >>$1 +props_finalize "$1" diff --git a/tests/integrate/tools/props_basic.sh b/tests/integrate/tools/props_basic.sh new file mode 100644 index 00000000000..7d1781d7fdb --- /dev/null +++ b/tests/integrate/tools/props_basic.sh @@ -0,0 +1,174 @@ +#!/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 +#-------------------------------------------- +out_alllog=$(get_input_key_value "out_alllog" "INPUT") +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 + +} From 2c87b663fabd0a07beba017fe07d24b2d35a47da Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 19:34:33 +0800 Subject: [PATCH 05/14] tests: extract props_mat.sh matrix collectors (step 3/7) Move the matrix/operator blocks out of catch_properties.sh into props_mat.sh: - run_mat_dm1_props(): out_dm1 DMR comparison - run_mat_props(): S(k)/H(k), H(R)/S(R), VXC, separated eband terms, NPZ existence, r/T/SYNS/dH(R) and dH(k) term matrix comparisons - run_mat_dm_props(): out_dm density matrix comparison The three hooks preserve the original interleaving with the cube collectors that still live inline (moved in step 4). The hard-coded "../../integrate/tools" paths to compare_hsk_binary.py and compare_hsr_binary.py are replaced by $PROPS_TOOLS_DIR while moving. Verified: bash -n passes; output identical to the step-2 baseline on the three reference cases plus synthetic cases covering the basic post-hooks and an out_band matrix comparison. --- tests/integrate/tools/catch_properties.sh | 323 +------------------- tests/integrate/tools/props_mat.sh | 350 ++++++++++++++++++++++ 2 files changed, 357 insertions(+), 316 deletions(-) create mode 100644 tests/integrate/tools/props_mat.sh diff --git a/tests/integrate/tools/catch_properties.sh b/tests/integrate/tools/catch_properties.sh index 1f5e3849471..b65bbf355b3 100755 --- a/tests/integrate/tools/catch_properties.sh +++ b/tests/integrate/tools/catch_properties.sh @@ -9,6 +9,7 @@ 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" # Property collectors run in the same order as the original monolithic # script so result files stay byte-identical; props_finalize() writes the @@ -17,14 +18,9 @@ source "$PROPS_SCRIPT_DIR/props_basic.sh" run_basic_props #------------------------------- -# echo $out_dm1 +# matrix collectors: out_dm1 (props_mat.sh) #------------------------------- -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 +run_mat_dm1_props #------------------------------- # echo $out_pot1 @@ -58,304 +54,9 @@ if ! test -z "$out_elf" && [ $out_elf == 1 ]; then 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 +# matrix collectors (props_mat.sh): S/H(k)/XC/eband/H(R)/NPZ/r/T/dH #------------------------------- -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 +run_mat_props #--------------------------------------- # Charge density @@ -486,19 +187,9 @@ if ! test -z "$has_lowf" && [ $has_lowf == 1 ]; then fi #-------------------------------------------- -# density matrix information +# matrix collector: out_dm (props_mat.sh) #-------------------------------------------- -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 +run_mat_dm_props #-------------------------------------------- # mulliken charge diff --git a/tests/integrate/tools/props_mat.sh b/tests/integrate/tools/props_mat.sh new file mode 100644 index 00000000000..1099d5065ca --- /dev/null +++ b/tests/integrate/tools/props_mat.sh @@ -0,0 +1,350 @@ +#!/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 +# 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 $?" >>$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 + if test -z "$dmfile"; then + echo "Can't find DM files" + exit 1 + else + python3 $COMPARE_SCRIPT $dmref $dmfile 5 + echo "DM_different $?" >>$props_result_file + fi +fi + +} From 4d3e621878464bc6dd838375827046eda0c654a6 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 19:38:21 +0800 Subject: [PATCH 06/14] tests: extract props_cube.sh cube/wfc collectors (step 4/7) Move the real-space cube and wave-function blocks out of catch_properties.sh into props_cube.sh: - run_cube_pot_props(): out_pot=1/2 potential cubes and out_elf - run_cube_props(): chg cube, SCAN tau, LDOS, wfc real-space variance, PW wfc maxima, LCAO wfc comparison - run_cube_tail_props(): mulliken, pchg cube loop, get_wf/get_pchg and PW norm/Re-Im cube integration/fingerprints, nspin=4 pointwise spinor identity check The three hooks wrap the out_dm matrix block exactly as in the original script, preserving result-line order. Block bodies move verbatim; only the result target changes from $1 to $props_result_file. Verified: bash -n passes; output identical to the step-3 baseline on the three reference cases and synthetic basic/matrix/cube cases. --- tests/integrate/tools/catch_properties.sh | 260 +------------------ tests/integrate/tools/props_cube.sh | 289 ++++++++++++++++++++++ 2 files changed, 296 insertions(+), 253 deletions(-) create mode 100644 tests/integrate/tools/props_cube.sh diff --git a/tests/integrate/tools/catch_properties.sh b/tests/integrate/tools/catch_properties.sh index b65bbf355b3..e6c5422ff40 100755 --- a/tests/integrate/tools/catch_properties.sh +++ b/tests/integrate/tools/catch_properties.sh @@ -10,6 +10,7 @@ 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" # Property collectors run in the same order as the original monolithic # script so result files stay byte-identical; props_finalize() writes the @@ -23,35 +24,9 @@ run_basic_props run_mat_dm1_props #------------------------------- -# echo $out_pot1 +# cube collectors: out_pot/out_elf (props_cube.sh) #------------------------------- -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 +run_cube_pot_props #------------------------------- # matrix collectors (props_mat.sh): S/H(k)/XC/eband/H(R)/NPZ/r/T/dH @@ -59,132 +34,9 @@ fi run_mat_props #--------------------------------------- -# 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 +# cube collectors (props_cube.sh): chg/tau/LDOS/wfc real-space/PW/LCAO #--------------------------------------- -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 +run_cube_props #-------------------------------------------- # matrix collector: out_dm (props_mat.sh) @@ -192,107 +44,9 @@ fi run_mat_dm_props #-------------------------------------------- -# mulliken charge +# cube collectors (props_cube.sh): mulliken/pchg/fingerprints/spinor #-------------------------------------------- -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 +run_cube_tail_props #-------------------------------------------- # ML gene data descriptors (.npy) diff --git a/tests/integrate/tools/props_cube.sh b/tests/integrate/tools/props_cube.sh new file mode 100644 index 00000000000..593dc2d469a --- /dev/null +++ b/tests/integrate/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 + +} From d1f759d4683dbaf976e173a3b34dbb2efc03b476 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 19:41:22 +0800 Subject: [PATCH 07/14] tests: extract props_ml.sh ML/RPA/LR/RDMFT collectors (step 5/7) Move the advanced-method blocks out of catch_properties.sh into props_ml.sh: - run_ml_descriptor_props(): MLKEDF .npy descriptor means - run_ml_rpa_props(): Etot_without_rpa plus librpa ref compares - run_ml_lr_props(): linear-response excitation energies - run_ml_rdmft_props(): RDMFT energy-term extraction Entry point now only orchestrates hooks in the original emission order. Block bodies move verbatim; only the result target changes from $1 to $props_result_file. Verified: bash -n passes; output identical to the step-4 baseline on the three reference cases and synthetic basic/matrix/cube/LR/RDMFT cases. --- tests/integrate/tools/catch_properties.sh | 80 ++-------------- tests/integrate/tools/props_ml.sh | 112 ++++++++++++++++++++++ 2 files changed, 121 insertions(+), 71 deletions(-) create mode 100644 tests/integrate/tools/props_ml.sh diff --git a/tests/integrate/tools/catch_properties.sh b/tests/integrate/tools/catch_properties.sh index e6c5422ff40..f5be283759d 100755 --- a/tests/integrate/tools/catch_properties.sh +++ b/tests/integrate/tools/catch_properties.sh @@ -11,6 +11,7 @@ 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" # Property collectors run in the same order as the original monolithic # script so result files stay byte-identical; props_finalize() writes the @@ -49,12 +50,9 @@ run_mat_dm_props run_cube_tail_props #-------------------------------------------- -# ML gene data descriptors (.npy) +# ML collectors (props_ml.sh): MLKEDF descriptors #-------------------------------------------- -descriptor_dir="OUT.autotest/MLKEDF_Descriptors" -if [ -d "$descriptor_dir" ]; then - python3 $COLLECT_NPY_MEANS "$descriptor_dir" >> "$1" -fi +run_ml_descriptor_props #-------------------------------------------- # basic collectors that run after ml: imp_sol @@ -62,30 +60,9 @@ fi run_basic_props_post_ml #-------------------------------------------- -# random phase approximation +# ML collectors (props_ml.sh): RPA #-------------------------------------------- -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 +run_ml_rpa_props #-------------------------------------------- # deepks @@ -132,52 +109,13 @@ if ! test -z "$out_vecpot" && [ "$out_vecpot" == 1 ]; then fi #-------------------------------------------- -# Linear response function +# ML collectors (props_ml.sh): linear response excitations #-------------------------------------------- -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 +run_ml_lr_props #-------------------------------------------- -# Check RDMFT method +# ML collectors (props_ml.sh): RDMFT energy terms #-------------------------------------------- -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 +run_ml_rdmft_props #-------------------------------------------- # basic collectors that run after rdmft: alllog diff --git a/tests/integrate/tools/props_ml.sh b/tests/integrate/tools/props_ml.sh new file mode 100644 index 00000000000..dd949f065bf --- /dev/null +++ b/tests/integrate/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 + +} From 9c511026f0a63ae21b0759b441e14ec3c083e9f7 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 19:46:30 +0800 Subject: [PATCH 08/14] tests: extract props_tddft.sh/props_deepks.sh (step 6/7) - props_tddft.sh: run_tddft_props() for the rt-TDDFT current, efield and vecpot comparisons. - props_deepks.sh: process_npy/process_many_npys plus run_deepks_props(), reusing the props_common.sh helpers instead of duplicating sum_file/get_input_key_value/COMPARE_SCRIPT. The "../tools/get_sum_*.py" calls stay CWD-relative on purpose: from a tests/09_DeePKS/ CWD they resolve to tests/09_DeePKS/tools/. - catch_deepks_properties.sh becomes a thin standalone entry that sources the common library + props_deepks.sh; it deliberately skips props_init() so manual invocation appends instead of truncating. - catch_properties.sh calls run_deepks_props/run_tddft_props in process; the DeePKS subprocess boundary disappears. Verified: bash -n passes; entry-point output identical to the step-5 baseline on the three reference cases and all five synthetic cases; the new standalone DeePKS entry reproduces the old one output exactly. --- .../tools/catch_deepks_properties.sh | 248 +----------------- tests/integrate/tools/catch_properties.sh | 39 +-- tests/integrate/tools/props_deepks.sh | 227 ++++++++++++++++ tests/integrate/tools/props_tddft.sh | 44 ++++ 4 files changed, 289 insertions(+), 269 deletions(-) create mode 100644 tests/integrate/tools/props_deepks.sh create mode 100644 tests/integrate/tools/props_tddft.sh diff --git a/tests/integrate/tools/catch_deepks_properties.sh b/tests/integrate/tools/catch_deepks_properties.sh index d9039d261e9..0fbde171308 100644 --- a/tests/integrate/tools/catch_deepks_properties.sh +++ b/tests/integrate/tools/catch_deepks_properties.sh @@ -1,238 +1,14 @@ #!/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 -} - -# General function to process npy files -process_npy() { - local mode=$1 - local op=$2 - local file_prefix=$3 - local base_prefix=$4 - local output_name=$5 - - local total="0" - local file_pattern="" - local base_pattern="" - - # Check if force or stress - local is_force_or_stress=0 - 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 - if [ "$is_force_or_stress" -eq 1 ] ; then - file_pattern="OUT.autotest/DeePKS_Labels_Elec/${file_prefix}.npy" - base_pattern="OUT.autotest/DeePKS_Labels_Elec/${base_prefix}.npy" - else - file_pattern="OUT.autotest/DeePKS_Labels_Elec/${file_prefix}_e*.npy" - base_pattern="OUT.autotest/DeePKS_Labels_Elec/${base_prefix}_e*.npy" - fi - elif [ "$mode" = "single" ];then - # single mode: single file - 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 - local step="" - if [ "$mode" = "multi" ]; then - step=$(basename "$file" | grep -oP 'e\d+') - fi - - # Get corresponding base file - local base_file="" - if [ "$mode" = "multi" ] && [ "$is_force_or_stress" -ne 1 ]; then - base_file="OUT.autotest/DeePKS_Labels_Elec/${base_prefix}_${step}.npy" - else - base_file=$base_pattern - fi - - # Calculate value based on operation - if [ "$op" = "abs" ]; then - val=$(python3 ../tools/get_sum_abs.py "$file") - elif [ "$op" = "delta" ] && [ -f "$base_file" ]; then - val=$(python3 ../tools/get_sum_delta.py "$file" "$base_file") - elif [ "$op" = "numpy" ]; then - val=$(python3 ../tools/get_sum_numpy.py "$file") - else - val=0 - fi - - if [ "$mode" = "multi" ]; then - #echo "total: $total, val: $val" - total=$(echo "$total + $val" | bc) - else - total=$val - fi - done - - echo "$output_name $total" >>$6 -} - -# Process a group of label outputs -process_many_npys() { - local mode=$1 - local suffix=$2 - local output_file=$3 - - # energy - process_npy "$mode" "abs" "etot" "" "deepks_e_label$suffix" "$output_file" - process_npy "$mode" "delta" "etot" "ebase" "deepks_edelta$suffix" "$output_file" - - # For deepks_bandgap > 0 - if ! test -z "$deepks_bandgap" && [ $deepks_bandgap -gt 0 ]; then - process_npy "$mode" "abs" "otot" "" "deepks_o_label$suffix" "$output_file" - 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 - process_npy "$mode" "abs" "phialpha" "" "deepks_phialpha$suffix" "$output_file" - process_npy "$mode" "numpy" "gevdm" "" "deepks_gevdm$suffix" "$output_file" - fi - fi - - if ! test -z "$has_force" && [ $has_force == 1 ]; then - 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" - process_npy "$mode" "delta" "stot" "sbase" "deepks_sdelta$suffix" "$output_file" - fi -} - -# Main script -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") -deepks_scf=$(get_input_key_value "deepks_scf" "INPUT") -deepks_bandgap=$(get_input_key_value "deepks_bandgap" "INPUT") -deepks_v_delta=$(get_input_key_value "deepks_v_delta" "INPUT") -deepks_out_freq_elec=$(get_input_key_value "deepks_out_freq_elec" "INPUT") - -#--------------------------------------------------------------------------- -# Test for descriptor -#--------------------------------------------------------------------------- -if ! test -z "$deepks_scf" && [ $deepks_scf == 1 ]; then - # Process descriptor data - 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" -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" - - # 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" - fi - - # For cal_stress = 1 - if ! test -z "$has_stress" && [ $has_stress == 1 ]; then - process_npy "single" "abs" "gvepsl" "" "deepks_spre" "$1" - fi - - # For deepks_v_delta < 0 - 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 - # For deepks_v_delta = -1 - if [ $deepks_v_delta -eq -1 ]; then - process_npy "single" "abs" "vdrpre" "" "deepks_vdrp" "$1" - 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" - 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" - 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" - - fi - fi -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" - - if ! test -z "$has_force" && [ $has_force == 1 ]; then - process_npy "single" "numpy" "force" "" "deepks_force" "$1" - fi - - if ! test -z "$has_stress" && [ $has_stress == 1 ]; then - process_npy "single" "numpy" "stress" "" "deepks_stress" "$1" - fi - - if ! test -z "$deepks_bandgap" && [ $deepks_bandgap == 1 ]; then - process_npy "single" "numpy" "orbital" "" "deepks_orbital" "$1" - 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" - fi -fi \ No newline at end of file +# 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/tools/catch_properties.sh b/tests/integrate/tools/catch_properties.sh index f5be283759d..28a693cf7d1 100755 --- a/tests/integrate/tools/catch_properties.sh +++ b/tests/integrate/tools/catch_properties.sh @@ -12,6 +12,8 @@ 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 @@ -65,10 +67,9 @@ run_basic_props_post_ml run_ml_rpa_props #-------------------------------------------- -# deepks +# DeePKS collectors (props_deepks.sh) #-------------------------------------------- -script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -bash ${script_dir}/catch_deepks_properties.sh $1 +run_deepks_props #-------------------------------------------- # basic collectors that run after deepks: symmetry @@ -76,37 +77,9 @@ bash ${script_dir}/catch_deepks_properties.sh $1 run_basic_props_post_deepks #-------------------------------------------- -# check currents in rt-TDDFT +# rt-TDDFT collectors (props_tddft.sh): current/efield/vecpot #-------------------------------------------- -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 +run_tddft_props #-------------------------------------------- # ML collectors (props_ml.sh): linear response excitations diff --git a/tests/integrate/tools/props_deepks.sh b/tests/integrate/tools/props_deepks.sh new file mode 100644 index 00000000000..ba18c456f36 --- /dev/null +++ b/tests/integrate/tools/props_deepks.sh @@ -0,0 +1,227 @@ +#!/bin/bash + +# 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() { + local mode=$1 + local op=$2 + local file_prefix=$3 + local base_prefix=$4 + local output_name=$5 + + local total="0" + local file_pattern="" + local base_pattern="" + + # Check if force or stress + local is_force_or_stress=0 + 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 + if [ "$is_force_or_stress" -eq 1 ] ; then + file_pattern="OUT.autotest/DeePKS_Labels_Elec/${file_prefix}.npy" + base_pattern="OUT.autotest/DeePKS_Labels_Elec/${base_prefix}.npy" + else + file_pattern="OUT.autotest/DeePKS_Labels_Elec/${file_prefix}_e*.npy" + base_pattern="OUT.autotest/DeePKS_Labels_Elec/${base_prefix}_e*.npy" + fi + elif [ "$mode" = "single" ];then + # single mode: single file + 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 + local step="" + if [ "$mode" = "multi" ]; then + step=$(basename "$file" | grep -oP 'e\d+') + fi + + # Get corresponding base file + local base_file="" + if [ "$mode" = "multi" ] && [ "$is_force_or_stress" -ne 1 ]; then + base_file="OUT.autotest/DeePKS_Labels_Elec/${base_prefix}_${step}.npy" + else + base_file=$base_pattern + fi + + # Calculate value based on operation + if [ "$op" = "abs" ]; then + val=$(python3 ../tools/get_sum_abs.py "$file") + elif [ "$op" = "delta" ] && [ -f "$base_file" ]; then + val=$(python3 ../tools/get_sum_delta.py "$file" "$base_file") + elif [ "$op" = "numpy" ]; then + val=$(python3 ../tools/get_sum_numpy.py "$file") + else + val=0 + fi + + if [ "$mode" = "multi" ]; then + #echo "total: $total, val: $val" + total=$(echo "$total + $val" | bc) + else + total=$val + fi + done + + echo "$output_name $total" >>$6 +} + +# Process a group of label outputs +process_many_npys() { + local mode=$1 + local suffix=$2 + local output_file=$3 + + # energy + process_npy "$mode" "abs" "etot" "" "deepks_e_label$suffix" "$output_file" + process_npy "$mode" "delta" "etot" "ebase" "deepks_edelta$suffix" "$output_file" + + # For deepks_bandgap > 0 + if ! test -z "$deepks_bandgap" && [ $deepks_bandgap -gt 0 ]; then + process_npy "$mode" "abs" "otot" "" "deepks_o_label$suffix" "$output_file" + 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 + process_npy "$mode" "abs" "phialpha" "" "deepks_phialpha$suffix" "$output_file" + process_npy "$mode" "numpy" "gevdm" "" "deepks_gevdm$suffix" "$output_file" + fi + fi + + if ! test -z "$has_force" && [ $has_force == 1 ]; then + 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" + process_npy "$mode" "delta" "stot" "sbase" "deepks_sdelta$suffix" "$output_file" + fi +} + +run_deepks_props(){ + +# Main script +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") +deepks_scf=$(get_input_key_value "deepks_scf" "INPUT") +deepks_bandgap=$(get_input_key_value "deepks_bandgap" "INPUT") +deepks_v_delta=$(get_input_key_value "deepks_v_delta" "INPUT") +deepks_out_freq_elec=$(get_input_key_value "deepks_out_freq_elec" "INPUT") + +#--------------------------------------------------------------------------- +# Test for descriptor +#--------------------------------------------------------------------------- +if ! test -z "$deepks_scf" && [ $deepks_scf == 1 ]; then + # Process descriptor data + 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" >>$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" "" "$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" "$props_result_file" + fi + + # For cal_stress = 1 + if ! test -z "$has_stress" && [ $has_stress == 1 ]; then + process_npy "single" "abs" "gvepsl" "" "deepks_spre" "$props_result_file" + fi + + # For deepks_v_delta < 0 + 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 $?" >>$props_result_file + python3 $COMPARE_SCRIPT "deepks_hrdelta.csr.ref" "OUT.autotest/deepks_hrdelta.csr" 8 + echo "deepks_vdelta_r_pass $?" >>$props_result_file + # For deepks_v_delta = -1 + if [ $deepks_v_delta -eq -1 ]; then + 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" "$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" "$props_result_file" + if ! test -z "$deepks_v_delta" && [[ $deepks_v_delta -gt 0 ]]; then + process_npy "single" "abs" "overlap" "" "deepks_overlap" "$props_result_file" + process_npy "multi" "abs" "overlap" "" "deepks_overlap_elec" "$props_result_file" + + fi + fi +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" "$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" "$props_result_file" + fi + + if ! test -z "$has_stress" && [ $has_stress == 1 ]; then + 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" "$props_result_file" + fi + + if ! test -z "$deepks_v_delta" && [[ $deepks_v_delta -gt 0 ]]; then + process_npy "single" "numpy" "hamiltonian" "" "deepks_hamiltonian" "$props_result_file" + process_npy "single" "numpy" "overlap" "" "deepks_overlap" "$props_result_file" + fi +fi + +} diff --git a/tests/integrate/tools/props_tddft.sh b/tests/integrate/tools/props_tddft.sh new file mode 100644 index 00000000000..5d3f7d1eace --- /dev/null +++ b/tests/integrate/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 + +} From 0c9d1c1579e9dd2c58f9e167fcc6bfe8e4c19198 Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 20:11:57 +0800 Subject: [PATCH 09/14] tests: final cleanup of split property collectors (step 7/7) - Drop the redundant get_s alias of $calculation from props_init(); the S(R) overlap check in props_mat.sh now tests $calculation directly (identical trigger: get_s was a second read of the same INPUT key). - Remove the dead 'test -z "$dmfile"' branch in run_mat_dm_props: dmfile is assigned a non-empty literal, so the branch could never fire; a missing dm file is reported through the CompareFile.py exit status like every other matrix block. Verification: bash -n on all nine shell files; the collector output of all 384 test-case directories on disk that contain OUT.autotest results was diffed against the step-6 baseline (exit codes and stdout, modulo totaltimeref) -- 384/384 identical, plus synthetic cases for get_s/scf triggering, basic, cube, LR, RDMFT and DeePKS. --- tests/integrate/tools/props_common.sh | 1 - tests/integrate/tools/props_mat.sh | 17 ++++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/tests/integrate/tools/props_common.sh b/tests/integrate/tools/props_common.sh index 395c0994818..b350830d1e4 100644 --- a/tests/integrate/tools/props_common.sh +++ b/tests/integrate/tools/props_common.sh @@ -126,7 +126,6 @@ props_init(){ 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") diff --git a/tests/integrate/tools/props_mat.sh b/tests/integrate/tools/props_mat.sh index 1099d5065ca..f114b5bbbc4 100644 --- a/tests/integrate/tools/props_mat.sh +++ b/tests/integrate/tools/props_mat.sh @@ -32,9 +32,9 @@ run_mat_props(){ #------------------------------- # Overlap matrix -# echo $get_s +# calculation == get_s #------------------------------- -if ! test -z "$get_s" && [ $get_s == "get_s" ]; then +if [ "$calculation" == "get_s" ]; then sref=sr_nao.csr.ref scal=OUT.autotest/sr_nao.csr python3 $COMPARE_SCRIPT $sref $scal 8 @@ -336,15 +336,10 @@ 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 - if test -z "$dmfile"; then - echo "Can't find DM files" - exit 1 - else - python3 $COMPARE_SCRIPT $dmref $dmfile 5 - echo "DM_different $?" >>$props_result_file - fi + dmfile=OUT.autotest/dm_nao.txt + dmref=dm_nao.txt.ref + python3 $COMPARE_SCRIPT $dmref $dmfile 5 + echo "DM_different $?" >>$props_result_file fi } From 9f6c21932d3818df54152599f361f15ce3543c7c Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 20:36:36 +0800 Subject: [PATCH 10/14] tests: rename integrate/tools to validation_tools and document validation The directory contains only validation/comparison scripts (collectors, CompareFile.py, cube_tool.py, run_check.sh), never result data, so give it a name that says what it is. - git mv tests/integrate/tools -> tests/integrate/validation_tools. - Update every reference: Autotest.sh (both collector invocations), run_check.sh, Single_job.sh, docs/CONTRIBUTING.md (also fixing the stale ../tools relative path in the example), the Windows toolchain comment, and comments in props_common.sh. - Update the stale data-path comments in mathzone_add1_test.cpp from the long-gone tests/integrate/tools/PP_ORB to tests/PP_ORB. - tests/README: add a "How to validate an integrate test case" section explaining result.out/result.ref, the modular props_*.sh collectors, and the three validation entry points (Autotest.sh, Single_job.sh, catch_properties.sh + diff), and fix the stale Single.sh path. The unrelated ../tools/get_sum_*.py calls in props_deepks.sh are untouched on purpose: from a case CWD they resolve to tests/09_DeePKS/tools, a different directory. Verified: bash -n on all shell files; no integrate/tools references remain; collector and standalone DeePKS outputs are identical before and after the rename on the reference cases. --- docs/CONTRIBUTING.md | 4 +- .../source_base/test/mathzone_add1_test.cpp | 6 +- tests/README | 59 ++++++++++++++++++- tests/integrate/Autotest.sh | 4 +- tests/integrate/Single_job.sh | 2 +- .../CompareFile.py | 0 .../catch_deepks_properties.sh | 0 .../catch_properties.sh | 0 .../collect_npy_means.py | 0 .../compare_hsk_binary.py | 0 .../compare_hsr_binary.py | 0 .../{tools => validation_tools}/cube_tool.py | 0 .../props_basic.sh | 0 .../props_common.sh | 5 +- .../{tools => validation_tools}/props_cube.sh | 0 .../props_deepks.sh | 0 .../{tools => validation_tools}/props_mat.sh | 0 .../{tools => validation_tools}/props_ml.sh | 0 .../props_tddft.sh | 0 .../{tools => validation_tools}/run_check.sh | 2 +- toolchain/toolchain_windows.sh | 2 +- 21 files changed, 70 insertions(+), 14 deletions(-) rename tests/integrate/{tools => validation_tools}/CompareFile.py (100%) rename tests/integrate/{tools => validation_tools}/catch_deepks_properties.sh (100%) rename tests/integrate/{tools => validation_tools}/catch_properties.sh (100%) rename tests/integrate/{tools => validation_tools}/collect_npy_means.py (100%) rename tests/integrate/{tools => validation_tools}/compare_hsk_binary.py (100%) rename tests/integrate/{tools => validation_tools}/compare_hsr_binary.py (100%) rename tests/integrate/{tools => validation_tools}/cube_tool.py (100%) rename tests/integrate/{tools => validation_tools}/props_basic.sh (100%) rename tests/integrate/{tools => validation_tools}/props_common.sh (97%) rename tests/integrate/{tools => validation_tools}/props_cube.sh (100%) rename tests/integrate/{tools => validation_tools}/props_deepks.sh (100%) rename tests/integrate/{tools => validation_tools}/props_mat.sh (100%) rename tests/integrate/{tools => validation_tools}/props_ml.sh (100%) rename tests/integrate/{tools => validation_tools}/props_tddft.sh (100%) rename tests/integrate/{tools => validation_tools}/run_check.sh (97%) 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 index 93052f1c8c0..814b56939ae 100644 --- a/tests/README +++ b/tests/README @@ -76,7 +76,7 @@ CASES_*.txt of its category, otherwise it will not be picked up. c) 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/tools/run_check.sh from the environment). + expanded by integrate/validation_tools/run_check.sh from the environment). Effective priority: '-a' flag > ABACUS_EXE > hard-coded value in the files > 'abacus' from PATH. @@ -84,11 +84,66 @@ CASES_*.txt of its category, otherwise it will not be picked up. ../integrate/Autotest.sh 3. 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 +../../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 tests/integrate: + bash Autotest.sh -n 4 + Autotest.sh runs every case in 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 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 ../../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 the scripts into the + case directory for local editing. + +3. Collector only (ABACUS output already exists in OUT.autotest/): + 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 -------------------------------------- diff --git a/tests/integrate/Autotest.sh b/tests/integrate/Autotest.sh index 2ff3044ab21..6960a798f22 100755 --- a/tests/integrate/Autotest.sh +++ b/tests/integrate/Autotest.sh @@ -340,7 +340,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++ @@ -354,7 +354,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..f30f24f8b95 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: 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/tools/catch_deepks_properties.sh b/tests/integrate/validation_tools/catch_deepks_properties.sh similarity index 100% rename from tests/integrate/tools/catch_deepks_properties.sh rename to tests/integrate/validation_tools/catch_deepks_properties.sh diff --git a/tests/integrate/tools/catch_properties.sh b/tests/integrate/validation_tools/catch_properties.sh similarity index 100% rename from tests/integrate/tools/catch_properties.sh rename to tests/integrate/validation_tools/catch_properties.sh 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 100% rename from tests/integrate/tools/cube_tool.py rename to tests/integrate/validation_tools/cube_tool.py diff --git a/tests/integrate/tools/props_basic.sh b/tests/integrate/validation_tools/props_basic.sh similarity index 100% rename from tests/integrate/tools/props_basic.sh rename to tests/integrate/validation_tools/props_basic.sh diff --git a/tests/integrate/tools/props_common.sh b/tests/integrate/validation_tools/props_common.sh similarity index 97% rename from tests/integrate/tools/props_common.sh rename to tests/integrate/validation_tools/props_common.sh index b350830d1e4..c492966fef4 100644 --- a/tests/integrate/tools/props_common.sh +++ b/tests/integrate/validation_tools/props_common.sh @@ -5,7 +5,7 @@ # 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/tools" relative path; +# 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 @@ -16,7 +16,8 @@ # then defines run__props() that reads the global switch variables # set by props_init() and appends "key value" lines to the result file. -# Absolute path of the directory containing this file (i.e. integrate/tools). +# 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" diff --git a/tests/integrate/tools/props_cube.sh b/tests/integrate/validation_tools/props_cube.sh similarity index 100% rename from tests/integrate/tools/props_cube.sh rename to tests/integrate/validation_tools/props_cube.sh diff --git a/tests/integrate/tools/props_deepks.sh b/tests/integrate/validation_tools/props_deepks.sh similarity index 100% rename from tests/integrate/tools/props_deepks.sh rename to tests/integrate/validation_tools/props_deepks.sh diff --git a/tests/integrate/tools/props_mat.sh b/tests/integrate/validation_tools/props_mat.sh similarity index 100% rename from tests/integrate/tools/props_mat.sh rename to tests/integrate/validation_tools/props_mat.sh diff --git a/tests/integrate/tools/props_ml.sh b/tests/integrate/validation_tools/props_ml.sh similarity index 100% rename from tests/integrate/tools/props_ml.sh rename to tests/integrate/validation_tools/props_ml.sh diff --git a/tests/integrate/tools/props_tddft.sh b/tests/integrate/validation_tools/props_tddft.sh similarity index 100% rename from tests/integrate/tools/props_tddft.sh rename to tests/integrate/validation_tools/props_tddft.sh diff --git a/tests/integrate/tools/run_check.sh b/tests/integrate/validation_tools/run_check.sh similarity index 97% rename from tests/integrate/tools/run_check.sh rename to tests/integrate/validation_tools/run_check.sh index 44f5b3af351..366b45019ad 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 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 From 5820e4f245cd38fe9cd1073bd4f3b3fbea3c290f Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 21:44:57 +0800 Subject: [PATCH 11/14] Test: tolerate nonzero helper status in deepks collector under bash -e The split moved the deepks collector from a separate `bash` invocation (without -e) into the `bash -e` catch_properties.sh process, so helper commands that legitimately return nonzero now abort the whole collection: - process_npy step extraction: force/stress multi-mode files (ftot.npy, stot.npy) have no e suffix, so `grep -oP 'e\d+'` returns 1 and killed the collector before the deepks_*_elec keys were written; every deepks_out_freq_elec case was reported as fatal. - the deepks_v_delta < 0 branch: CompareFile.py exits 1 when files differ; capture the raw status in named variables so it is still recorded instead of aborting. Verified with build_max_para_test/abacus_max_para (v3.11.0-beta10): all 31 cases in tests/09_DeePKS pass, 284 key checks OK, including 25_NO_GO_deepks_out_freq_elec and 26_NO_KP_deepks_out_freq_elec. --- .../validation_tools/props_deepks.sh | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/integrate/validation_tools/props_deepks.sh b/tests/integrate/validation_tools/props_deepks.sh index ba18c456f36..beb6f1a904f 100644 --- a/tests/integrate/validation_tools/props_deepks.sh +++ b/tests/integrate/validation_tools/props_deepks.sh @@ -52,9 +52,14 @@ process_npy() { 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. This + # collector runs in-process under `bash -e` (before the split it ran + # as a separate script without -e), so tolerate the empty match + # explicitly instead of aborting the whole collection. 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 @@ -171,11 +176,16 @@ if ! test -z "$deepks_out_labels" && [ $deepks_out_labels == 1 ]; then fi # For deepks_v_delta < 0 + # CompareFile.py exits 1 when the files differ; capture the status in a + # named variable so `bash -e` does not abort before it is recorded (the + # pre-split deepks collector ran without -e and recorded the raw status). 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 $?" >>$props_result_file - python3 $COMPARE_SCRIPT "deepks_hrdelta.csr.ref" "OUT.autotest/deepks_hrdelta.csr" 8 - echo "deepks_vdelta_r_pass $?" >>$props_result_file + 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" "$props_result_file" From 298eda73c0f9646e1b8e30d074142b762b1c222b Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 21:52:57 +0800 Subject: [PATCH 12/14] Test: let Autotest.sh source an untracked general_info.local override Running the integrate tests required exporting ABACUS_EXE in every new shell (or editing tracked files, which risks committing a local absolute path). Autotest.sh now sources integrate/general_info.local at startup when it exists; the file is gitignored and may set the 'abacus' variable (or export ABACUS_EXE). Effective priority: -a flag > ABACUS_EXE environment > general_info.local > 'abacus' from PATH; behavior is unchanged when the file is absent. Document the option in tests/README. Verified: with the local file present and ABACUS_EXE unset, Autotest.sh resolves the override and case 25_NO_GO_deepks_out_freq_elec passes; env override, -a override and no-file fallback behave as documented. --- .gitignore | 1 + tests/README | 11 ++++++++--- tests/integrate/Autotest.sh | 17 ++++++++++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) 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/tests/README b/tests/README index 814b56939ae..2b3c20c632a 100644 --- a/tests/README +++ b/tests/README @@ -73,13 +73,18 @@ CASES_*.txt of its category, otherwise it will not be picked up. b) Environment variable (works for Autotest.sh and run_check.sh): export ABACUS_EXE=/path/to/abacus Add it to ~/.bashrc to make it persistent across sessions. - c) Edit the files directly (hard-coded; do NOT commit a local absolute path): + c) Local override file (Autotest.sh only; not tracked by git): + create 'integrate/general_info.local' containing e.g. + 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 > hard-coded value in the files - > 'abacus' from PATH. + Effective priority: '-a' flag > ABACUS_EXE > general_info.local > hard-coded + value in the files > 'abacus' from PATH. 2. Enter each integrate test directory, try this script for autotests ../integrate/Autotest.sh 3. if you want to focus on No.xxx example, such as 101_PW_OU: diff --git a/tests/integrate/Autotest.sh b/tests/integrate/Autotest.sh index 6960a798f22..baf79abe193 100755 --- a/tests/integrate/Autotest.sh +++ b/tests/integrate/Autotest.sh @@ -1,8 +1,23 @@ #!/bin/bash +# 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 +# or export ABACUS_EXE=/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 plain 'abacus=...' set +# here. Effective priority: +# -a flag > ABACUS_EXE environment > general_info.local > 'abacus' from PATH +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. -abacus=${ABACUS_EXE:-abacus} +# A path set by general_info.local (above) is only used when ABACUS_EXE is +# not exported. +abacus=${ABACUS_EXE:-${abacus:-abacus}} # number of MPI processes np=4 nt=$OMP_NUM_THREADS # number of OpenMP threads, default is $OMP_NUM_THREADS From 367dfffe779362e5b51a8244790643387dae80cd Mon Sep 17 00:00:00 2001 From: abacus_fixer Date: Wed, 7 Oct 2026 22:01:50 +0800 Subject: [PATCH 13/14] Test: convert tests/README to Markdown Rename tests/README to tests/README.md (history preserved via git mv) and reformat it as Markdown: proper headings, a table for the folder overview and fenced code blocks for the commands. Fix a few typos (multple, acripts, integrte, Cmake) and update the self-reference to README.md. No other file references tests/README, so no further updates are needed. --- tests/README | 173 -------------------------------------- tests/README.md | 218 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 173 deletions(-) delete mode 100644 tests/README create mode 100644 tests/README.md diff --git a/tests/README b/tests/README deleted file mode 100644 index 2b3c20c632a..00000000000 --- a/tests/README +++ /dev/null @@ -1,173 +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; - set the executable once via 'export ABACUS_EXE=/path/to/abacus') - --------------------------------------- - 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 - --------------------------------------- - 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: - - 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): - ../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): - 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. - 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, try this script for autotests -../integrate/Autotest.sh -3. if you want to focus on No.xxx example, such as 101_PW_OU: -cd 101_PW_OU and then type -../../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 tests/integrate: - bash Autotest.sh -n 4 - Autotest.sh runs every case in 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 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 ../../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 the scripts into the - case directory for local editing. - -3. Collector only (ABACUS output already exists in OUT.autotest/): - 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 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/README.md b/tests/README.md new file mode 100644 index 00000000000..4c2e3d6b0fd --- /dev/null +++ b/tests/README.md @@ -0,0 +1,218 @@ +# 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 `tests/integrate`: + + ```bash + bash Autotest.sh -n 4 + ``` + + `Autotest.sh` runs every case in `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 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 the scripts into the case + directory for local editing. + +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. From 14b9b73856049c25899bf3f7d5b3aa5fb0c6fbd0 Mon Sep 17 00:00:00 2001 From: Critsium Date: Fri, 9 Oct 2026 21:19:59 +0800 Subject: [PATCH 14/14] Test: address review comments on split property collectors - catch_properties.sh: run the DeePKS collector in a subshell with errexit disabled, restoring the pre-split behavior where a failing get_sum_*.py or a missing deepks_desc.dat leaves an empty value instead of aborting the whole collection under `bash -e`. - run_check.sh: substitute only the ABACUS_EXE placeholders instead of `eval`, quote the executable path, and check it with `command -v` so the documented fallback to `abacus` from PATH works. - Single_job.sh/run_check.sh: `debug` now copies the whole validation_tools/ directory into the case directory and collects with that copy, since catch_properties.sh sources its modules from its own directory. - Autotest.sh: save ABACUS_EXE before sourcing general_info.local so the environment keeps priority, and clear `abacus` so a stray environment variable of that name is not picked up. - Restore the empty tests/integrate/CASES_CPU.txt/CASES_GPU.txt used by the integrated_test CTest entries. - props_common.sh: parse out_alllog in props_init() and correct the module/sourcing contract comments. - README: run Autotest.sh from a category directory; describe debug mode. - general_info: drop trailing space after EXEC. Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/README.md | 14 ++++++----- tests/integrate/Autotest.sh | 11 +++++--- tests/integrate/CASES_CPU.txt | 0 tests/integrate/CASES_GPU.txt | 0 tests/integrate/Single_job.sh | 5 +++- tests/integrate/general_info | 2 +- .../validation_tools/catch_properties.sh | 6 ++++- .../integrate/validation_tools/props_basic.sh | 1 - .../validation_tools/props_common.sh | 11 +++++--- .../validation_tools/props_deepks.sh | 12 ++++----- tests/integrate/validation_tools/run_check.sh | 25 ++++++++++++------- 11 files changed, 54 insertions(+), 33 deletions(-) create mode 100644 tests/integrate/CASES_CPU.txt create mode 100644 tests/integrate/CASES_GPU.txt diff --git a/tests/README.md b/tests/README.md index 4c2e3d6b0fd..c46d9689e57 100644 --- a/tests/README.md +++ b/tests/README.md @@ -142,13 +142,14 @@ lines from the fresh run into `result.out`, and comparing the two: Ways to validate, from a whole category down to one case: -1. Whole category (what ctest/CI do), from `tests/integrate`: +1. Whole category (what ctest/CI do), from the category directory + (e.g. `tests/01_PW`): ```bash - bash Autotest.sh -n 4 + bash ../integrate/Autotest.sh -n 4 ``` - `Autotest.sh` runs every case in `CASES_CPU.txt`, invokes the collectors to + `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 @@ -164,7 +165,7 @@ Ways to validate, from a whole category down to one case: Example: ```bash - bash Autotest.sh -r 035_PW_15_SO + 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`): @@ -176,8 +177,9 @@ Ways to validate, from a whole category down to one case: 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 the scripts into the case - directory for local editing. + 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/`): diff --git a/tests/integrate/Autotest.sh b/tests/integrate/Autotest.sh index baf79abe193..728f7f0c670 100755 --- a/tests/integrate/Autotest.sh +++ b/tests/integrate/Autotest.sh @@ -3,11 +3,14 @@ # 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 -# or export ABACUS_EXE=/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 plain 'abacus=...' set -# here. Effective priority: +# 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" @@ -17,7 +20,7 @@ fi # 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:-${abacus:-abacus}} +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 diff --git a/tests/integrate/CASES_CPU.txt b/tests/integrate/CASES_CPU.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integrate/CASES_GPU.txt b/tests/integrate/CASES_GPU.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integrate/Single_job.sh b/tests/integrate/Single_job.sh index f30f24f8b95..12dcfddba66 100755 --- a/tests/integrate/Single_job.sh +++ b/tests/integrate/Single_job.sh @@ -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 4da3bbae950..2eead2532e1 100644 --- a/tests/integrate/general_info +++ b/tests/integrate/general_info @@ -1,3 +1,3 @@ -EXEC ${ABACUS_EXE:-abacus} +EXEC ${ABACUS_EXE:-abacus} CHECKACCURACY 2 NUMBEROFPROCESS 4 diff --git a/tests/integrate/validation_tools/catch_properties.sh b/tests/integrate/validation_tools/catch_properties.sh index 28a693cf7d1..22ef71b9e32 100755 --- a/tests/integrate/validation_tools/catch_properties.sh +++ b/tests/integrate/validation_tools/catch_properties.sh @@ -69,7 +69,11 @@ run_ml_rpa_props #-------------------------------------------- # DeePKS collectors (props_deepks.sh) #-------------------------------------------- -run_deepks_props +# 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 diff --git a/tests/integrate/validation_tools/props_basic.sh b/tests/integrate/validation_tools/props_basic.sh index 7d1781d7fdb..50b0ca88822 100644 --- a/tests/integrate/validation_tools/props_basic.sh +++ b/tests/integrate/validation_tools/props_basic.sh @@ -136,7 +136,6 @@ run_basic_props_post_rdmft(){ # 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 if [ -z "$calculation" ]; then echo "Error: calculation parameter not found in INPUT" diff --git a/tests/integrate/validation_tools/props_common.sh b/tests/integrate/validation_tools/props_common.sh index c492966fef4..4068370ebb3 100644 --- a/tests/integrate/validation_tools/props_common.sh +++ b/tests/integrate/validation_tools/props_common.sh @@ -12,9 +12,11 @@ # shell variables, plus clearing of the result file; # - props_finalize(): the always-emitted total-time entry. # -# Extension contract: each props_.sh module sources this file, -# then defines run__props() that reads the global switch variables -# set by props_init() and appends "key value" lines to the result file. +# 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). @@ -77,7 +79,7 @@ record_compare_result(){ # props_init RESULT_FILE # -# Reads every INPUT switch key once into global variables, locates the +# 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. @@ -144,6 +146,7 @@ props_init(){ 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 diff --git a/tests/integrate/validation_tools/props_deepks.sh b/tests/integrate/validation_tools/props_deepks.sh index beb6f1a904f..918eee161ac 100644 --- a/tests/integrate/validation_tools/props_deepks.sh +++ b/tests/integrate/validation_tools/props_deepks.sh @@ -53,10 +53,9 @@ process_npy() { # 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. This - # collector runs in-process under `bash -e` (before the split it ran - # as a separate script without -e), so tolerate the empty match - # explicitly instead of aborting the whole collection. + # 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+' || true) @@ -137,6 +136,8 @@ process_many_npys() { 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") @@ -177,8 +178,7 @@ if ! test -z "$deepks_out_labels" && [ $deepks_out_labels == 1 ]; then # For deepks_v_delta < 0 # CompareFile.py exits 1 when the files differ; capture the status in a - # named variable so `bash -e` does not abort before it is recorded (the - # pre-split deepks collector ran without -e and recorded the raw status). + # named variable so it is recorded even when sourced under `bash -e`. if ! test -z "$deepks_v_delta" && [ $deepks_v_delta -lt 0 ]; then hr_pass=0 python3 $COMPARE_SCRIPT "deepks_hrtot.csr.ref" "OUT.autotest/deepks_hrtot.csr" 8 || hr_pass=$? diff --git a/tests/integrate/validation_tools/run_check.sh b/tests/integrate/validation_tools/run_check.sh index 366b45019ad..a1d390ab2c7 100755 --- a/tests/integrate/validation_tools/run_check.sh +++ b/tests/integrate/validation_tools/run_check.sh @@ -37,15 +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 contain $VAR / ${VAR} placeholders that are -# expanded from the environment (e.g. "EXEC ${ABACUS_EXE}"), so a single -# env var can override the executable without editing general_info. +# 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}'` -exec_path=$(eval echo "$exec_path") +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}'` @@ -57,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!" @@ -70,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