diff --git a/AGENTS.md b/AGENTS.md index 0705adefe89..f235f134264 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,6 +79,9 @@ rules. Read the complete governance document before making or reviewing changes: guards and `#ifdef`/`#ifndef` usages of existing macros are exempt. 21. Do not initialize member variables from `PARAM` in `.h`/`.hpp` files; pass the value through the constructor or a setter instead. + 22. Print ionic-step indices one-based (starting from 1) in user-facing + output files, matching the `g{N}` filename suffix convention; do not + write zero-based step counters such as `STEP: 0` into output files. - Use LF line endings for text files. Only `.bat` and `.cmd` files may use CRLF. - Keep source file additions deterministic: update the relevant `CMakeLists.txt` or explain why the file is generated or included indirectly. diff --git a/docs/advanced/elec_properties/Mulliken.md b/docs/advanced/elec_properties/Mulliken.md index c768e83c7ff..afb585d0e29 100644 --- a/docs/advanced/elec_properties/Mulliken.md +++ b/docs/advanced/elec_properties/Mulliken.md @@ -1,62 +1,67 @@ # Mulliken Charge Analysis From version 2.1.0, ABACUS has the function of Mulliken population analysis. The example can be found in [examples/mulliken](https://github.com/deepmodeling/abacus-develop/tree/develop/examples/14_mulliken). \ -To use this function, set [out_mul](../input_files/input-main.md#out_mul) to `1` in the INPUT file. After calculation, there will be an output file named `mulliken.txt` in the output directory. In MD calculations, the output interval is controlled by the keyword [out_freq_ion](../input_files/input-main.md#out_freq_ion). In the file, there are contents like (`nspin 1`): +To use this function, set [out_mul](../input_files/input-main.md#out_mul) to `1` in the INPUT file. After calculation, there will be an output file named `mulliken.txt` in the output directory. In MD calculations, the output interval is controlled by the keyword [out_freq_ion](../input_files/input-main.md#out_freq_ion). In the file, there are contents like (`nspin 1`, H2): ``` -STEP: 0 -CALCULATE THE MULLIkEN ANALYSIS FOR EACH ATOM - Total charge of spin 1: 8 - Total charge: 8 -Decomposed Mulliken populations -0 Zeta of Si Spin 1 -s 0 1.2553358 - sum over m 1.2553358 -s 1 -0.030782972 - sum over m -0.030782972 - sum over m+zeta 1.2245529 -pz 0 0.85945806 -px 0 0.85945806 -py 0 0.85945806 - sum over m 2.5783742 -pz 1 0.0065801228 -px 1 0.0065801228 -py 1 0.0065801228 - sum over m 0.019740368 - sum over m+zeta 2.5981145 -dz^2 0 0.0189287 -dxz 0 0.046491729 -dyz 0 0.046491729 -dx^2-y^2 0 0.0189287 -dxy 0 0.046491729 - sum over m 0.17733259 - sum over m+zeta 0.17733259 -Total Charge on atom: Si 4 + --- Ionic Step 1 --- + Total charge 2 + Decomposed Mulliken population analysis for each atom + l and m from Ylm, z stands for zeta orbital + + ------------------ + Atom 1 is H + ------------------ + zeta spin1 + s 1 0.9980 + s 2 -0.0052 + sum mz 0.9928 + + pz 1 0.0072 + px 1 0.0000 + py 1 0.0000 + sum m 0.0072 + sum mz 0.0072 + + sum lmz 1.0000 + + total charge on atom 1 1.0000 ... ``` -The file gives Mulliken charge in turn according to the order of atoms in the system. For example, the following block is for the first atom in system (`nspin 2`), +The ionic step index starts from 1. The file gives Mulliken charge in turn according to the order of atoms in the system. For example, the following block is for the first atom in system (`nspin 2`), ``` -0 Zeta of Si Spin 1 Spin 2 Sum Diff + ------------------ + Atom 1 is Fe1 + ------------------ + zeta spin1 spin2 sum diff + s 1 0.9437 0.8381 1.7818 0.1056 ... -Total Charge on atom: Si 4 -Total Magnetism on atom: Si -1.2739809e-14 + sum lmz ... ``` And the next block is for the second atom in system, and so on. ``` -1 Zeta of Si Spin 1 Spin 2 Sum Diff + ------------------ + Atom 2 is Fe2 + ------------------ + zeta spin1 spin2 sum diff ... ``` For each atom, the file gives detailed Mulliken population analysis at different levels, -- magnetic quantum number level: such as lines beigin with ‘s,px,py,pz,...’ -- azimuthal quantum number level: such as lines begin with ‘sum over m’. -- principal quantum number level: such as lines begin with ‘sum over m+zeta’. Here ‘zeta’ - equals ‘zeta’ in the file, which means how many radial atomic orbitals there are for a given orbital angular momentum. -- atomic level: such as lines begin with ‘Total Charge on atom’. +- magnetic quantum number level: such as lines begin with 's, px, py, pz, ...'. The columns + are the radial index (`zeta`, one-based) followed by the spin channels. +- azimuthal quantum number level: such as lines begin with 'sum m', the sum over the + magnetic quantum number m for one radial orbital. +- principal quantum number level: such as lines begin with 'sum mz', the sum over both the + magnetic quantum number m and the radial index zeta for a given orbital angular momentum. + Here 'z' equals 'zeta' in the file, which means how many radial atomic orbitals there are + for a given orbital angular momentum. +- atomic level: such as lines begin with 'sum lmz' for the atom total and + 'total charge on atom' for the charge assigned to the atom. -More orbital information can be found in 'Orbital' file output with 'mulliken.txt' when `out_mul 1` +More orbital information can be found in 'nao.txt' file output with 'mulliken.txt' when `out_mul 1` diff --git a/docs/advanced/elec_properties/hs_matrix.md b/docs/advanced/elec_properties/hs_matrix.md index 5adf14e3971..8e13d2457ad 100644 --- a/docs/advanced/elec_properties/hs_matrix.md +++ b/docs/advanced/elec_properties/hs_matrix.md @@ -39,7 +39,7 @@ The output filenames depend on the k-point algorithm and `nspin`: Here `${k}` is the one-based k-point index. For `nspin = 2`, the overlap matrix is spin-independent, so only one overlap file is written for each physical k point. The gamma-only algorithm does not support `nspin = 4`; use the multi-k algorithm with an explicit Gamma-only `KPT` file for a noncollinear calculation at Gamma. -When `out_app_flag` is false, `g${step}` is inserted before `_nao`, where `${step}` is the one-based ionic-step index. For example, the first spin channel at the first k point and first ionic step is written to `hk1s1g1_nao.txt`. +When `out_app_flag` is not 1, `g${step}` is inserted before `_nao`, where `${step}` is the one-based ionic-step index. For example, the first spin channel at the first k point and first ionic step is written to `hk1s1g1_nao.txt`. Each output block starts with a comment header containing the one-based ionic-step index, filename, `gamma only` flag, and matrix dimensions. It is followed by `Row 1`, `Row 2`, and so on. Each row contains the matrix elements from the diagonal through the upper triangle. @@ -57,7 +57,7 @@ For `out_hsk 2`, the filenames in the table above use `.dat` instead of `.txt`. The file therefore contains `sizeof(int) + N(N+1)/2 * sizeof(double)` bytes for a gamma-only record and `sizeof(int) + N(N+1) * sizeof(double)` bytes for a multi-k or spinor record. The format uses the host integer representation and byte order and is intended for readers using a compatible ABI. -When `out_app_flag` is true, the first ionic step truncates the shared file and every later step appends another complete record. When it is false, each ionic step has a separate filename containing `g${step}` before `_nao`. +When `out_app_flag` is 1, the first ionic step truncates the shared file and every later step appends another complete record. When it is not 1, each ionic step has a separate filename containing `g${step}` before `_nao`. ## out_hsr @@ -95,11 +95,11 @@ In gamma-only mode, every generated file reports one Bravais lattice vector and ### Native Binary CSR Format -Set `out_hsr 2` to write the same H(R) and S(R) matrix sets with a `.dat` suffix. For example, an `nspin = 2` calculation writes `hrs1_nao.dat`, `hrs2_nao.dat`, and `sr_nao.dat`. When `out_app_flag` is false, the one-based ionic step is included before `_nao`, for example `hrs1g1_nao.dat` and `srg1_nao.dat`. +Set `out_hsr 2` to write the same H(R) and S(R) matrix sets with a `.dat` suffix. For example, an `nspin = 2` calculation writes `hrs1_nao.dat`, `hrs2_nao.dat`, and `sr_nao.dat`. When `out_app_flag` is not 1, the one-based ionic step is included before `_nao`, for example `hrs1g1_nao.dat` and `srg1_nao.dat`. Each ionic step is a complete record with no padding or self-describing header: -1. Native `int`: zero-based ionic step, matrix dimension, number of R blocks. +1. Native `int`: one-based ionic step, matrix dimension, number of R blocks. 2. For every R block in lexicographic `(Rx, Ry, Rz)` order, four native `int` values: `Rx`, `Ry`, `Rz`, and `nnz`. 3. `nnz` matrix values in CSR order. Real matrices use one native `double`; complex matrices use consecutive real and imaginary `double` values. 4. `nnz` native `int` column indices. @@ -107,7 +107,7 @@ Each ionic step is a complete record with no padding or self-describing header: All R blocks stored by the internal HContainer are present, including blocks with zero nonzero values. The sparse threshold is `1e-10`, matching text CSR output. The format uses the host integer representation and byte order and therefore requires a compatible ABI. It does not contain unit-cell, spin, or matrix-label metadata; those are determined by the calculation input and filename. -When `out_app_flag` is true, the first ionic step truncates the shared file and later ionic steps append complete records. Otherwise every ionic step is written to its own file. +When `out_app_flag` is 1, the first ionic step truncates the shared file and later ionic steps append complete records. Otherwise every ionic step is written to its own file. ### NPZ Format diff --git a/docs/advanced/elec_properties/position_matrix.md b/docs/advanced/elec_properties/position_matrix.md index c504a866b64..ccd71f56dcf 100644 --- a/docs/advanced/elec_properties/position_matrix.md +++ b/docs/advanced/elec_properties/position_matrix.md @@ -18,7 +18,7 @@ Each file or each section of the appended file starts with "STEP: " followed by Each block here contains the matrix for the corresponding cell. There are three columns in each block, giving the matrix elements in x, y, z directions, respectively. There are altogether nbasis * nbasis lines in each block, which emulates the matrix elements. -In molecular dynamics (MD) calculations, if [out_app_flag](../input_files/input-main.md#out_app_flag) is set to true, then `data-rR-tr` is written in an append manner. Otherwise, output files will be put in a separate directory, `matrix`, and named as `$x`_data-rR-tr, where `$x` is the number of MD step. In addition, the output frequency is controlled by [out_freq_ion](../input_files/input-main.md#out_freq_ion). For example, if we are running a 10-step MD with out_freq_ion = 3, then `$x` will be 0, 3, 6, and 9. +In molecular dynamics (MD) calculations, the output frequency is controlled by [out_freq_ion](../input_files/input-main.md#out_freq_ion). Each ionic step is written to its own file named `rrg${step}_nao.txt`, where `${step}` is the one-based ionic-step index, while the single-shot output of `calculation = get_s` is named `rr_nao.txt`. The files are stored in `OUT.${suffix}` when [out_app_flag](../input_files/input-main.md#out_app_flag) is 0 or 1, and in its `MAT/` subdirectory when out_app_flag is 2. For example, if we are running a 10-step MD with out_freq_ion = 3 and out_app_flag = 2, the files are `MAT/rrg1_nao.txt`, `MAT/rrg4_nao.txt`, `MAT/rrg7_nao.txt`, and `MAT/rrg10_nao.txt`. ## get_s We also offer the option of only calculating the position matrix without running SCF. For that purpose, in `INPUT` file we need to set the keyword [calculation](../input_files/input-main.md#calculation) to `get_s`, and [out_mat_r](../input_files/input-main.md#out_mat_r) to `true`. diff --git a/docs/advanced/elec_properties/wfc.md b/docs/advanced/elec_properties/wfc.md index 354d8844fc4..562b0c4b32b 100644 --- a/docs/advanced/elec_properties/wfc.md +++ b/docs/advanced/elec_properties/wfc.md @@ -18,7 +18,7 @@ The [`init_wfc file binary`](../scf/initialization.md#wavefunction) path reads o For `basis_type=lcao`, [`out_wfc_lcao`](https://abacus-rtd.readthedocs.io/en/latest/advanced/input_files/input-main.html#out-wfc-lcao) controls the output of numerical-atomic-orbital coefficients: `1` writes text files with the `.txt` suffix and `2` writes binary files with the `.dat` suffix. Multi-k calculations generate `wfs{spin}k{k-point}_nao.txt` or `.dat`, while Gamma-only calculations generate `wfs{spin}_nao.txt` or `.dat`. -To reuse LCAO coefficients, point `read_file_dir` to the coefficient files and set `init_wfc file txt` for text input or `init_wfc file binary` for binary input. The selected extension is required; there is no automatic format fallback. Only independent files without geometry-step indices are supported, not files accumulated with `out_app_flag`. +To reuse LCAO coefficients, point `read_file_dir` to the coefficient files and set `init_wfc file txt` for text input or `init_wfc file binary` for binary input. The selected extension is required; there is no automatic format fallback. Only independent files without geometry-step indices are supported, not files accumulated with `out_app_flag=1`. ## Wave Function in Real Space diff --git a/docs/advanced/output_files/output-specification.md b/docs/advanced/output_files/output-specification.md index 71e55026865..55a2b6babb5 100644 --- a/docs/advanced/output_files/output-specification.md +++ b/docs/advanced/output_files/output-specification.md @@ -55,6 +55,12 @@ All output file naming conventions can be found in the online documentation (wit - All index numbers start from 1 (not 0) - For Gamma-only algorithm in LCAO, no `k` index is included - Overlap matrix `s` does not distinguish spin, so only one matrix is output +- `out_app_flag = 1` omits the `g#` suffix for cube files (chg/tau/pot/elf): the + first written ionic step truncates the fixed-name file and every later + written step appends one complete cube frame whose first comment line is + `Ionic_Step {#}` (one-based). `out_app_flag = 0` writes one suffixed file per + step, and `out_app_flag = 2` writes those suffixed files into the `CHG/`, + `POT/` and `ELF/` subdirectories - For initial charge density output (`out_chg = 2`): - `out_freq_ion = 0`: `chg_ini.cube` (nspin=1) or `chgs{#}_ini.cube` (nspin=2/4) - output at every step (overwrite same file) - `out_freq_ion > 0`: `chgg{#}_ini.cube` (nspin=1) or `chgs{#}g{#}_ini.cube` (nspin=2/4) - output every out_freq_ion steps @@ -98,7 +104,7 @@ All output file naming conventions can be found in the online documentation (wit | `doss1g1.txt` | Density of states | | `eig_occ.txt` | Eigenvalues and occupations | | `mulliken.txt` | Mulliken population analysis | -| `band.txt` | Band structure | +| `band.txt` | Band structure (nspin=1/4); `bands1.txt`/`bands2.txt` when nspin=2 | | `chgs1.cube`, `chgs2.cube` | Charge density (spin 1, spin 2) | | `chg.cube` | Total charge density | | Initial charge density (out_chg=2) | | @@ -121,6 +127,21 @@ All output file naming conventions can be found in the online documentation (wit | `elfs{#}g{#}.cube` | ELF, spin {#}, geometry step {#} (nspin=2) | | `elftotg{#}.cube` | ELF, total, geometry step {#} (nspin=2) | +**Band structure files (`out_band`):** each ionic step of `band.txt`, +`bands1.txt` and `bands2.txt` is preceded by a provenance comment block, so the +k-point blocks appended by a relax/md run stay distinguishable: + +``` +# ABACUS version: v3.11.0-beta11 +# Written at 2026-10-20 15:40:45 +# IONIC STEP 1 (spin up) + 1 0.00000000 -5.36802595 6.70427084 ... +``` + +`# IONIC STEP` is one-based and carries the collinear spin-channel label only +when `nspin = 2`. Consumers that parse the numeric columns must skip lines +starting with `#`. + ## 3. File Format Standards ### 3.1 Header Section with Comments diff --git a/docs/advanced/scf/initialization.md b/docs/advanced/scf/initialization.md index 427146bf223..4517b691b44 100644 --- a/docs/advanced/scf/initialization.md +++ b/docs/advanced/scf/initialization.md @@ -26,7 +26,7 @@ For `basis_type=pw`, the available options are: * `nao+random`: Apply an approximately 5% multiplicative random perturbation to the NAO initialization; any bands not covered by NAO states are first initialized randomly. * `file binary`: Read binary `wf*_pw.dat` files generated with [`out_wfc_pw=2`](../elec_properties/wfc.md) from `read_file_dir`. The files must match the current k points, `nbands`, plane-wave layout, and lattice. The `txt` format is not supported for PW wavefunctions. -For `basis_type=lcao`, use `init_wfc file txt` to read text `wf*_nao.txt` files generated with [`out_wfc_lcao=1`](../elec_properties/wfc.md), or `init_wfc file binary` to read binary `wf*_nao.dat` files generated with `out_wfc_lcao=2`, from `read_file_dir`. LCAO wavefunctions initialize the density matrix and real-space charge density. The selected format is required; ABACUS does not automatically detect or fall back to the other format. The files must use a compatible NAO basis, match the current k-point and spin setup, and contain enough bands. Only independent files without geometry-step indices are supported, not files accumulated with `out_app_flag`. +For `basis_type=lcao`, use `init_wfc file txt` to read text `wf*_nao.txt` files generated with [`out_wfc_lcao=1`](../elec_properties/wfc.md), or `init_wfc file binary` to read binary `wf*_nao.dat` files generated with `out_wfc_lcao=2`, from `read_file_dir`. LCAO wavefunctions initialize the density matrix and real-space charge density. The selected format is required; ABACUS does not automatically detect or fall back to the other format. The files must use a compatible NAO basis, match the current k-point and spin setup, and contain enough bands. Only independent files without geometry-step indices are supported, not files accumulated with `out_app_flag=1`. For `basis_type=lcao_in_pw`, `init_wfc` is automatically set to `nao`. diff --git a/examples/17_relax/04_relax_with_output_lcao/INPUT b/examples/17_relax/04_relax_with_output_lcao/INPUT index f58cf4201de..e8408708355 100644 --- a/examples/17_relax/04_relax_with_output_lcao/INPUT +++ b/examples/17_relax/04_relax_with_output_lcao/INPUT @@ -34,7 +34,6 @@ out_dmr 1 out_dos 1 out_band 1 out_stru 1 -out_app_flag 0 out_mat_l 1 out_mat_r 1 @@ -42,3 +41,5 @@ out_mat_t 1 #out_mat_dh 1 out_freq_ion 2 + +out_app_flag 2 diff --git a/examples/18_md/02_lcao_output/INPUT b/examples/18_md/02_lcao_output/INPUT index 7c29d6e1355..c8333b2f0e8 100644 --- a/examples/18_md/02_lcao_output/INPUT +++ b/examples/18_md/02_lcao_output/INPUT @@ -36,3 +36,5 @@ md_tchain 1 dft_functional hse restart_save 1 + +out_app_flag 0 diff --git a/interfaces/Multiwfn_interface/molden.py b/interfaces/Multiwfn_interface/molden.py index 1935267a2c9..856cf55aad9 100644 --- a/interfaces/Multiwfn_interface/molden.py +++ b/interfaces/Multiwfn_interface/molden.py @@ -533,6 +533,10 @@ def read_abacus_lowf(flowf, pat=r'^(WFC_NAO_(K\d+|GAMMA\d+)(_ION\d+)?|wf((k\d+)? with open(flowf, 'r') as file: lines = file.readlines() + # ignore provenance comment lines (version/timestamp/ionic step) that + # ABACUS writes before the data block + lines = [line for line in lines if not line.lstrip().startswith('#')] + if lines[0].endswith('(index of k points)\n'): # discard the first two lines lines = lines[2:] diff --git a/source/source_base/global_file.cpp b/source/source_base/global_file.cpp index 76a16036be7..1889357c2c8 100644 --- a/source/source_base/global_file.cpp +++ b/source/source_base/global_file.cpp @@ -30,7 +30,6 @@ void ModuleBase::Global_File::make_dir_out( const bool &restart, const bool out_alllog, const std::string &global_out_dir, - const std::string &global_stru_dir, const std::string &global_matrix_dir, const std::string &global_wfc_dir, const std::string &global_mlkedf_descriptor_dir, @@ -84,45 +83,6 @@ void ModuleBase::Global_File::make_dir_out( MPI_Barrier(MPI_COMM_WORLD); #endif - if(calculation == "md") - { - int make_dir_stru = 0; - - times = 0; - while(times0) { break; -} - ++times; - } - -#ifdef __MPI - if(make_dir_stru==0) - { - std::cout << " CAN NOT MAKE THE STRU DIR......." << std::endl; - ModuleBase::QUIT(); - } - MPI_Barrier(MPI_COMM_WORLD); -#endif - } - // make dir for HS matrix output in md calculation if((out_dir) && calculation == "md") { diff --git a/source/source_base/global_file.h b/source/source_base/global_file.h index d051b5e81c4..4ce5adb18d3 100644 --- a/source/source_base/global_file.h +++ b/source/source_base/global_file.h @@ -28,7 +28,6 @@ namespace Global_File const bool &restart, const bool out_alllog, const std::string &global_out_dir, - const std::string &global_stru_dir, const std::string &global_matrix_dir, const std::string &global_wfc_dir, const std::string &global_mlkedf_descriptor_dir, diff --git a/source/source_base/module_out/filename.cpp b/source/source_base/module_out/filename.cpp index 4dd86dc2bea..c7fa58fb9eb 100644 --- a/source/source_base/module_out/filename.cpp +++ b/source/source_base/module_out/filename.cpp @@ -14,7 +14,7 @@ std::string filename_output( const int nspin, const int nkstot, const int out_type, - const bool out_app_flag, + const int out_app_flag, const bool gamma_only, const int istep, const int iter) @@ -97,14 +97,14 @@ std::string filename_output( } std::string istep_block - = (istep >= 0 && (!out_app_flag)) + = (istep >= 0 && (out_app_flag != 1)) ? "g" + std::to_string(istep + 1) // istep starts from 0 - : ""; // only when istep >= 0 and out_app_flag is false will write each wfc to a separate file + : ""; // only when istep >= 0 and out_app_flag is not append mode will write each wfc to a separate file std::string iter_block - = (iter >= 0 && (!out_app_flag)) + = (iter >= 0 && (out_app_flag != 1)) ? "e" + std::to_string(iter) // iter starts from 1 - : ""; // only when istep >= 0 and out_app_flag is false will write each wfc to a separate file + : ""; // only when istep >= 0 and out_app_flag is not append mode will write each wfc to a separate file std::string suffix_block; if (out_type == 1) diff --git a/source/source_base/module_out/filename.h b/source/source_base/module_out/filename.h index d76970c769f..d4c351e3a1e 100644 --- a/source/source_base/module_out/filename.h +++ b/source/source_base/module_out/filename.h @@ -16,7 +16,7 @@ namespace ModuleIO * @param nspin: number of spin channels, 1,2 or 4 * @param nkstot: number of total k-points * @param out_type: two types of output file format, 1 for .txt and 2 for .dat (binary) - * @param out_app_flag: whether to append to existing file. + * @param out_app_flag: 0=no append, 1=append to same file, 2=write to subdirectory. * @param gamma_only: gamma_only algorithm or not. * @param istep: index of the ion step starting from 0. If < 0, the step number is not included in the file name. * @param iter: index of the electronic step starting from 0. If < 0, the step number is not included in the file name. @@ -31,7 +31,7 @@ std::string filename_output( const int nspin, const int nkstot, const int out_type, - const bool out_app_flag, + const int out_app_flag, const bool gamma_only, const int istep=-1, const int iter=-1); diff --git a/source/source_base/test/CMakeLists.txt b/source/source_base/test/CMakeLists.txt index 16ba949d640..7a48043bef6 100644 --- a/source/source_base/test/CMakeLists.txt +++ b/source/source_base/test/CMakeLists.txt @@ -280,3 +280,9 @@ if(ENABLE_GOOGLEBENCH) SOURCES perf_sphbes_test.cpp ../math_sphbes.cpp ../timer.cpp ) endif() + +AddTest( + TARGET MODULE_BASE_filename + LIBS parameter base device + SOURCES test_filename.cpp +) diff --git a/source/source_base/test/global_file_test.cpp b/source/source_base/test/global_file_test.cpp index 07184237553..fe7bdac7446 100644 --- a/source/source_base/test/global_file_test.cpp +++ b/source/source_base/test/global_file_test.cpp @@ -25,7 +25,6 @@ class GlobalFile : public testing::Test { protected: std::string global_out_dir; - std::string global_stru_dir; std::string global_matrix_dir; std::string global_wfc_dir; std::string global_mlkedf_descriptor_dir; @@ -34,7 +33,6 @@ class GlobalFile : public testing::Test { const std::string suffix = "Si"; global_out_dir = "OUT." + suffix + "/"; - global_stru_dir = global_out_dir + "STRU/"; global_matrix_dir = global_out_dir + "matrix/"; global_wfc_dir = global_out_dir + "WFC/"; global_mlkedf_descriptor_dir = global_out_dir + "MLKEDF_Descriptors/"; @@ -47,7 +45,7 @@ TEST_F(GlobalFile,mkdirout) std::string output; testing::internal::CaptureStdout(); ModuleBase::Global_File::make_dir_out("Si","m",false,false,0,true,true, - global_out_dir, global_stru_dir, global_matrix_dir, global_wfc_dir, + global_out_dir, global_matrix_dir, global_wfc_dir, global_mlkedf_descriptor_dir, global_deepks_label_elec_dir, "running_m_1.log", false, false); output = testing::internal::GetCapturedStdout(); @@ -58,22 +56,9 @@ TEST_F(GlobalFile,mkdirout) std::string dd = "OUT.Si/running_m_1.log"; remove(dd.c_str()); - testing::internal::CaptureStdout(); - ModuleBase::Global_File::make_dir_out("Si","md",false,false,0,true,false, - global_out_dir, global_stru_dir, global_matrix_dir, global_wfc_dir, - global_mlkedf_descriptor_dir, global_deepks_label_elec_dir, - "running_md.log", false, false); - output = testing::internal::GetCapturedStdout(); - EXPECT_THAT(output,testing::HasSubstr("MAKE THE STRU DIR")); - EXPECT_TRUE(GlobalV::ofs_running.is_open()); - GlobalV::ofs_running.close(); - GlobalV::ofs_warning.close(); - std::string bb = "OUT.Si/running_md.log"; - remove(bb.c_str()); - testing::internal::CaptureStdout(); ModuleBase::Global_File::make_dir_out("Si","md",true,false,0,true,true, - global_out_dir, global_stru_dir, global_matrix_dir, global_wfc_dir, + global_out_dir, global_matrix_dir, global_wfc_dir, global_mlkedf_descriptor_dir, global_deepks_label_elec_dir, "running_md_1.log", false, false); output = testing::internal::GetCapturedStdout(); @@ -84,7 +69,6 @@ TEST_F(GlobalFile,mkdirout) remove(cc.c_str()); std::string aa = "OUT.Si/warning.log"; remove(aa.c_str()); - rmdir(global_stru_dir.c_str()); rmdir(global_matrix_dir.c_str()); rmdir(global_out_dir.c_str()); } diff --git a/source/source_base/test/test_filename.cpp b/source/source_base/test/test_filename.cpp new file mode 100644 index 00000000000..5bffa1fba8c --- /dev/null +++ b/source/source_base/test/test_filename.cpp @@ -0,0 +1,120 @@ +#ifndef TEST_FILENAME_CPP +#define TEST_FILENAME_CPP + +#include +#include +#include +#include "source_base/module_out/filename.h" + +// Keep local names short; do not pollute. +using ModuleIO::filename_output; + +// --------------------------------------------------------------------------- +// Fixture: minimal ik2iktot mapping for a 2-k-point pool. +// --------------------------------------------------------------------------- +class FilenameOutputTest : public ::testing::Test +{ +protected: + const std::vector ik2iktot = {0, 1}; + const int ik_local = 0; + const int nkstot = 2; + const int nspin = 2; + const std::string dir = "OUT.test/"; +}; + +// --------------------------------------------------------------------------- +// mode 0 = no append -> per-step g{N} suffix included. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, Mode0IstepAddsG) +{ + // PW wavefunction, binary, k1, spin up (ik0=0 < half_k=1 -> s1). + const std::string fn = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/2, /*out_app_flag=*/0, /*gamma_only=*/false, + /*istep=*/1, /*iter=*/-1); + EXPECT_EQ(fn, "OUT.test/wfk1s1g2_pw.dat"); +} + +// --------------------------------------------------------------------------- +// mode 1 = append -> fixed name, no g suffix even when istep >= 0. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, Mode1FixedNameNoG) +{ + const std::string fn = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/2, /*out_app_flag=*/1, /*gamma_only=*/false, + /*istep=*/1, /*iter=*/-1); + EXPECT_EQ(fn, "OUT.test/wfk1s1_pw.dat"); +} + +// --------------------------------------------------------------------------- +// mode 2 = write to subdirectory -> same naming as mode 0 (g suffix kept). +// The directory routing is handled by the caller (set_global_dir), so +// filename_output itself treats modes 0 and 2 identically. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, Mode2SameNamingAsMode0) +{ + const std::string fn = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/2, /*out_app_flag=*/2, /*gamma_only=*/false, + /*istep=*/1, /*iter=*/-1); + EXPECT_EQ(fn, "OUT.test/wfk1s1g2_pw.dat"); +} + +// --------------------------------------------------------------------------- +// istep < 0 (SCF single-point) -> no g suffix in any mode. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, SinglePointNoG) +{ + const std::string fn = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/2, /*out_app_flag=*/0, /*gamma_only=*/false, + /*istep=*/-1, /*iter=*/-1); + EXPECT_EQ(fn, "OUT.test/wfk1s1_pw.dat"); +} + +// --------------------------------------------------------------------------- +// iter >= 0 adds e{N} in modes 0/2, but is suppressed in mode 1 too. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, IterIndexRespectsAppendFlag) +{ + // mode 0: both g and e present. + const std::string fn_mode0 = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/1, /*out_app_flag=*/0, /*gamma_only=*/false, + /*istep=*/1, /*iter=*/3); + EXPECT_EQ(fn_mode0, "OUT.test/wfk1s1g2e3_pw.txt"); + + // mode 1: neither g nor e. + const std::string fn_mode1 = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/1, /*out_app_flag=*/1, /*gamma_only=*/false, + /*istep=*/1, /*iter=*/3); + EXPECT_EQ(fn_mode1, "OUT.test/wfk1s1_pw.txt"); +} + +// --------------------------------------------------------------------------- +// gamma_only suppresses the k-point label for non-hk/sk/tk properties. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, GammaOnlyNoKLabel) +{ + const std::string fn = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot, + /*out_type=*/2, /*out_app_flag=*/1, /*gamma_only=*/true, + /*istep=*/-1, /*iter=*/-1); + EXPECT_EQ(fn, "OUT.test/wfs1_pw.dat"); +} + +// --------------------------------------------------------------------------- +// nspin=4 (noncollinear SOC) -> spin block is s4, no half-k split. +// --------------------------------------------------------------------------- +TEST_F(FilenameOutputTest, NoncollinearSpin4) +{ + const std::string fn = filename_output( + dir, "wf", "pw", ik_local, ik2iktot, /*nspin=*/4, nkstot, + /*out_type=*/2, /*out_app_flag=*/2, /*gamma_only=*/false, + /*istep=*/0, /*iter=*/-1); + EXPECT_EQ(fn, "OUT.test/wfk1s4g1_pw.dat"); +} + +#endif // TEST_FILENAME_CPP diff --git a/source/source_cell/cell_index.cpp b/source/source_cell/cell_index.cpp index c147afa936f..f6b9d3d8362 100644 --- a/source/source_cell/cell_index.cpp +++ b/source/source_cell/cell_index.cpp @@ -264,7 +264,7 @@ void CellIndex::cal_orbitalCounts() void CellIndex::write_orb_info(const std::string& out_dir) const { std::stringstream os; - os << out_dir << "Orbital"; + os << out_dir << "nao.txt"; std::ofstream out(os.str().c_str()); out << std::setw(5) << "#io" << std::setw(8) << "spec" << std::setw(5) << "l" << std::setw(5) << "m" << std::setw(5) << "z" << std::setw(5) << "sym" << std::endl; diff --git a/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp b/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp index 50c907ab4f0..e23707c7aab 100644 --- a/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp +++ b/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp @@ -1,4 +1,5 @@ #include "source_cell/domain_decomposition.h" +#include #include #include "source_cell/mdcell_reader.h" @@ -227,7 +228,7 @@ TEST(MDCellReaderTest, RestartStruPreservesAtomRecordsAcrossRanks) StruMeta metadata; metadata.species.resize(2); const std::string output_file = "distributed_mdcell_restart.STRU"; - mdcell::print_stru_file(mdcell, metadata, output_file); + mdcell::print_stru_file(mdcell, metadata, output_file, "", false); DomainDecomposition reader_decomp; MDCell round_trip = MDCellReader::read_stru(output_file, @@ -319,6 +320,71 @@ TEST(MDCellReaderTest, RestartStruPreservesAtomRecordsAcrossRanks) if (rank == 0) std::remove(output_file.c_str()); } +// --------------------------------------------------------------------------- +// mdcell::print_stru_file append + frame_comment. +// --------------------------------------------------------------------------- +TEST(MDCellReaderTest, STRUAppendWithFrameComment) +{ + int rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + std::vector owned_atoms; + owned_atoms.push_back(LocalAtom(ModuleBase::Vector3(0.0, 0.0, 0.0), + ModuleBase::Vector3(0.0, 0.0, 0.0), + ModuleBase::Vector3(0.0, 0.0, 0.0), + ModuleBase::Vector3(0.0, 0.0, 0.0), + ModuleBase::Vector3(1, 0, 1), + 1.0, + 0, + 0, + rank)); + + ModuleBase::Matrix3 lattice = make_lattice(); + MDCell mdcell; + mdcell.initialize_from_owned_atoms(lattice, + lattice.Inverse(), + 1.0, + 1.0, + 1, + owned_atoms, + std::vector{"A"}, + std::vector{1.0}, + std::vector{1}, + 0.0, + ModuleBase::world_comm_domain()); + StruMeta metadata; + metadata.species.resize(1); + const std::string output_file = "mdcell_append_test.STRU"; + + // First frame: append=false, frame comment set. + mdcell::print_stru_file(mdcell, metadata, output_file, + "# MD step 1\n\n", /*append=*/false); + // Second frame: append=true with a different comment. + mdcell::print_stru_file(mdcell, metadata, output_file, + "# MD step 2\n\n", /*append=*/true); + + if (rank == 0) + { + std::ifstream ifs(output_file.c_str()); + std::string str((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + // Both frame markers must be present. + EXPECT_THAT(str, testing::HasSubstr("# MD step 1")); + EXPECT_THAT(str, testing::HasSubstr("# MD step 2")); + // Two STRU blocks -> two ATOMIC_SPECIES markers. + int occurrences = 0; + size_t pos = 0; + while ((pos = str.find("ATOMIC_SPECIES", pos)) != std::string::npos) + { + ++occurrences; + pos += 14; + } + EXPECT_EQ(occurrences, 2); + std::remove(output_file.c_str()); + } +} + int main(int argc, char** argv) { MPI_Init(&argc, &argv); diff --git a/source/source_cell/print_cell.cpp b/source/source_cell/print_cell.cpp index f98b3ab8a99..2697bd1a961 100644 --- a/source/source_cell/print_cell.cpp +++ b/source/source_cell/print_cell.cpp @@ -101,7 +101,8 @@ namespace unitcell const bool& dpks_desc, const int& iproc, const ModuleBase::matrix& force, - const bool& has_force) + const bool& has_force, + const bool append) { ModuleBase::TITLE("UnitCell","print_stru_file"); if (iproc != 0) @@ -235,7 +236,12 @@ namespace unitcell nat_++; } } - std::ofstream ofs(fn.c_str()); + std::ios_base::openmode mode = std::ios::out; + if (append) + { + mode |= std::ios::app; + } + std::ofstream ofs(fn.c_str(), mode); ofs << str; ofs.close(); return; @@ -368,7 +374,8 @@ StruMeta make_stru_meta(const UnitCell& ucell) namespace mdcell { -void print_stru_file(const MDCell& cell, const StruMeta& stru_meta, const std::string& fn) +void print_stru_file(const MDCell& cell, const StruMeta& stru_meta, const std::string& fn, + const std::string& frame_comment, const bool append) { if (stru_meta.species.size() != cell.type_labels().size() || stru_meta.species.size() != cell.type_masses().size() @@ -376,27 +383,35 @@ void print_stru_file(const MDCell& cell, const StruMeta& stru_meta, const std::s { throw std::runtime_error("MDCell STRU metadata does not match the MDCell type data."); } - const std::string header = mdcell_stru_header(cell, stru_meta); + const std::string header = frame_comment + mdcell_stru_header(cell, stru_meta); #ifdef __MPI int rank = 0; const MPI_Comm comm = cell.communicator(); MPI_Comm_rank(comm, &rank); int header_ok = 1; + MPI_Offset base_offset = 0; if (rank == 0) { - const int header_file = open(fn.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0666); + // No O_APPEND: on Linux pwrite() ignores the given offset when the + // file was opened with O_APPEND. Truncate unless appending, then + // seek to the current end to learn the base offset. + const int open_flags = O_CREAT | O_WRONLY | (append ? 0 : O_TRUNC); + const int header_file = open(fn.c_str(), open_flags, 0666); if (header_file < 0) { header_ok = 0; } else { - const bool header_written = write_at(header_file, header, 0); + const off_t end_pos = lseek(header_file, 0, SEEK_END); + base_offset = end_pos >= 0 ? static_cast(end_pos) : 0; + const bool header_written = end_pos >= 0 && write_at(header_file, header, base_offset); const bool header_closed = close(header_file) == 0; header_ok = header_written && header_closed; } } MPI_Bcast(&header_ok, 1, MPI_INT, 0, comm); + MPI_Bcast(&base_offset, 1, MPI_OFFSET, 0, comm); if (header_ok == 0) { throw std::runtime_error("Unable to create MDCell restart STRU file: " + fn + ": " + std::strerror(errno)); @@ -413,7 +428,7 @@ void print_stru_file(const MDCell& cell, const StruMeta& stru_meta, const std::s throw std::runtime_error("Unable to open MDCell restart STRU file: " + fn + ": " + std::strerror(errno)); } - MPI_Offset offset = static_cast(header.size()); + MPI_Offset offset = base_offset + static_cast(header.size()); for (std::size_t it = 0; it < stru_meta.species.size(); ++it) { const std::string type_header = mdcell_type_header(cell, stru_meta, it); @@ -453,7 +468,12 @@ void print_stru_file(const MDCell& cell, const StruMeta& stru_meta, const std::s throw std::runtime_error("Unable to close MDCell restart STRU file: " + fn + ": " + std::strerror(errno)); } #else - std::ofstream output(fn.c_str()); + std::ios_base::openmode mode = std::ios::out; + if (append) + { + mode |= std::ios::app; + } + std::ofstream output(fn.c_str(), mode); output << header; for (std::size_t it = 0; it < stru_meta.species.size(); ++it) output << mdcell_type_header(cell, stru_meta, it) << local_mdcell_atoms(cell, it); diff --git a/source/source_cell/print_cell.h b/source/source_cell/print_cell.h index 1370629b0cd..ab39ddcf0dc 100644 --- a/source/source_cell/print_cell.h +++ b/source/source_cell/print_cell.h @@ -51,6 +51,8 @@ namespace unitcell * @param iproc GlobalV::MY_RANK [in] * @param force atomic forces in Ry/Bohr, used only when has_force is true [in] * @param has_force true to emit per-atom f fields; force matrix must match nat [in] + * @param append true to append the structure block to an existing file + * (out_app_flag == 1 multi-step runs); false truncates [in] */ void print_stru_file(const UnitCell& ucell, const Atom* atoms, @@ -64,7 +66,8 @@ namespace unitcell const bool& dpks_desc, const int& iproc, const ModuleBase::matrix& force, - const bool& has_force); + const bool& has_force, + const bool append); /** * @brief Print basic unitcell information to output stream. @@ -79,9 +82,22 @@ namespace unitcell namespace mdcell { +/** + * @brief Write an MDCell structure file in STRU format. + * + * @param mdcell distributed MD cell data [in] + * @param stru_meta species/orbital metadata used for the header [in] + * @param fn output file path [in] + * @param frame_comment comment block prepended before the structure header + * (e.g. "# MD step N\n\n"); pass an empty string for no marker [in] + * @param append true to append the frame to an existing file + * (out_app_flag == 1 multi-step runs); false truncates [in] + */ void print_stru_file(const MDCell& mdcell, const StruMeta& stru_meta, - const std::string& fn); + const std::string& fn, + const std::string& frame_comment, + const bool append); } #endif diff --git a/source/source_cell/test/cell_index_test.cpp b/source/source_cell/test/cell_index_test.cpp index e0767ad6aaf..06a932c92d7 100644 --- a/source/source_cell/test/cell_index_test.cpp +++ b/source/source_cell/test/cell_index_test.cpp @@ -56,11 +56,11 @@ TEST_F(CellIndexTest, Index) TEST_F(CellIndexTest, WriteOrbInfo) { cell_index.write_orb_info("./"); - std::ifstream ifs("./Orbital"); + std::ifstream ifs("./nao.txt"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); EXPECT_THAT(str, testing::HasSubstr("#io spec l m z sym")); EXPECT_THAT(str, testing::HasSubstr("0 C 2 4 1 dxy")); EXPECT_THAT(str, testing::HasSubstr("1 H 2 4 1 dxy")); EXPECT_THAT(str, testing::HasSubstr("2 H 2 4 1 dxy")); - remove("./Orbital"); + remove("./nao.txt"); } \ No newline at end of file diff --git a/source/source_cell/test/read_atoms_hlp_test.cpp b/source/source_cell/test/read_atoms_hlp_test.cpp index c536fa4eabf..194a9f67fc3 100644 --- a/source/source_cell/test/read_atoms_hlp_test.cpp +++ b/source/source_cell/test/read_atoms_hlp_test.cpp @@ -684,7 +684,7 @@ TEST_F(ReadAtomsHelperTest, RoundTripMultiAtomMixedFields) const std::string filename = "test_stru_roundtrip.tmp"; unitcell::print_stru_file(ucell, ucell.atoms, ucell.latvec, filename, "", - 2, false, false, false, false, 0, force, true); + 2, false, false, false, false, 0, force, true, false); std::ifstream ifpos(filename.c_str()); diff --git a/source/source_cell/test/test_print_cell.cpp b/source/source_cell/test/test_print_cell.cpp index b1d131ffcb4..c4ff6e08ae7 100644 --- a/source/source_cell/test/test_print_cell.cpp +++ b/source/source_cell/test/test_print_cell.cpp @@ -48,7 +48,7 @@ TEST_F(PrintCellTest, PrintSTRU_nspin1) std::string fn = "C1H2_STRU_nspin1"; unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 1, false, false, false, false, 0, ModuleBase::matrix(), false); + fn, "", 1, false, false, false, false, 0, ModuleBase::matrix(), false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -91,7 +91,7 @@ TEST_F(PrintCellTest, PrintSTRU_zero_force_computed) force.zero_out(); unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 2, false, false, false, false, 0, force, true); + fn, "", 2, false, false, false, false, 0, force, true, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -114,7 +114,7 @@ TEST_F(PrintCellTest, PrintSTRU_force_matrix_without_flag) force(0, 0) = 0.1; force(0, 1) = 0.2; force(0, 2) = 0.3; unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 2, false, false, false, false, 0, force, false); + fn, "", 2, false, false, false, false, 0, force, false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -131,7 +131,7 @@ TEST_F(PrintCellTest, PrintSTRU_nspin2_no_force) std::string fn = "C1H2_STRU_nspin2"; unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 2, true, false, false, false, 0, ModuleBase::matrix(), false); + fn, "", 2, true, false, false, false, 0, ModuleBase::matrix(), false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -163,7 +163,7 @@ TEST_F(PrintCellTest, PrintSTRU_nspin2_with_mag) ucell->atoms[1].mag[0] = -0.5; // H1 ucell->atoms[1].mag[1] = 2.0; // H2 unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 2, false, false, false, false, 0, ModuleBase::matrix(), false); + fn, "", 2, false, false, false, false, 0, ModuleBase::matrix(), false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -192,7 +192,7 @@ TEST_F(PrintCellTest, PrintSTRU_nspin2_mulliken) ucell->orbital_fn[1] = "__unittest_orbital_fn_1__"; ucell->atom_mulliken = {{-1, 0.5}, {-1, 0.4}, {-1, 0.3}}; unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 2, false, true, true, true, 0, ModuleBase::matrix(), false); + fn, "", 2, false, true, true, true, 0, ModuleBase::matrix(), false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -221,7 +221,7 @@ TEST_F(PrintCellTest, PrintSTRU_nspin4_initial_mag) ucell->atoms[1].m_loc_[0].set(0.0, 1.0, 0.0); // H1 ucell->atoms[1].m_loc_[1].set(0.0, 0.0, 1.0); // H2 unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 4, false, false, false, false, 0, ModuleBase::matrix(), false); + fn, "", 4, false, false, false, false, 0, ModuleBase::matrix(), false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -247,7 +247,7 @@ TEST_F(PrintCellTest, PrintSTRU_nspin4_mulliken) ucell->atom_mulliken = {{-1, 0.5, 0.1, 0.2}, {-1, 0.4, 0.3, 0.4}, {-1, 0.3, 0.5, 0.6}}; unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 4, false, true, false, false, 0, ModuleBase::matrix(), false); + fn, "", 4, false, true, false, false, 0, ModuleBase::matrix(), false, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -274,7 +274,7 @@ TEST_F(PrintCellTest, PrintSTRU_with_force) force(2, 0) = 0.05; force(2, 1) = 0.15; force(2, 2) = -0.25; // H2 unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, - fn, "", 2, false, false, false, false, 0, force, true); + fn, "", 2, false, false, false, false, 0, force, true, false); std::ifstream ifs; ifs.open(fn); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -390,3 +390,38 @@ TEST_F(PrintCellTest, PrintTauCartesian) // remove the file remove("print_tau_Cartesian"); } + +// --------------------------------------------------------------------------- +// STRU append flag: append=true accumulates successive structures in one file. +// --------------------------------------------------------------------------- +TEST_F(PrintCellTest, STRUAppend) +{ + UcellTestPrepare utp = UcellTestLib["C1H2-Index"]; + ucell = utp.SetUcellInfo(); + const std::string fn = "STRU_append_test"; + + // First call: append=false -> create/truncate. + unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, + fn, "", 1, false, false, false, false, 0, ModuleBase::matrix(), false, + /*append=*/false); + // Second call: append=true -> accumulate into same file. + unitcell::print_stru_file(*ucell, ucell->atoms, ucell->latvec, + fn, "", 1, false, false, false, false, 0, ModuleBase::matrix(), false, + /*append=*/true); + + std::ifstream ifs(fn); + std::string str((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + // Two STRU blocks -> two ATOMIC_SPECIES markers. + int occurrences = 0; + size_t pos = 0; + while ((pos = str.find("ATOMIC_SPECIES", pos)) != std::string::npos) + { + ++occurrences; + pos += 14; + } + EXPECT_EQ(occurrences, 2); + + remove(fn.c_str()); +} diff --git a/source/source_esolver/esolver_dm2rho.cpp b/source/source_esolver/esolver_dm2rho.cpp index 77b7825cc34..6c5dc31302a 100644 --- a/source/source_esolver/esolver_dm2rho.cpp +++ b/source/source_esolver/esolver_dm2rho.cpp @@ -99,6 +99,7 @@ void ESolver_DM2rho::runner(BaseCell& basecell, const int istep) 1, PARAM.globalv.two_fermi, false, + false, GlobalV::ofs_running, desc_chg); } diff --git a/source/source_esolver/esolver_ks.cpp b/source/source_esolver/esolver_ks.cpp index 9d12dfe07dd..01f08d8d78d 100644 --- a/source/source_esolver/esolver_ks.cpp +++ b/source/source_esolver/esolver_ks.cpp @@ -440,7 +440,7 @@ void ESolver_KS::after_scf(UnitCell& ucell, const int istep, const bool conv_eso istep); // 4) write band information to band.txt - ModuleIO::write_bands(*this->inp_, this->pelec->ekb, this->kv, GlobalV::ofs_running); + ModuleIO::write_bands(*this->inp_, this->pelec->ekb, this->kv, GlobalV::ofs_running, istep); } diff --git a/source/source_esolver/esolver_ks_lcao.cpp b/source/source_esolver/esolver_ks_lcao.cpp index bb07dc88947..4fcb00e7e22 100644 --- a/source/source_esolver/esolver_ks_lcao.cpp +++ b/source/source_esolver/esolver_ks_lcao.cpp @@ -165,6 +165,7 @@ void ESolver_KS_LCAO::before_all_runners(BaseCell& basecell, const Input template void ESolver_KS_LCAO::before_scf(UnitCell& ucell, const int istep) { + this->istep_ = istep; this->before_scf(ucell, istep, istep); } @@ -443,6 +444,7 @@ void ESolver_KS_LCAO::after_all_runners(BaseCell& basecell) ModuleIO::ctrl_runner_lcao(ucell, *this->inp_, + this->istep_, this->kv, this->pelec, this->dmat, diff --git a/source/source_esolver/esolver_ks_lcao.h b/source/source_esolver/esolver_ks_lcao.h index 6fca9c417ec..093625c3210 100644 --- a/source/source_esolver/esolver_ks_lcao.h +++ b/source/source_esolver/esolver_ks_lcao.h @@ -60,6 +60,11 @@ class ESolver_KS_LCAO : public ESolver_KS //! Prepare an ionic step with a separate index for initial charge and potential output. void before_scf(UnitCell& ucell, const int istep, const int output_step); + /// Ionic step index of the most recent ionic step, recorded in before_scf(). + /// Read by after_all_runners() when labelling provenance headers of files + /// written once at the end of a run. + int istep_ = 0; + //! Prepare charge mixing and Hamiltonian updates for an SCF iteration. virtual void iter_init(UnitCell& ucell, const int istep, const int iter) override; diff --git a/source/source_esolver/esolver_ks_lcao_tddft.cpp b/source/source_esolver/esolver_ks_lcao_tddft.cpp index ff2b1e06e40..5ba39719c03 100644 --- a/source/source_esolver/esolver_ks_lcao_tddft.cpp +++ b/source/source_esolver/esolver_ks_lcao_tddft.cpp @@ -144,6 +144,9 @@ void ESolver_KS_LCAO_TDDFT::runner(BaseCell& basecell, const int ist ModuleBase::TITLE("ESolver_KS_LCAO_TDDFT", "runner"); ModuleBase::timer::start(this->classname, "runner"); + // Record the ionic step for provenance headers written in after_all_runners. + this->istep_ = istep; + //---------------------------------------------------------------- // 1) before_scf (preparation before the electronic-step loop) //---------------------------------------------------------------- diff --git a/source/source_esolver/esolver_ks_lcaopw.cpp b/source/source_esolver/esolver_ks_lcaopw.cpp index 5c6ba6e9549..f5ece6c3be7 100644 --- a/source/source_esolver/esolver_ks_lcaopw.cpp +++ b/source/source_esolver/esolver_ks_lcaopw.cpp @@ -299,7 +299,7 @@ namespace ModuleESolver this->kv, this->pelec->wg, PARAM.globalv.gamma_only_local, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_mat_dir, this->inp_->out_ndigits, this->inp_->ks_solver, cal_exx, diff --git a/source/source_estate/write_elecstat_pot.cpp b/source/source_estate/write_elecstat_pot.cpp index f530c3d3d0c..dc0d968359f 100644 --- a/source/source_estate/write_elecstat_pot.cpp +++ b/source/source_estate/write_elecstat_pot.cpp @@ -25,6 +25,7 @@ void write_elecstat_pot( const std::vector* pcc_potential, const std::vector* solvent_potential, const int precision, + const bool append, std::ofstream& ofs_running) { ModuleBase::TITLE("ModuleIO", "write_elecstat_pot"); @@ -122,6 +123,7 @@ void write_elecstat_pot( out_fermi, PARAM.globalv.two_fermi, false, + append, ofs_running, "electrostatic potential"); diff --git a/source/source_estate/write_elecstat_pot.h b/source/source_estate/write_elecstat_pot.h index 15c7dc28662..8bb53d243b2 100644 --- a/source/source_estate/write_elecstat_pot.h +++ b/source/source_estate/write_elecstat_pot.h @@ -21,7 +21,8 @@ namespace ModuleIO /// @param ucell_ /// @param v_eff_fixed /// @param solvent: for solvation model -/// #param precision: output precision +/// @param precision: output precision +/// @param append: append a frame to an existing cube file (out_app_flag == 1) void write_elecstat_pot( #ifdef __MPI const int& bz, @@ -37,6 +38,7 @@ void write_elecstat_pot( const std::vector* pcc_potential, const std::vector* solvent_potential, const int precision, + const bool append, std::ofstream& ofs_running); } // namespace ModuleIO diff --git a/source/source_estate/write_init.cpp b/source/source_estate/write_init.cpp index 6b083c1986a..a6defbe0f72 100644 --- a/source/source_estate/write_init.cpp +++ b/source/source_estate/write_init.cpp @@ -6,6 +6,7 @@ // - Frequency is out_freq_td for RT-TDDFT and out_freq_ion otherwise. // - Frequency = 0: chg_ini.cube/pot_ini.cube, chgs1_ini.cube/chgs2_ini.cube // - Frequency > 0: chgg{#}_ini.cube/potg{#}_ini.cube, chgs{#}g{#}_ini.cube +// (out_app_flag == 1 keeps the fixed names without the g{#} index) // - File indices start from 1 (istep + 1); RT-TDDFT uses electronic steps. // // Usage: @@ -82,7 +83,11 @@ void ModuleIO::write_chg_init(const UnitCell& ucell, if (should_output) { - const bool include_geom_step = (output_frequency > 0); + // In append mode (out_app_flag == 1) each written step reuses the + // same fixed filename and contributes one complete cube frame, so + // the geometry-step index is omitted and frames accumulate. + const bool include_geom_step = (output_frequency > 0) && (inp.out_app_flag != 1); + const bool append = (inp.out_app_flag == 1) && (istep > 0); for (int is = 0; is < nspin; is++) { @@ -119,6 +124,7 @@ void ModuleIO::write_chg_init(const UnitCell& ucell, 1, two_fermi, false, + append, ofs_running, desc_chg); } @@ -151,7 +157,11 @@ void ModuleIO::write_pot_init(const UnitCell& ucell, if (should_output) { - const bool include_geom_step = (output_frequency > 0); + // In append mode (out_app_flag == 1) each written step reuses the + // same fixed filename and contributes one complete cube frame, so + // the geometry-step index is omitted and frames accumulate. + const bool include_geom_step = (output_frequency > 0) && (inp.out_app_flag != 1); + const bool append = (inp.out_app_flag == 1) && (istep > 0); for (int is = 0; is < nspin; is++) { @@ -171,6 +181,7 @@ void ModuleIO::write_pot_init(const UnitCell& ucell, 0, two_fermi, false, + append, ofs_running, desc_pot); } diff --git a/source/source_io/module_chgpot/get_pchg_lcao.cpp b/source/source_io/module_chgpot/get_pchg_lcao.cpp index 3ececff4b1f..9add37b9758 100644 --- a/source/source_io/module_chgpot/get_pchg_lcao.cpp +++ b/source/source_io/module_chgpot/get_pchg_lcao.cpp @@ -80,7 +80,7 @@ void Get_pchg_lcao::begin_gamma(const UnitCell& ucell, const std::string desc_pchg = ModuleIO::make_data_desc("partial charge", "partial magnetization m", is, nspin_); - ModuleIO::write_vdata_palgrid(pgrid, rho[is].data(), is, nspin_, 0, ssc.str(), 0.0, &ucell, precision, 0, false, false, ofs_running, desc_pchg); + ModuleIO::write_vdata_palgrid(pgrid, rho[is].data(), is, nspin_, 0, ssc.str(), 0.0, &ucell, precision, 0, false, false, false, ofs_running, desc_pchg); } } } @@ -172,7 +172,7 @@ void Get_pchg_lcao::begin_k(const ModulePW::PW_Basis& rho_pw, const std::string desc_pchg = ModuleIO::make_data_desc("partial charge", "partial magnetization m", is, nspin_); - ModuleIO::write_vdata_palgrid(pgrid, rho[is].data(), is, nspin_, 0, ssc.str(), 0.0, &ucell, precision, 0, false, false, ofs_running, desc_pchg); + ModuleIO::write_vdata_palgrid(pgrid, rho[is].data(), is, nspin_, 0, ssc.str(), 0.0, &ucell, precision, 0, false, false, false, ofs_running, desc_pchg); } } } @@ -212,7 +212,7 @@ void Get_pchg_lcao::begin_k(const ModulePW::PW_Basis& rho_pw, const std::string desc_pchg = ModuleIO::make_data_desc("partial charge", "partial magnetization m", is, nspin_); - ModuleIO::write_vdata_palgrid(pgrid, rho[is].data(), is, nspin_, 0, ssc.str(), 0.0, &ucell, precision, 0, false, false, ofs_running, desc_pchg); + ModuleIO::write_vdata_palgrid(pgrid, rho[is].data(), is, nspin_, 0, ssc.str(), 0.0, &ucell, precision, 0, false, false, false, ofs_running, desc_pchg); } } } diff --git a/source/source_io/module_chgpot/get_pchg_pw.cpp b/source/source_io/module_chgpot/get_pchg_pw.cpp index 10561069885..554ef4caf8e 100644 --- a/source/source_io/module_chgpot/get_pchg_pw.cpp +++ b/source/source_io/module_chgpot/get_pchg_pw.cpp @@ -474,7 +474,7 @@ void Get_pchg_pw::write_cube(const int band, } filename << ".cube"; const std::string desc_pchg = ModuleIO::make_data_desc("partial charge", "partial magnetization m", component, nspin_); - ModuleIO::write_vdata_palgrid(pgrid, values.data(), component, nspin_, 0, filename.str(), 0.0, &ucell, 11, 0, false, separate_k, ofs_running, desc_pchg); + ModuleIO::write_vdata_palgrid(pgrid, values.data(), component, nspin_, 0, filename.str(), 0.0, &ucell, 11, 0, false, separate_k, false, ofs_running, desc_pchg); } // Explicit instantiation emits both precisions for each supported device from this .cpp file. diff --git a/source/source_io/module_ctrl/ctrl_output_fp.cpp b/source/source_io/module_ctrl/ctrl_output_fp.cpp index 73e0d679e5b..e8cf6db0b06 100644 --- a/source/source_io/module_ctrl/ctrl_output_fp.cpp +++ b/source/source_io/module_ctrl/ctrl_output_fp.cpp @@ -32,10 +32,14 @@ void ctrl_output_fp(UnitCell& ucell, ModuleBase::TITLE("ModuleIO", "ctrl_output_fp"); ModuleBase::timer::start("ModuleIO", "ctrl_output_fp"); - const bool out_app_flag = inp.out_app_flag; + const int out_app_flag = inp.out_app_flag; const bool gamma_only = PARAM.globalv.gamma_only_local; const int nspin = inp.nspin; const std::string global_out_dir = PARAM.globalv.global_out_dir; + const std::string global_chg_dir = PARAM.globalv.global_chg_dir; + const std::string global_pot_dir = PARAM.globalv.global_pot_dir; + const std::string global_elf_dir = PARAM.globalv.global_elf_dir; + const std::string global_dip_dir = PARAM.globalv.global_dip_dir; const int output_frequency = inp.esolver_type == "tddft" ? inp.out_freq_td : inp.out_freq_ion; // Print the global electronic step for TDDFT and the ionic step otherwise. @@ -52,16 +56,30 @@ void ctrl_output_fp(UnitCell& ucell, bool should_output = (output_frequency == 0); if (istep_in >= 0) { - geom_block = "g" + std::to_string(istep + 1); + // In append mode (out_app_flag == 1) each cube output reuses a fixed + // filename, so every written step appends one complete frame to that + // file. Modes 0 and 2 keep a per-step g{index} suffix, which makes + // every file hold exactly one frame. + if (out_app_flag != 1) + { + geom_block = "g" + std::to_string(istep + 1); + } should_output = true; } + // Mirror the out_wfc_lcao convention: in append mode the first ionic step + // (istep == 0, always written because every out_freq_ion divides it) + // truncates the fixed-name cube files and later steps append one complete + // frame each. Single-point SCF/NSCF runs stop at istep == 0, so they keep + // overwriting instead of stacking on a stale file from an earlier run. + const bool append_cube = (out_app_flag == 1) && (istep > 0); + // 4) write charge density if (inp.out_chg[0] > 0 && should_output) { for (int is = 0; is < nspin; ++is) { - std::string fn = PARAM.globalv.global_out_dir + "chg"; + std::string fn = global_chg_dir + "chg"; std::string spin_block; if (nspin == 2 || nspin == 4) @@ -89,12 +107,13 @@ void ctrl_output_fp(UnitCell& ucell, 1, PARAM.globalv.two_fermi, false, + append_cube, ofs_running, desc_chg); if (XC_Functional::get_ked_flag()) { - fn = PARAM.globalv.global_out_dir + "tau"; + fn = global_chg_dir + "tau"; fn += spin_block + geom_block + ".cube"; @@ -112,6 +131,7 @@ void ctrl_output_fp(UnitCell& ucell, 1, // default out_fermi PARAM.globalv.two_fermi, false, + append_cube, ofs_running, desc_tau); } @@ -123,7 +143,7 @@ void ctrl_output_fp(UnitCell& ucell, { for (int is = 0; is < nspin; is++) { - std::string fn = PARAM.globalv.global_out_dir + "pot"; + std::string fn = global_pot_dir + "pot"; std::string spin_block; if (nspin == 2 || nspin == 4) @@ -151,13 +171,14 @@ void ctrl_output_fp(UnitCell& ucell, 0, // out_fermi PARAM.globalv.two_fermi, false, + append_cube, ofs_running, desc_pot); } } else if (inp.out_pot[0] == 2 && should_output) { - std::string fn = PARAM.globalv.global_out_dir + "potes"; + std::string fn = global_pot_dir + "potes"; fn += geom_block + ".cube"; const std::vector* pcc_potential = pelec->pot->pcc_electron_potential(); @@ -177,6 +198,7 @@ void ctrl_output_fp(UnitCell& ucell, pcc_potential, solvent_potential, inp.out_pot[1], + append_cube, ofs_running); } @@ -191,7 +213,7 @@ void ctrl_output_fp(UnitCell& ucell, module_charge::cal_rhog_symm(is, chr, pw_rhod, ucell.symm); } - std::string out_dir = PARAM.globalv.global_out_dir; + std::string out_dir = global_elf_dir; ModuleIO::write_elf(out_dir, istep, nspin, @@ -203,6 +225,7 @@ void ctrl_output_fp(UnitCell& ucell, inp.out_elf[1], geom_block, PARAM.globalv.two_fermi, + append_cube, ofs_running); } @@ -228,7 +251,7 @@ void ctrl_output_fp(UnitCell& ucell, for (int is = 0; is < nspin; ++is) { std::stringstream ss_dipole; - ss_dipole << global_out_dir << "dipole_s" << is + 1 << ".txt"; + ss_dipole << global_dip_dir << "dipole_s" << is + 1 << ".txt"; ModuleIO::write_dipole(ucell, chr.rho[is], pw_rhod, istep, ss_dipole.str(), ofs_running); } } diff --git a/source/source_io/module_ctrl/ctrl_output_pw.cpp b/source/source_io/module_ctrl/ctrl_output_pw.cpp index 8294d7cbae6..7b8baff6f86 100644 --- a/source/source_io/module_ctrl/ctrl_output_pw.cpp +++ b/source/source_io/module_ctrl/ctrl_output_pw.cpp @@ -73,8 +73,9 @@ void ModuleIO::ctrl_iter_pw(const int istep, GlobalV::RANK_IN_POOL, GlobalV::NPROC_IN_POOL, inp.out_wfc_pw, + inp.out_app_flag, inp.ecutwfc, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_psi_dir, stp.psi_cpu[0], kv, pw_wfc, @@ -174,7 +175,7 @@ void ModuleIO::ctrl_scf_pw(const int istep, PARAM.globalv.two_fermi, inp.out_app_flag, inp.bndpar, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_dos_dir, ofs_running); } } @@ -188,14 +189,14 @@ void ModuleIO::ctrl_scf_pw(const int istep, // Use the solver's native wavefunction precision for FFT and projector overlaps. ModuleIO::Get_pchg_pw output(*stp.template get_psi_t(), *pw_wfc, *pw_rho, *pw_rhod, ppcell, inp.nspin, inp.nbands); - output.begin(&ucell, para_grid, kv, inp.out_pchg, PARAM.globalv.global_out_dir, inp.if_separate_k, inp.noncolin, ofs_running); + output.begin(&ucell, para_grid, kv, inp.out_pchg, PARAM.globalv.global_chg_dir, inp.if_separate_k, inp.noncolin, ofs_running); } if ((inp.out_wfc_norm.size() > 0 || inp.out_wfc_re_im.size() > 0) && output_td_state) { // Use the solver's native wavefunction precision for FFT. ModuleIO::Get_wf_pw output(*stp.template get_psi_t(), *pw_wfc, *pw_rho, *pw_rhod, inp.nspin, inp.nbands); - output.begin(ucell, para_grid, kv, inp.out_wfc_norm, inp.out_wfc_re_im, PARAM.globalv.global_out_dir, ofs_running); + output.begin(ucell, para_grid, kv, inp.out_wfc_norm, inp.out_wfc_re_im, PARAM.globalv.global_psi_dir, ofs_running); } //------------------------------------------------------------------ @@ -294,7 +295,7 @@ void ModuleIO::ctrl_runner_pw(UnitCell& ucell, inp.out_ldos, inp.stm_bias, inp.nspin, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_dos_dir, PARAM.globalv.two_fermi, inp.nbands, inp.dos_edelta_ev, diff --git a/source/source_io/module_ctrl/ctrl_runner_lcao.cpp b/source/source_io/module_ctrl/ctrl_runner_lcao.cpp index 5ceec5f9165..aac9aa1f898 100644 --- a/source/source_io/module_ctrl/ctrl_runner_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_runner_lcao.cpp @@ -20,6 +20,7 @@ namespace ModuleIO template void ctrl_runner_lcao(UnitCell& ucell, // unitcell const Input_para &inp, // input + const int istep, // ionic step index, zero-based K_Vectors &kv, // k-point elecstate::ElecState* pelec,// electronic info const module_dm::Setup_DM &dmat, // mohan add 2025-11-02 @@ -54,7 +55,7 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell { ModuleIO::Cal_ldos::cal_ldos_lcao(pelec->eferm, chr, dmat, kv, pelec->ekb, pelec->wg, psi[0], pgrid, pv, gd, ucell, - inp.stm_bias, inp.nspin, PARAM.globalv.global_out_dir, + inp.stm_bias, inp.nspin, PARAM.globalv.global_dos_dir, PARAM.globalv.two_fermi, inp.out_ldos[1], ofs_running); } @@ -84,7 +85,7 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell gd, inp.dft_plus_u, PARAM.globalv.gamma_only_local, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_mat_dir, inp.out_ndigits, inp.ks_solver, cal_exx, @@ -119,7 +120,7 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell kv, orb.cutoffs(), gd, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_mat_dir, cal_exx, hybrid_alpha, real_number @@ -138,7 +139,11 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell // write eband terms if (inp.out_eband_terms) { + const double etot = pelec->f_en.etot; ModuleIO::write_eband_terms(inp.nspin, + istep, + etot, + GlobalV::MY_RANK, PARAM.globalv.nlocal, GlobalV::DRANK, &pv, @@ -173,6 +178,7 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell // TK: double TR: double template void ctrl_runner_lcao(UnitCell& ucell, // unitcell const Input_para &inp, // input + const int istep, // ionic step index, zero-based K_Vectors &kv, // k-point elecstate::ElecState* pelec,// electronic info const module_dm::Setup_DM &dmat, // mohan add 2025-11-02 @@ -196,6 +202,7 @@ template void ctrl_runner_lcao(UnitCell& ucell, // unitcell // TK: complex TR: double template void ctrl_runner_lcao, double>(UnitCell& ucell, // unitcell const Input_para &inp, // input + const int istep, // ionic step index, zero-based K_Vectors &kv, // k-point elecstate::ElecState* pelec,// electronic info const module_dm::Setup_DM> &dmat, // mohan add 2025-11-02 @@ -219,6 +226,7 @@ template void ctrl_runner_lcao, double>(UnitCell& ucell, // TK: complex TR: complex template void ctrl_runner_lcao, std::complex>(UnitCell& ucell, // unitcell const Input_para &inp, // input + const int istep, // ionic step index, zero-based K_Vectors &kv, // k-point elecstate::ElecState* pelec,// electronic info const module_dm::Setup_DM> &dmat, // mohan add 2025-11-02 diff --git a/source/source_io/module_ctrl/ctrl_runner_lcao.h b/source/source_io/module_ctrl/ctrl_runner_lcao.h index 96434852343..a9f36154f48 100644 --- a/source/source_io/module_ctrl/ctrl_runner_lcao.h +++ b/source/source_io/module_ctrl/ctrl_runner_lcao.h @@ -18,6 +18,7 @@ namespace ModuleIO template void ctrl_runner_lcao(UnitCell& ucell, // unitcell const Input_para &inp, // input + const int istep, // ionic step index, zero-based K_Vectors &kv, // k-point elecstate::ElecState* pelec,// electronic info const module_dm::Setup_DM &dmat, // mohan add 2025-11-02 diff --git a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp index 6c21487e0a4..0ee884ec369 100644 --- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp +++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp @@ -136,10 +136,11 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, //***** - const bool out_app_flag = inp.out_app_flag; + const int out_app_flag = inp.out_app_flag; const bool gamma_only = PARAM.globalv.gamma_only_local; const int nspin = inp.nspin; const std::string global_out_dir = PARAM.globalv.global_out_dir; + const std::string global_mat_dir = PARAM.globalv.global_mat_dir; //------------------------------------------------------------------ //! 1) print out density of states (DOS) @@ -168,7 +169,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, inp.dos_emin_ev, PARAM.globalv.two_fermi, inp.bndpar, - PARAM.globalv.global_out_dir, + PARAM.globalv.global_dos_dir, ofs_running); } @@ -178,9 +179,10 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, if (inp.out_dmr[0]) { const int precision = inp.out_dmr[1]; + const bool append_dmr = (out_app_flag == 1); - ModuleIO::write_dmr(dm->get_dmr_vec(), &ucell, precision, pv, out_app_flag, - ucell.get_iat2iwt(), ucell.nat, istep, ofs_running); + ModuleIO::write_dmr(dm->get_dmr_vec(), &ucell, precision, pv, append_dmr, + ucell.get_iat2iwt(), ucell.nat, global_mat_dir, istep, ofs_running); } //------------------------------------------------------------------ @@ -195,7 +197,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, } const int precision = inp.out_dmk[1]; - ModuleIO::write_dmk(dm->get_dmk_vec(), kv, precision, efermis, &(ucell), pv, global_out_dir, istep, ofs_running); + ModuleIO::write_dmk(dm->get_dmk_vec(), kv, precision, efermis, &(ucell), pv, global_mat_dir, istep, out_app_flag, ofs_running); } //------------------------------------------------------------------ @@ -205,7 +207,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, if (hsk_out_type == 1 || hsk_out_type == 2) { const int precision = inp.out_hsk[1]; - ModuleIO::write_hsk(global_out_dir, + ModuleIO::write_hsk(global_mat_dir, nspin, kv.get_nks(), kv.get_nkstot(), @@ -279,8 +281,8 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, const hamilt::HContainer* sr = p_hamilt->getSR(); ModuleIO::write_hsr(hr_vec, sr, &ucell, inp.out_hsr[0], precision, pv, - out_app_flag, gamma_only, ucell.get_iat2iwt(), ucell.nat, istep, - PARAM.globalv.global_out_dir, pelec->eferm, + (out_app_flag == 1), gamma_only, ucell.get_iat2iwt(), ucell.nat, istep, + global_mat_dir, pelec->eferm, ofs_running); } @@ -290,7 +292,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, const bool output_hsr_npz = inp.out_hsr[0] == 3 || inp.out_hsr_npz_compat; if (output_hsr_npz) { - std::string zipname = PARAM.globalv.global_out_dir + "sr_nao.npz"; + std::string zipname = global_mat_dir + "sr_nao.npz"; ModuleIO::output_mat_npz(ucell, zipname, *(p_hamilt->getSR())); } @@ -300,7 +302,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, for (int ispin = 0; ispin < hr_vec.size(); ++ispin) { std::string zipname - = PARAM.globalv.global_out_dir + "hrs" + std::to_string(ispin + 1) + "_nao.npz"; + = global_mat_dir + "hrs" + std::to_string(ispin + 1) + "_nao.npz"; ModuleIO::output_mat_npz(ucell, zipname, *(hr_vec[ispin])); } } @@ -311,7 +313,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, for (int ispin = 0; ispin < dmr_vec.size(); ++ispin) { std::string zipname - = PARAM.globalv.global_out_dir + "output_DM" + std::to_string(ispin) + ".npz"; + = global_mat_dir + "output_DM" + std::to_string(ispin) + ".npz"; ModuleIO::output_mat_npz(ucell, zipname, *(dmr_vec[ispin])); } } @@ -399,7 +401,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, dh_params.nspin = inp.nspin; dh_params.istep = istep; dh_params.gamma_only = gamma_only; - dh_params.append = out_app_flag; + dh_params.append = (out_app_flag == 1); dh_params.ofs_running = &ofs_running; if (PARAM.inp.nspin == 1 || PARAM.inp.nspin == 2) { @@ -442,7 +444,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, h_params.nrxx = pw_rho->nrxx; h_params.nspin = nspin; h_params.istep = istep; - h_params.append = out_app_flag; + h_params.append = (out_app_flag == 1); h_params.iat2iwt = ucell.get_iat2iwt(); h_params.nat = ucell.nat; h_params.nlocal = PARAM.globalv.nlocal; @@ -453,7 +455,9 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, h_params.gga_grad = PARAM.inp.gga_grad; h_params.out_app_flag = out_app_flag; h_params.calculation = inp.calculation; - h_params.global_out_dir = global_out_dir; + // H(k) component matrices follow the selected matrix directory so + // they land in MAT/ together with the other matrix outputs in mode 2 + h_params.global_out_dir = global_mat_dir; h_params.global_matrix_dir = PARAM.globalv.global_matrix_dir; h_params.ks_solver = PARAM.inp.ks_solver; h_params.drank = GlobalV::DRANK; @@ -509,7 +513,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, const int out_label = 1; // 1: .txt, 2: .dat - std::string t_fn = ModuleIO::filename_output(global_out_dir, + std::string t_fn = ModuleIO::filename_output(global_mat_dir, "tk", "nao", ik, @@ -527,7 +531,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, false, // bit inp.out_mat_tk[1], 1, // true for upper triangle matrix - inp.out_app_flag, + (inp.out_app_flag == 1), t_fn, pv, GlobalV::DRANK, @@ -558,7 +562,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell, PARAM.globalv.gamma_only_local, &ofs_running, GlobalV::MY_RANK); - mylcalculator.calculate(inp.suffix, global_out_dir, ucell, inp.out_mat_l[1], GlobalV::MY_RANK, istep_in); + mylcalculator.calculate(inp.suffix, global_mat_dir, ucell, inp.out_mat_l[1], GlobalV::MY_RANK, istep_in, out_app_flag); } //------------------------------------------------------------------ diff --git a/source/source_io/module_dhs/write_dh.cpp b/source/source_io/module_dhs/write_dh.cpp index 9a61cf3dee9..4f82a30c9de 100644 --- a/source/source_io/module_dhs/write_dh.cpp +++ b/source/source_io/module_dhs/write_dh.cpp @@ -43,10 +43,12 @@ void write_dh_perI(WriteDHParams& params, const int nspin_k = (nspin == 2 ? 2 : 1); const int nks = params.kv->get_nks() / nspin_k; const int nlocal = PARAM.globalv.nlocal; - const std::string global_out_dir = PARAM.globalv.global_out_dir; - const bool out_app_flag = PARAM.inp.out_app_flag; - const std::string r_dir - = (PARAM.inp.calculation == "md" && !out_app_flag) ? PARAM.globalv.global_matrix_dir : global_out_dir; + // All dH component outputs (CSR dH/dR, dense dH/dk) belong to the unified + // matrix directory: global_mat_dir is set to OUT.root in modes 0/1 and to + // OUT.root/MAT/ in mode 2 by ReadInput::set_global_dir, so no caller-side + // calculation or out_app_flag branching is needed here. + const std::string mat_dir = PARAM.globalv.global_mat_dir; + const int out_app_flag = PARAM.inp.out_app_flag; #ifdef __MPI Parallel_Orbitals serialV; @@ -79,7 +81,7 @@ void write_dh_perI(WriteDHParams& params, if (GlobalV::MY_RANK == 0) #endif { - std::string fr = r_dir + ModuleIO::dhr_gen_fname(rprefix + tag, ispin, params.append, params.istep); + std::string fr = mat_dir + ModuleIO::dhr_gen_fname(rprefix + tag, ispin, params.append, params.istep); // dH/dR is not a Hamiltonian: no Fermi energy annotation in the header const double no_efermi = 0.0; #ifdef __MPI @@ -117,19 +119,20 @@ void write_dh_perI(WriteDHParams& params, #else hamilt::folding_HR(*hR, hk.data(), params.kv->kvec_d[ik], nlocal, 0); #endif - std::string fk = global_out_dir + kprefix + tag; + std::string fk = mat_dir + kprefix + tag; if (nks > 1) { fk += "_ik" + std::to_string(params.kv->ik2iktot[ik]); } fk += "_nao.txt"; + const bool append_fk = (out_app_flag == 1); ModuleIO::save_mat(params.istep, hk.data(), nlocal, false, 8, false, - out_app_flag, + append_fk, fk, pv, GlobalV::DRANK, diff --git a/source/source_io/module_dipole/write_dipole.cpp b/source/source_io/module_dipole/write_dipole.cpp index 74020c0492e..35cd8667fbf 100644 --- a/source/source_io/module_dipole/write_dipole.cpp +++ b/source/source_io/module_dipole/write_dipole.cpp @@ -67,10 +67,13 @@ void write_dipole(const UnitCell& ucell, ofs_running << " Write dipole data to file: " << fn << std::endl; - // Validate grid dimensions to prevent division by zero - if (rhopw->nx == 0 || rhopw->ny == 0 || rhopw->nz == 0 || rhopw->nxyz == 0 || rhopw->nplane == 0) + // Validate global grid dimensions to prevent division by zero. + // Do NOT reject nplane == 0: some MPI ranks legitimately hold zero + // z-planes (empty partition), in which case nrxx is also 0 and the + // accumulation loop below is a harmless no-op. + if (rhopw->nx == 0 || rhopw->ny == 0 || rhopw->nz == 0 || rhopw->nxyz == 0) { - ModuleBase::WARNING_QUIT("ModuleIO::write_dipole", "Invalid grid parameters: nx, ny, nz, nxyz, or nplane is zero"); + ModuleBase::WARNING_QUIT("ModuleIO::write_dipole", "Invalid grid parameters: nx, ny, nz, or nxyz is zero"); } // Accumulate the electronic first moment in fractional coordinates. @@ -117,9 +120,12 @@ void write_dipole(const UnitCell& ucell, // Output electronic dipole moment print_dipole_moment(ofs_running, "Electronic dipole moment", dipole_elec[0], dipole_elec[1], dipole_elec[2]); - // Write to file: step index and three dipole components - ofs << std::setprecision(precision) << istep + 1 << " " << dipole_elec[0] << " " << dipole_elec[1] << " " << dipole_elec[2] - << std::endl; + // Write to file on master process only; ofs is opened only there. + if (GlobalV::MY_RANK == 0) + { + ofs << std::setprecision(precision) << istep + 1 << " " << dipole_elec[0] << " " << dipole_elec[1] << " " + << dipole_elec[2] << std::endl; + } // Calculate ionic dipole moment // Accumulate Z_v * taud, then transform to Cartesian coordinates. diff --git a/source/source_io/module_dm/test/write_dmk_test.cpp b/source/source_io/module_dm/test/write_dmk_test.cpp index 4d6a2a8f1bc..97a440add57 100644 --- a/source/source_io/module_dm/test/write_dmk_test.cpp +++ b/source/source_io/module_dm/test/write_dmk_test.cpp @@ -165,8 +165,8 @@ TEST(DMKTest,WriteDMK) { kv.kd_done = true; std::ofstream ofs_log; - ModuleIO::write_dmk(dmk, kv, 3, efs, ucell, pv, out_dir, istep, ofs_log); - ModuleIO::write_dmk(dmk_multik, kv, 3, efs, ucell, pv, out_dir, istep, ofs_log); + ModuleIO::write_dmk(dmk, kv, 3, efs, ucell, pv, out_dir, istep, 0, ofs_log); + ModuleIO::write_dmk(dmk_multik, kv, 3, efs, ucell, pv, out_dir, istep, 0, ofs_log); std::ifstream ifs; @@ -195,6 +195,8 @@ TEST(DMKTest,WriteDMK) { std::istreambuf_iterator()); EXPECT_THAT(str, testing::HasSubstr("0 # Fermi energy in Ry")); EXPECT_THAT(str, testing::HasSubstr("20 # number of localized basis")); + // the matrix body must end with a newline so appended records are not glued + EXPECT_EQ(str.back(), '\n'); ifs.close(); fn = "dmk2s1_nao.txt"; @@ -233,6 +235,180 @@ TEST(DMKTest,WriteDMK) { }; +// --------------------------------------------------------------------------- +// out_app_flag = 0: per-step files with g suffix, overwrite each call. +// --------------------------------------------------------------------------- +TEST(DMKTest, Mode0PerStepFiles) { + UnitCell* ucell; + UcellTestPrepare utp = UcellTestLib["Si"]; + ucell = utp.SetUcellInfo(); + + int nspin = 2; + int nk = 1; + int nlocal = 20; + std::vector> dmk; + std::vector efs; + Parallel_2D pv; + init_pv(nlocal, pv); + gen_dmk(dmk, efs, nspin, nk, nlocal, pv); + + K_Vectors kv; + kv.set_nkstot(1); + kv.set_nkstot_nospin(1); + kv.set_nks(1); + kv.set_spin_mult(2); + kv.kvec_c.resize(1); + kv.kvec_c[0].x = 0.0; + kv.kvec_c[0].y = 0.0; + kv.kvec_c[0].z = 0.0; + kv.kvec_d.resize(1); + kv.kvec_d[0].x = 0.0; + kv.kvec_d[0].y = 0.0; + kv.kvec_d[0].z = 0.0; + kv.wk.resize(1); + kv.wk[0] = 1.0; + kv.isk.resize(1); + kv.isk[0] = 0; + + const std::string out_dir = "./"; + const int out_app_flag = 0; + std::ofstream ofs_log; + + // Two different isteps -> two differently-named files. + ModuleIO::write_dmk(dmk, kv, /*precision=*/3, efs, ucell, pv, out_dir, /*istep=*/0, out_app_flag, ofs_log); + ModuleIO::write_dmk(dmk, kv, /*precision=*/3, efs, ucell, pv, out_dir, /*istep=*/1, out_app_flag, ofs_log); + + if (GlobalV::MY_RANK == 0) + { + // First call: g1 suffix (istep 0 -> istep+1). + std::ifstream ifs("dms1g1_nao.txt"); + EXPECT_TRUE(ifs.good()); + ifs.close(); + // Second call: g2 suffix. + ifs.open("dms1g2_nao.txt"); + EXPECT_TRUE(ifs.good()); + ifs.close(); + // Fixed-name file must NOT exist. + ifs.open("dms1_nao.txt"); + EXPECT_FALSE(ifs.good()); + remove("dms1g1_nao.txt"); + remove("dms1g2_nao.txt"); + } + delete ucell; +} + +// --------------------------------------------------------------------------- +// out_app_flag = 1: fixed filename, multiple calls append successive records. +// --------------------------------------------------------------------------- +TEST(DMKTest, Mode1FixedNameAppend) { + UnitCell* ucell; + UcellTestPrepare utp = UcellTestLib["Si"]; + ucell = utp.SetUcellInfo(); + + int nspin = 2; + int nk = 1; + int nlocal = 20; + std::vector> dmk; + std::vector efs; + Parallel_2D pv; + init_pv(nlocal, pv); + gen_dmk(dmk, efs, nspin, nk, nlocal, pv); + + K_Vectors kv; + kv.set_nkstot(1); + kv.set_nkstot_nospin(1); + kv.set_nks(1); + kv.set_spin_mult(2); + kv.kvec_c.resize(1); + kv.kvec_c[0].x = 0.0; + kv.kvec_c[0].y = 0.0; + kv.kvec_c[0].z = 0.0; + kv.kvec_d.resize(1); + kv.kvec_d[0].x = 0.0; + kv.kvec_d[0].y = 0.0; + kv.kvec_d[0].z = 0.0; + kv.wk.resize(1); + kv.wk[0] = 1.0; + kv.isk.resize(1); + kv.isk[0] = 0; + + const std::string out_dir = "./"; + const int out_app_flag = 1; + std::ofstream ofs_log; + + // Two ionic steps -> both write to dms1_nao.txt. + ModuleIO::write_dmk(dmk, kv, /*precision=*/3, efs, ucell, pv, out_dir, /*istep=*/0, out_app_flag, ofs_log); + ModuleIO::write_dmk(dmk, kv, /*precision=*/3, efs, ucell, pv, out_dir, /*istep=*/1, out_app_flag, ofs_log); + + if (GlobalV::MY_RANK == 0) + { + std::ifstream ifs("dms1_nao.txt"); + std::string str((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + // Two "Ionic Step" headers present -> two appended records. + EXPECT_THAT(str, testing::HasSubstr(" --- Ionic Step 1 ---")); + EXPECT_THAT(str, testing::HasSubstr(" --- Ionic Step 2 ---")); + // g-suffixed file must NOT exist. + ifs.open("dms1g1_nao.txt"); + EXPECT_FALSE(ifs.good()); + remove("dms1_nao.txt"); + } + delete ucell; +} + +// --------------------------------------------------------------------------- +// out_app_flag = 2: same naming as mode 0 (g suffix kept); directory routing +// is handled by set_global_dir and is covered by io_sys_var_test. +// --------------------------------------------------------------------------- +TEST(DMKTest, Mode2SameNamingAsMode0) { + UnitCell* ucell; + UcellTestPrepare utp = UcellTestLib["Si"]; + ucell = utp.SetUcellInfo(); + + int nspin = 2; + int nk = 1; + int nlocal = 20; + std::vector> dmk; + std::vector efs; + Parallel_2D pv; + init_pv(nlocal, pv); + gen_dmk(dmk, efs, nspin, nk, nlocal, pv); + + K_Vectors kv; + kv.set_nkstot(1); + kv.set_nkstot_nospin(1); + kv.set_nks(1); + kv.set_spin_mult(2); + kv.kvec_c.resize(1); + kv.kvec_c[0].x = 0.0; + kv.kvec_c[0].y = 0.0; + kv.kvec_c[0].z = 0.0; + kv.kvec_d.resize(1); + kv.kvec_d[0].x = 0.0; + kv.kvec_d[0].y = 0.0; + kv.kvec_d[0].z = 0.0; + kv.wk.resize(1); + kv.wk[0] = 1.0; + kv.isk.resize(1); + kv.isk[0] = 0; + + const std::string out_dir = "./"; + const int out_app_flag = 2; + std::ofstream ofs_log; + + ModuleIO::write_dmk(dmk, kv, /*precision=*/3, efs, ucell, pv, out_dir, /*istep=*/2, out_app_flag, ofs_log); + + if (GlobalV::MY_RANK == 0) + { + std::ifstream ifs("dms1g3_nao.txt"); + EXPECT_TRUE(ifs.good()); + ifs.close(); + remove("dms1g3_nao.txt"); + } + delete ucell; +} + /* // no function in the main code calls read_dmk??? mohan note 2025-05-25 TEST(DMKTest, ReadDMK) { diff --git a/source/source_io/module_dm/write_dmk.cpp b/source/source_io/module_dm/write_dmk.cpp index beec822652a..9a162a8ba78 100644 --- a/source/source_io/module_dm/write_dmk.cpp +++ b/source/source_io/module_dm/write_dmk.cpp @@ -188,11 +188,21 @@ void ModuleIO::write_dmk(const std::vector>& dmk, const Parallel_2D& pv, const std::string& dmk_dir, const int istep, + const int out_app_flag, std::ofstream& ofs_running) { ModuleBase::TITLE("ModuleIO", "write_dmk"); ModuleBase::timer::start("ModuleIO", "write_dmk"); + // In append mode (out_app_flag == 1) DM(k) reuses a fixed filename, + // so each ionic step overwrites the previous snapshot; the file body + // still records the real ionic step via istep. + const int istep_file = (out_app_flag == 1) ? -1 : istep; + // Each multi-step run (MD or relax) appends a new record to the fixed + // file, mirroring the open_sparse_file convention; single-point SCF + // passes istep = -1 and starts a fresh file. + const bool append_on_restart = (out_app_flag == 1) && (istep >= 0); + int my_rank = 0; #ifdef __MPI MPI_Comm_rank(pv.comm(), &my_rank); @@ -239,9 +249,15 @@ void ModuleIO::write_dmk(const std::vector>& dmk, if (my_rank == 0) { std::string fn = dmk_dir - + dmk_gen_fname(gamma_only, ispin, nspin, ik, istep); + + dmk_gen_fname(gamma_only, ispin, nspin, ik, istep_file); - std::ofstream ofs(fn.c_str()); + std::ofstream ofs; + std::ios_base::openmode mode = std::ios::out; + if (append_on_restart) + { + mode |= std::ios::app; + } + ofs.open(fn.c_str(), mode); ofs << " --- Ionic Step " << istep+1 << " ---" << std::endl; @@ -315,6 +331,10 @@ void ModuleIO::write_dmk(const std::vector>& dmk, } } } + // Terminate the matrix body with a newline so that the next + // appended record (out_app_flag == 1) starts on a fresh line + // instead of being glued to the last value of this step. + ofs << std::endl; ofs.close(); } // rank0 } // ik @@ -347,6 +367,7 @@ template void ModuleIO::write_dmk(const std::vector> const Parallel_2D& pv, const std::string& dmk_dir, const int istep, + const int out_app_flag, std::ofstream& ofs_running); template void ModuleIO::write_dmk>(const std::vector>>& dmk, @@ -357,5 +378,6 @@ template void ModuleIO::write_dmk>(const std::vector void write_dmk(const std::vector>& dmk, @@ -78,6 +81,7 @@ void write_dmk(const std::vector>& dmk, const Parallel_2D& pv, const std::string& dmk_dir, const int istep, + const int out_app_flag, std::ofstream& ofs_running); } // namespace ModuleIO diff --git a/source/source_io/module_dm/write_dmr.cpp b/source/source_io/module_dm/write_dmr.cpp index 6b95e726a31..58bdd2ec16a 100644 --- a/source/source_io/module_dm/write_dmr.cpp +++ b/source/source_io/module_dm/write_dmr.cpp @@ -1,7 +1,7 @@ #include "write_dmr.h" #include "source_io/module_output/spin_tag.h" -#include "source_io/module_parameter/parameter.h" +#include "source_base/global_variable.h" #include "source_hamilt/module_hcontainer/hcontainer_funcs.h" #include "source_hamilt/module_hcontainer/output_hcontainer.h" #include "source_cell/ucell_io.h" @@ -85,6 +85,7 @@ void write_dmr(const std::vector*> dmr, const bool append, const int* iat2iwt, const int nat, + const std::string& dmr_dir, const int istep, std::ofstream& ofs_running) { @@ -110,7 +111,7 @@ void write_dmr(const std::vector*> dmr, // out_type = 1, csr format; // out_type = 2, npz format (currently not support) const int out_type = 1; - std::string fname = PARAM.globalv.global_out_dir + dmr_gen_fname(out_type, ispin, append, istep); + std::string fname = dmr_dir + dmr_gen_fname(out_type, ispin, append, istep); write_dmr_csr(fname, ucell, precision, &dm_serial, istep, ispin, nspin); const std::string spin_tag = ModuleIO::make_spin_tag(ispin, nspin); ofs_running << " Write DM(R)" << spin_tag << " matrix in NAO basis to file: " << fname << std::endl; diff --git a/source/source_io/module_dm/write_dmr.h b/source/source_io/module_dm/write_dmr.h index 644e2828b52..957553242e8 100644 --- a/source/source_io/module_dm/write_dmr.h +++ b/source/source_io/module_dm/write_dmr.h @@ -54,6 +54,7 @@ void write_dmr_csr(std::string& fname, * @param ispin The spin index, starting from 0. * @param append Whether to append the data to an existing file or create a new file. The file name is related to this * flag. + * @param dmr_dir The directory the DM(R) files are written into. * @param istep The ION step, starting from 0. */ void write_dmr(const std::vector*> dmr, @@ -63,6 +64,7 @@ void write_dmr(const std::vector*> dmr, const bool append, const int* iat2iwt, const int nat, + const std::string& dmr_dir, const int istep, std::ofstream& ofs_running); } // namespace ModuleIO diff --git a/source/source_io/module_dos/cal_dos.cpp b/source/source_io/module_dos/cal_dos.cpp index 7975c5e6ded..79f5dd7d65a 100644 --- a/source/source_io/module_dos/cal_dos.cpp +++ b/source/source_io/module_dos/cal_dos.cpp @@ -108,7 +108,7 @@ bool ModuleIO::cal_dos(const int& is, // index for spin const ModuleBase::matrix& ekb, // energy for each k point and each band const ModuleBase::matrix& wg, // weight of k-points and bands const int istep, // ionic step - const bool out_app_flag, + const int out_app_flag, const int bndpar) { ModuleBase::TITLE("ModuleIO", "cal_dos"); @@ -117,7 +117,7 @@ bool ModuleIO::cal_dos(const int& is, // index for spin if (GlobalV::MY_RANK == 0) { - if(out_app_flag==true) + if(out_app_flag == 1) { ofs_dos.open(fn.c_str(), std::ios::app); } diff --git a/source/source_io/module_dos/cal_dos.h b/source/source_io/module_dos/cal_dos.h index d133c4e9423..e8f305b685b 100644 --- a/source/source_io/module_dos/cal_dos.h +++ b/source/source_io/module_dos/cal_dos.h @@ -37,7 +37,7 @@ namespace ModuleIO const ModuleBase::matrix &ekb, //store energy for each k point and each band const ModuleBase::matrix &wg, //weight of (kpoint,bands)) const int istep_in, // ionic_step - const bool out_app_flag, // whether to append output + const int out_app_flag, // 0: no append, 1: append, 2: subdirectory const int bndpar); // number of band parallel groups } diff --git a/source/source_io/module_dos/cal_ldos.cpp b/source/source_io/module_dos/cal_ldos.cpp index 63f0e2c3f5b..5e8e14de0d9 100644 --- a/source/source_io/module_dos/cal_ldos.cpp +++ b/source/source_io/module_dos/cal_ldos.cpp @@ -102,6 +102,7 @@ void Cal_ldos::cal_ldos_lcao( 0, two_fermi, false, + false, ofs_running, "local DOS"); @@ -230,7 +231,7 @@ void stm_mode_pw(const elecstate::ElecStatePW>* pelec, << ".cube"; const int precision = out_ldos_precision; - ModuleIO::write_vdata_palgrid(pgrid, ldos.data(), 0, nspin, 0, fn.str(), 0, &ucell, precision, 0, two_fermi, false, ofs_running, "local DOS"); + ModuleIO::write_vdata_palgrid(pgrid, ldos.data(), 0, nspin, 0, fn.str(), 0, &ucell, precision, 0, two_fermi, false, false, ofs_running, "local DOS"); } } diff --git a/source/source_io/module_dos/cal_pdos_gamma.cpp b/source/source_io/module_dos/cal_pdos_gamma.cpp index 3b9ee1b1dd6..d042fb6ede5 100644 --- a/source/source_io/module_dos/cal_pdos_gamma.cpp +++ b/source/source_io/module_dos/cal_pdos_gamma.cpp @@ -23,6 +23,7 @@ void ModuleIO::cal_pdos( const double& dos_edelta_ev, const double& bcoeff, const int istep, + const int out_app_flag, const int nlocal, const int nspin, const std::string& global_out_dir) @@ -148,7 +149,7 @@ void ModuleIO::cal_pdos( if (GlobalV::MY_RANK == 0) { write_pdos_text(ucell, pdos.data(), nspin, nlocal, npoints, - emin, dos_edelta_ev, global_out_dir, istep); + emin, dos_edelta_ev, global_out_dir, istep, out_app_flag); ModuleIO::write_orb_info(&ucell); } } diff --git a/source/source_io/module_dos/cal_pdos_gamma.h b/source/source_io/module_dos/cal_pdos_gamma.h index e5122464262..0af1fca0540 100644 --- a/source/source_io/module_dos/cal_pdos_gamma.h +++ b/source/source_io/module_dos/cal_pdos_gamma.h @@ -25,6 +25,7 @@ namespace ModuleIO const double& dos_edelta_ev, const double& bcoeff, const int istep, + const int out_app_flag, const int nlocal, const int nspin, const std::string& global_out_dir); diff --git a/source/source_io/module_dos/cal_pdos_multik.cpp b/source/source_io/module_dos/cal_pdos_multik.cpp index 95ee8c7ba1c..219b5bf69cb 100644 --- a/source/source_io/module_dos/cal_pdos_multik.cpp +++ b/source/source_io/module_dos/cal_pdos_multik.cpp @@ -22,6 +22,7 @@ void ModuleIO::cal_pdos( const double& dos_edelta_ev, const double& bcoeff, const int istep, + const int out_app_flag, const int nlocal, const int nspin, const std::string& global_out_dir) @@ -188,7 +189,7 @@ void ModuleIO::cal_pdos( if (GlobalV::MY_RANK == 0) { write_pdos_text(ucell, pdos.data(), nspin, nlocal, npoints, - emin, dos_edelta_ev, global_out_dir, istep); + emin, dos_edelta_ev, global_out_dir, istep, out_app_flag); ModuleIO::write_orb_info(&ucell); } } diff --git a/source/source_io/module_dos/cal_pdos_multik.h b/source/source_io/module_dos/cal_pdos_multik.h index 110009becb4..a4b194d967b 100644 --- a/source/source_io/module_dos/cal_pdos_multik.h +++ b/source/source_io/module_dos/cal_pdos_multik.h @@ -26,6 +26,7 @@ namespace ModuleIO const double& dos_edelta_ev, const double& bcoeff, const int istep, + const int out_app_flag, const int nlocal, const int nspin, const std::string& global_out_dir); diff --git a/source/source_io/module_dos/unittests/test_cal_dos.cpp b/source/source_io/module_dos/unittests/test_cal_dos.cpp index abcf942bad4..82c99219112 100644 --- a/source/source_io/module_dos/unittests/test_cal_dos.cpp +++ b/source/source_io/module_dos/unittests/test_cal_dos.cpp @@ -37,7 +37,7 @@ TEST_F(DosTest,Dos) EXPECT_EQ(dosp.is,0); const int istep = 1; - const bool out_app_flag = false; + const int out_app_flag = 0; const int bndpar = 1; ModuleIO::cal_dos(dosp.is, dosp.fa, @@ -124,7 +124,7 @@ TEST_F(DosTest,DosW1) GlobalV::ofs_warning.open("warning1.log"); const int istep = 1; - const bool out_app_flag = false; + const int out_app_flag = 0; const int bndpar = 1; EXPECT_NO_THROW(ModuleIO::cal_dos(dosp.is, dosp.fa, @@ -171,7 +171,7 @@ TEST_F(DosTest,DosW2) GlobalV::ofs_warning.open("warning2.log"); const int istep = 1; - const bool out_app_flag = false; + const int out_app_flag = 0; const int bndpar = 1; EXPECT_NO_THROW(ModuleIO::cal_dos(dosp.is, dosp.fa, @@ -207,6 +207,92 @@ TEST_F(DosTest,DosW2) #endif } +// --------------------------------------------------------------------------- +// out_app_flag = 1: cal_dos appends successive records to the same file. +// --------------------------------------------------------------------------- +TEST_F(DosTest, Mode1Appends) +{ + const std::string fname = "doss1_mode1.txt"; + DosPrepare dosp(0, fname, 0.005, 18, -6, 0.07, + dos_test_data::nks, dos_test_data::nkstot, dos_test_data::nbands); + dosp.set_isk(); + dosp.set_wk(); + dosp.set_istate_info(); + + const int out_app_flag = 1; + const int bndpar = 1; + + // First call: istep=0 -> "1 # ionic step". + ModuleIO::cal_dos(dosp.is, dosp.fa, dosp.de_ev, dosp.emax_ev, dosp.emin_ev, + dosp.bcoeff, dosp.nks, dosp.nkstot, dosp.wk, dosp.isk, + dosp.nbands, dosp.ekb, dosp.wg, + /*istep=*/0, out_app_flag, bndpar); + // Second call: istep=1 -> "2 # ionic step", appended. + ModuleIO::cal_dos(dosp.is, dosp.fa, dosp.de_ev, dosp.emax_ev, dosp.emin_ev, + dosp.bcoeff, dosp.nks, dosp.nkstot, dosp.wk, dosp.isk, + dosp.nbands, dosp.ekb, dosp.wg, + /*istep=*/1, out_app_flag, bndpar); + +#ifdef __MPI + if (GlobalV::MY_RANK == 0) +#endif + { + std::ifstream ifs(fname); + std::string str((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + // Two ionic step headers present. + EXPECT_THAT(str, testing::HasSubstr("1 # ionic step")); + EXPECT_THAT(str, testing::HasSubstr("2 # ionic step")); + remove(fname.c_str()); + } +} + +// --------------------------------------------------------------------------- +// out_app_flag = 2: same overwrite behaviour as mode 0 (file is truncated each +// call; naming is handled by the caller write_dos_* which adds g{N}). +// --------------------------------------------------------------------------- +TEST_F(DosTest, Mode2Overwrites) +{ + const std::string fname = "doss1_mode2.txt"; + DosPrepare dosp(0, fname, 0.005, 18, -6, 0.07, + dos_test_data::nks, dos_test_data::nkstot, dos_test_data::nbands); + dosp.set_isk(); + dosp.set_wk(); + dosp.set_istate_info(); + + const int out_app_flag = 2; + const int bndpar = 1; + + // First call writes istep 1, second overwrites with istep 2. + ModuleIO::cal_dos(dosp.is, dosp.fa, dosp.de_ev, dosp.emax_ev, dosp.emin_ev, + dosp.bcoeff, dosp.nks, dosp.nkstot, dosp.wk, dosp.isk, + dosp.nbands, dosp.ekb, dosp.wg, + /*istep=*/0, out_app_flag, bndpar); + ModuleIO::cal_dos(dosp.is, dosp.fa, dosp.de_ev, dosp.emax_ev, dosp.emin_ev, + dosp.bcoeff, dosp.nks, dosp.nkstot, dosp.wk, dosp.isk, + dosp.nbands, dosp.ekb, dosp.wg, + /*istep=*/1, out_app_flag, bndpar); + +#ifdef __MPI + if (GlobalV::MY_RANK == 0) +#endif + { + std::ifstream ifs(fname); + std::string str((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + // Only the last ionic step header must be present. + EXPECT_THAT(str, testing::HasSubstr("2 # ionic step")); + // A simple count check: "1 # ionic step" must appear only inside + // the DOS grid lines as a value, not as a header. Check that the + // header for step 1 is absent by verifying it is NOT found as a + // standalone header line. + EXPECT_THAT(str, testing::Not(testing::HasSubstr("\n1 # ionic step"))); + remove(fname.c_str()); + } +} + #ifdef __MPI int main(int argc, char **argv) { diff --git a/source/source_io/module_dos/unittests/test_write_dos_pw.cpp b/source/source_io/module_dos/unittests/test_write_dos_pw.cpp index 384af45ac63..b866c0b24d6 100644 --- a/source/source_io/module_dos/unittests/test_write_dos_pw.cpp +++ b/source/source_io/module_dos/unittests/test_write_dos_pw.cpp @@ -67,7 +67,7 @@ TEST_F(DosPWTest,Dos1) const bool dos_setemax = true; const bool dos_setemin = true; const bool two_fermi = false; - const bool out_app_flag = false; + const int out_app_flag = 0; const int bndpar = 1; const std::string global_out_dir = "./"; @@ -144,7 +144,7 @@ TEST_F(DosPWTest,Dos2) const int nspin = 1; const int out_dos = 1; const bool two_fermi = false; - const bool out_app_flag = false; + const int out_app_flag = 0; const int bndpar = 1; const std::string global_out_dir = "./"; const bool dos_setemax = false; diff --git a/source/source_io/module_dos/unittests/test_write_pdos_text.cpp b/source/source_io/module_dos/unittests/test_write_pdos_text.cpp index 00681a3cb5f..69a93c8b92b 100644 --- a/source/source_io/module_dos/unittests/test_write_pdos_text.cpp +++ b/source/source_io/module_dos/unittests/test_write_pdos_text.cpp @@ -93,7 +93,7 @@ TEST_F(WritePdosTextTest, BasicOutput) } ModuleIO::write_pdos_text(*ucell, &pdos, nspin, nlocal, npoints, - emin, dos_edelta_ev, out_dir, istep); + emin, dos_edelta_ev, out_dir, istep, 0); const std::string str = read_file("./pdoss1.txt"); @@ -127,7 +127,7 @@ TEST_F(WritePdosTextTest, IstepFileName) pdos.zero_out(); ModuleIO::write_pdos_text(*ucell, &pdos, nspin, nlocal, npoints, - emin, dos_edelta_ev, "./", 2); + emin, dos_edelta_ev, "./", 2, 0); const std::string str = read_file("./pdoss1g3.txt"); EXPECT_THAT(str, testing::HasSubstr("# istep: 3")); @@ -163,7 +163,7 @@ TEST_F(WritePdosTextTest, SpinPolarized) } ModuleIO::write_pdos_text(*ucell, pdos_arr, nspin, nlocal, npoints, - emin, dos_edelta_ev, "./", -1); + emin, dos_edelta_ev, "./", -1, 0); const std::string s1 = read_file("./pdoss1.txt"); const std::string s2 = read_file("./pdoss2.txt"); @@ -177,3 +177,67 @@ TEST_F(WritePdosTextTest, SpinPolarized) GlobalV::ofs_running.close(); remove("test_write_pdos_text.log"); } + +// --------------------------------------------------------------------------- +// out_app_flag = 1: fixed-name files, successive calls append records. +// --------------------------------------------------------------------------- +TEST_F(WritePdosTextTest, Mode1FixedNameAppend) +{ + GlobalV::ofs_running.open("test_write_pdos_text.log"); + + const int nspin = 1; + const int nlocal = 4; + const int npoints = 2; + const double emin = 0.0; + const double dos_edelta_ev = 1.0; + const int out_app_flag = 1; + + ModuleBase::matrix pdos(nlocal, npoints); + pdos.zero_out(); + + // First call: istep=0, mode 1 -> fixed name pdoss1.txt. + ModuleIO::write_pdos_text(*ucell, &pdos, nspin, nlocal, npoints, + emin, dos_edelta_ev, "./", /*istep=*/0, out_app_flag); + // Second call: istep=1, mode 1 -> append to same file. + ModuleIO::write_pdos_text(*ucell, &pdos, nspin, nlocal, npoints, + emin, dos_edelta_ev, "./", /*istep=*/1, out_app_flag); + + const std::string str = read_file("./pdoss1.txt"); + // Two istep headers present -> two appended records. + EXPECT_THAT(str, testing::HasSubstr("# istep: 1")); + EXPECT_THAT(str, testing::HasSubstr("# istep: 2")); + // g-suffixed file must NOT exist. + std::ifstream ifs("./pdoss1g1.txt"); + EXPECT_FALSE(ifs.good()); + + remove("./pdoss1.txt"); + GlobalV::ofs_running.close(); + remove("test_write_pdos_text.log"); +} + +// --------------------------------------------------------------------------- +// out_app_flag = 2: same naming as mode 0 (g suffix kept). +// --------------------------------------------------------------------------- +TEST_F(WritePdosTextTest, Mode2SameNamingAsMode0) +{ + GlobalV::ofs_running.open("test_write_pdos_text.log"); + + const int nspin = 1; + const int nlocal = 4; + const int npoints = 2; + const double emin = 0.0; + const double dos_edelta_ev = 1.0; + const int out_app_flag = 2; + ModuleBase::matrix pdos(nlocal, npoints); + pdos.zero_out(); + + ModuleIO::write_pdos_text(*ucell, &pdos, nspin, nlocal, npoints, + emin, dos_edelta_ev, "./", 3, out_app_flag); + + const std::string str = read_file("./pdoss1g4.txt"); + EXPECT_THAT(str, testing::HasSubstr("# istep: 4")); + + remove("./pdoss1g4.txt"); + GlobalV::ofs_running.close(); + remove("test_write_pdos_text.log"); +} diff --git a/source/source_io/module_dos/write_dos_lcao.cpp b/source/source_io/module_dos/write_dos_lcao.cpp index 926d94bd4e5..ed47e0c5eaf 100644 --- a/source/source_io/module_dos/write_dos_lcao.cpp +++ b/source/source_io/module_dos/write_dos_lcao.cpp @@ -20,7 +20,7 @@ void write_dos_lcao( const double& dos_edelta_ev, const double& dos_scale, const double& bcoeff, - const bool out_app_flag, + const int out_app_flag, const int istep, const int nspin, const int nlocal, @@ -62,7 +62,9 @@ void write_dos_lcao( ss << global_out_dir << "doss" << is + 1; - if(istep>=0) + // In append mode (out_app_flag == 1) the fixed name is reused and + // each ionic step appends a new DOS block; modes 0 and 2 keep g{N}. + if(istep>=0 && out_app_flag!=1) { ss << "g" << istep+1; } @@ -102,6 +104,7 @@ void write_dos_lcao( dos_edelta_ev, bcoeff, istep, + out_app_flag, nlocal, nspin, global_out_dir); @@ -125,7 +128,7 @@ template void write_dos_lcao( const double& dos_edelta_ev, const double& dos_scale, const double& bcoeff, - const bool out_app_flag, + const int out_app_flag, const int istep, const int nspin, const int nlocal, @@ -152,7 +155,7 @@ template void write_dos_lcao( const double& dos_edelta_ev, const double& dos_scale, const double& bcoeff, - const bool out_app_flag, + const int out_app_flag, const int istep, const int nspin, const int nlocal, diff --git a/source/source_io/module_dos/write_dos_lcao.h b/source/source_io/module_dos/write_dos_lcao.h index 0e9017149a4..25e8b6d14a6 100644 --- a/source/source_io/module_dos/write_dos_lcao.h +++ b/source/source_io/module_dos/write_dos_lcao.h @@ -28,7 +28,7 @@ namespace ModuleIO const double& dos_edelta_ev, // Delta energy const double& dos_scale, const double& bcoeff, - const bool out_app_flag, + const int out_app_flag, const int istep, const int nspin, const int nlocal, diff --git a/source/source_io/module_dos/write_dos_pw.cpp b/source/source_io/module_dos/write_dos_pw.cpp index 1ce707f59de..4026b181b60 100644 --- a/source/source_io/module_dos/write_dos_pw.cpp +++ b/source/source_io/module_dos/write_dos_pw.cpp @@ -20,7 +20,7 @@ void ModuleIO::write_dos_pw( const bool dos_setemin, const double dos_emin_ev, const bool two_fermi, - const bool out_app_flag, + const int out_app_flag, const int bndpar, const std::string& global_out_dir, std::ofstream& ofs_running) @@ -54,7 +54,9 @@ void ModuleIO::write_dos_pw( std::stringstream ss; ss << global_out_dir << "doss" << is + 1; - if(istep_in>=0) + // In append mode (out_app_flag == 1) the fixed name is reused and + // each ionic step appends a new DOS block; modes 0 and 2 keep g{N}. + if(istep_in>=0 && out_app_flag!=1) { ss << "g" << istep_in+1; } diff --git a/source/source_io/module_dos/write_dos_pw.h b/source/source_io/module_dos/write_dos_pw.h index 4b2417d8f42..2491c7d303a 100644 --- a/source/source_io/module_dos/write_dos_pw.h +++ b/source/source_io/module_dos/write_dos_pw.h @@ -27,7 +27,7 @@ namespace ModuleIO const bool dos_setemin, const double dos_emin_ev, const bool two_fermi, - const bool out_app_flag, + const int out_app_flag, const int bndpar, const std::string& global_out_dir, std::ofstream& ofs_running); diff --git a/source/source_io/module_dos/write_pdos_text.cpp b/source/source_io/module_dos/write_pdos_text.cpp index f0b3331dcf1..84cfab41c85 100644 --- a/source/source_io/module_dos/write_pdos_text.cpp +++ b/source/source_io/module_dos/write_pdos_text.cpp @@ -17,24 +17,35 @@ void ModuleIO::write_pdos_text( const double& emin, const double& dos_edelta_ev, const std::string& out_dir, - const int istep) + const int istep, + const int out_app_flag) { ModuleBase::TITLE("ModuleIO", "write_pdos_text"); // number of spin channels to write const int nspin0 = (nspin == 2) ? 2 : 1; + // In append mode (out_app_flag == 1) reuse the fixed filename and append + // each ionic step; single-point SCF passes istep = -1 and truncates. + const int istep_file = (out_app_flag == 1) ? -1 : istep; + const bool append_step = (out_app_flag == 1) && (istep >= 0); + for (int is = 0; is < nspin0; ++is) { std::stringstream ss; ss << out_dir << "pdoss" << is + 1; - if (istep >= 0) + if (istep_file >= 0) { - ss << "g" << istep + 1; + ss << "g" << istep_file + 1; } ss << ".txt"; - std::ofstream ofs(ss.str().c_str()); + std::ios_base::openmode mode = std::ios::out; + if (append_step) + { + mode |= std::ios::app; + } + std::ofstream ofs(ss.str().c_str(), mode); // iw2m stores m as 0..2l, mapping to physical m values: // 0->0, 1->+1, 2->-1, 3->+2, 4->-2, 5->+3, 6->-3 diff --git a/source/source_io/module_dos/write_pdos_text.h b/source/source_io/module_dos/write_pdos_text.h index dcb9a184623..434c6f7ebe9 100644 --- a/source/source_io/module_dos/write_pdos_text.h +++ b/source/source_io/module_dos/write_pdos_text.h @@ -8,7 +8,7 @@ namespace ModuleIO { /// Write PDOS in plain text long-table format. -/// File name: pdoss{spin}g{geom}.txt +/// File name: pdoss{spin}g{geom}.txt (g{geom} omitted when out_app_flag == 1) /// Columns: energy(eV) atom species l z m pdos(1/eV) /// For nspin=4, the two spinor components are summed. void write_pdos_text( @@ -20,7 +20,8 @@ void write_pdos_text( const double& emin, const double& dos_edelta_ev, const std::string& out_dir, - const int istep); + const int istep, + const int out_app_flag); } diff --git a/source/source_io/module_elf/write_elf.cpp b/source/source_io/module_elf/write_elf.cpp index fe6164c0107..911c6cb47a1 100644 --- a/source/source_io/module_elf/write_elf.cpp +++ b/source/source_io/module_elf/write_elf.cpp @@ -18,6 +18,7 @@ void write_elf( const int& precision, const std::string& geom_block, const bool two_fermi, + const bool append, std::ofstream& ofs_running) { ModuleBase::timer::start("ModuleIO", "write_elf"); @@ -35,8 +36,14 @@ void write_elf( const int nrxx = rho_basis->nrxx; const int npw = rho_basis->npw; - assert(nrxx>0); - assert(npw>0); + // A rank may own zero real-space grid points (nrxx == 0) when the FFT + // grid is decomposed across more processes than it has z-slabs. Such + // empty partitions are legitimate: the per-grid-point loops below simply + // do not execute, but every rank must still call real2recip / recip2real + // because PW_Basis::gatherp_scatters performs an MPI_Alltoallv that + // requires all ranks in POOL_WORLD to participate. Skipping the FFT on + // empty ranks would deadlock the grid-owning ranks inside Alltoallv. + // The asserts are removed to allow nrxx == 0 / npw == 0 through. std::vector> elf(nspin_eff, std::vector(nrxx, 0.)); // 1) calculate the kinetic energy density of vW KEDF @@ -154,7 +161,12 @@ void write_elf( } } - // 4) output the ELF = 1 / (1 + F^2) to cube file + // 4) output the ELF = 1 / (1 + F^2) to cube file. + // Every rank, including empty-partition ranks with nrxx == 0, must + // enter this block so write_vdata_palgrid (which calls + // Parallel_Grid::reduce / MPI_Gatherv) sees a collective call from all + // ranks. Empty partitions pass a null pointer which Parallel_Grid::reduce + // accepts only when nrxx == 0 (sendcount 0 ignores the buffer). const double ef_tmp = 0.0; const int out_fermi = 0; @@ -175,6 +187,7 @@ void write_elf( out_fermi, two_fermi, false, + append, ofs_running, "electron localization function"); } @@ -200,6 +213,7 @@ void write_elf( out_fermi, two_fermi, false, + append, ofs_running, desc_elf); } @@ -238,6 +252,7 @@ void write_elf( out_fermi, two_fermi, false, + append, ofs_running, "electron localization function"); } diff --git a/source/source_io/module_elf/write_elf.h b/source/source_io/module_elf/write_elf.h index 970843e6526..e0396824d22 100644 --- a/source/source_io/module_elf/write_elf.h +++ b/source/source_io/module_elf/write_elf.h @@ -21,6 +21,7 @@ void write_elf( const int& precision, const std::string& geom_block, const bool two_fermi, + const bool append, std::ofstream& ofs_running); } diff --git a/source/source_io/module_energy/write_bands.cpp b/source/source_io/module_energy/write_bands.cpp index f796b19f07a..86bdc45ecc3 100644 --- a/source/source_io/module_energy/write_bands.cpp +++ b/source/source_io/module_energy/write_bands.cpp @@ -9,13 +9,64 @@ #include "source_base/tool_title.h" #include "source_io/module_output/spin_tag.h" #include "source_io/module_parameter/parameter.h" // mohan add 20250911 +#include "source_main/version.h" #ifdef __MPI #include #endif +#include + +namespace +{ + +/** + * @brief spin-channel label for the band provenance header. + * + * Unlike ModuleIO::make_spin_tag, which pads "up" so that both channels share + * the same width wherever they are printed next to each other, the band header + * goes into a per-spin file (bands1.txt/bands2.txt), so the compact form + * " (spin up)" is used. + * + * @param is spin channel index + * @param nspin number of spins, padding is dropped unless nspin == 2 + * @return " (spin up)", " (spin down)" or an empty string + */ +std::string band_spin_tag(const int is, const int nspin) +{ + if (nspin != 2 || is < 0) + { + return ""; + } + return (is == 0) ? " (spin up)" : " (spin down)"; +} + +/** + * @brief build the STRU-style provenance header that precedes the k-point + * block of one ionic step in a band output file. + * + * @param istep zero-based ionic step index, printed one-based + * @param spin_tag label of the collinear spin channel, empty when nspin != 2 + * @return three comment lines: ABACUS version, local timestamp, ionic step + */ +std::string build_band_header(const int istep, const std::string& spin_tag) +{ + const std::time_t now = std::time(nullptr); + std::tm tm_local; + localtime_r(&now, &tm_local); + char time_buf[64]; + std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &tm_local); + return FmtCore::format("# ABACUS version: %s\n# Written at %s\n# IONIC STEP %d%s\n", + VERSION, + time_buf, + istep + 1, + spin_tag.c_str()); +} + +} // namespace + void ModuleIO::write_bands(const Input_para& inp, const ModuleBase::matrix& ekb, const K_Vectors& kv, - std::ofstream& ofs_running) + std::ofstream& ofs_running, const int istep) { // write band information to band.txt if (inp.out_band[0]) @@ -41,9 +92,10 @@ void ModuleIO::write_bands(const Input_para& inp, const ModuleBase::matrix& ekb, ss << ".txt"; const std::string eig_file = ss.str(); - const std::string spin_tag = ModuleIO::make_spin_tag(is, nspin0); - ofs_running << " Write eigenvalues" << spin_tag << " to file: " << eig_file << std::endl; + const std::string log_spin_tag = ModuleIO::make_spin_tag(is, nspin0); + ofs_running << " Write eigenvalues" << log_spin_tag << " to file: " << eig_file << std::endl; + const std::string header = build_band_header(istep, band_spin_tag(is, nspin0)); const double eshift = 0.0; nscf_bands(is, eig_file, @@ -51,7 +103,8 @@ void ModuleIO::write_bands(const Input_para& inp, const ModuleBase::matrix& ekb, eshift, inp.out_band[1], // precision global_ekb, - kv); + kv, + header); } } } @@ -62,7 +115,8 @@ void ModuleIO::nscf_bands(const int& is, const double& fermie, const int& precision, const ModuleBase::matrix& ekb, - const K_Vectors& kv) + const K_Vectors& kv, + const std::string& header) { ModuleBase::TITLE("ModuleIO", "nscf_bands"); ModuleBase::timer::start("ModuleIO", "nscf_bands"); @@ -89,7 +143,7 @@ void ModuleIO::nscf_bands(const int& is, const int band_group = Parallel::make_band_world().rank(); if (GlobalV::MY_RANK == 0) { - if (PARAM.inp.out_app_flag == true) + if (PARAM.inp.out_app_flag == 1) { // do nothing } @@ -125,6 +179,10 @@ void ModuleIO::nscf_bands(const int& is, if (kv.para_k.rank_in_pool == 0 && band_group == 0) { std::ofstream ofs(eig_file.c_str(), std::ios::app); + if (ik == 0) + { + ofs << header; + } ofs << FmtCore::format("%4d", ik + 1); int width = precision + 4; std::string fmtstr = " %." + std::to_string(precision) + "f"; @@ -149,7 +207,7 @@ void ModuleIO::nscf_bands(const int& is, if (GlobalV::MY_RANK == 0) { - if (PARAM.inp.out_app_flag == true) + if (PARAM.inp.out_app_flag == 1) { ofs.open(eig_file.c_str(), std::ios::app); } @@ -171,6 +229,10 @@ void ModuleIO::nscf_bands(const int& is, const int spin_k = ik + is * nks_np; if (kv.isk[spin_k] == is) { + if (ik == 0) + { + ofs << header; + } ofs << FmtCore::format("%4d", ik + 1); int width = precision + 4; std::string fmtstr = " %." + std::to_string(precision) + "f"; diff --git a/source/source_io/module_energy/write_bands.h b/source/source_io/module_energy/write_bands.h index 09c2135aff3..39b912498e5 100644 --- a/source/source_io/module_energy/write_bands.h +++ b/source/source_io/module_energy/write_bands.h @@ -11,21 +11,36 @@ namespace ModuleIO { +/** + * @brief write the eigenvalues of every spin channel to band.txt (nspin=1/4) + * or to bands1.txt/bands2.txt (nspin=2). + * + * @param inp input parameters, out_band[0] enables the output and out_band[1] + * sets the number of printed decimals + * @param ekb eigenvalues of k points and bands + * @param kv klist + * @param ofs_running running log stream + * @param istep zero-based index of the current ionic step, printed one-based + * in the per-step provenance header + */ void write_bands(const Input_para& inp, const ModuleBase::matrix& ekb, const K_Vectors& kv, - std::ofstream& ofs_running); + std::ofstream& ofs_running, + const int istep); /** * @brief calculate the band structure * * @param is spin index is = 0 or 1 - * @param out_band_dir directory to save the band structure + * @param eig_file file to save the band structure * @param nband number of bands * @param fermie fermi energy * @param precision precision of the output * @param ekb eigenvalues of k points and bands * @param kv klist + * @param header provenance header written once in front of the k-point block; + * pass an empty string to omit it */ void nscf_bands(const int& is, const std::string &eig_file, @@ -33,7 +48,8 @@ void nscf_bands(const int& is, const double& fermie, const int& precision, const ModuleBase::matrix& ekb, - const K_Vectors& kv); + const K_Vectors& kv, + const std::string& header); } #endif diff --git a/source/source_io/module_energy/write_eband_terms.cpp b/source/source_io/module_energy/write_eband_terms.cpp index b7474b68023..4afa0946913 100644 --- a/source/source_io/module_energy/write_eband_terms.cpp +++ b/source/source_io/module_energy/write_eband_terms.cpp @@ -6,6 +6,9 @@ #include "source_lcao/module_operator_lcao/nonlocal.h" #include "source_cell/module_neighbor/sltk_grid_driver.h" +#include "source_base/constants.h" +#include "source_base/formatter.h" +#include "source_main/version.h" #include "source_psi/psi.h" #include "source_cell/unitcell.h" #include "source_cell/klist.h" @@ -22,10 +25,57 @@ #include "source_lcao/module_operator_lcao/op_exx_lcao.h" #endif +#include +#include + +namespace +{ +/// @brief Format the current local time for output file headers. +/// @return Timestamp in "YYYY-MM-DD HH:MM:SS" format. +std::string local_time_stamp() +{ + const std::time_t now = std::time(nullptr); + std::tm tm_now; + localtime_r(&now, &tm_now); + char buffer[32]; + std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm_now); + return std::string(buffer); +} + +/// @brief Build the five comment lines preceding band-decomposed energy data. +/// @param istep Zero-based ionic step index, printed one-based. +/// @param etot_eV Total energy of the written frame in eV. +/// @param term_symbol Operator symbol used in the expectation value expression. +/// @param time_stamp Timestamp shared by all files of the same run. +/// @return Comment block terminated by a newline. +std::string build_eband_header(const int istep, + const double etot_eV, + const std::string& term_symbol, + const std::string& time_stamp) +{ + const int istep_one_based = istep + 1; + const std::string header = FmtCore::format( + "# ABACUS version: %s\n" + "# Written at %s\n" + "# RELAX STEP %d, Energy: %.8f eV\n" + "# e_{ikn} = in the KS orbital basis\n" + "# units: columns are e/2 in Hartree and e in eV; data rows follow nk, nspin0, nbands\n", + VERSION, + time_stamp.c_str(), + istep_one_based, + etot_eV, + term_symbol.c_str()); + return header; +} +} // namespace + namespace ModuleIO { template void write_eband_terms(const int nspin, + const int istep, + const double etot, + const int my_rank, const int nbasis, const int drank, const Parallel_Orbitals* pv, @@ -53,6 +103,8 @@ void write_eband_terms(const int nspin, // 0. prepare const int& nbands = wg.nc; const int nspin0 = (nspin == 2) ? 2 : 1; + const double etot_eV = etot * ModuleBase::Ry_to_eV; + const std::string time_stamp = local_time_stamp(); double etxc = 0.0; double vtxc = 0.0; @@ -68,6 +120,20 @@ void write_eband_terms(const int nspin, } }; + // Only rank 0 writes; the orbital energies above are reduced on all ranks. + auto write_term = [&](const std::vector>& e_orb, + const std::string& out_name, + const std::string& term_symbol) + { + if (my_rank != 0) + { + return; + } + const std::string header = build_eband_header(istep, etot_eV, term_symbol, time_stamp); + const std::string& out_dir = PARAM.globalv.global_out_dir; + write_orb_energy(kv, nspin0, nbands, e_orb, out_name, header, out_dir); + }; + auto all_band_energy = [&wg](const int ik, const std::vector& e_orb)->double { double e = 0; @@ -107,7 +173,7 @@ void write_eband_terms(const int nspin, cVc(kinetic_k_ao.get_hk(), &psi(ik, 0, 0), nbasis, nbands, *pv, p2d), p2d)); } - write_orb_energy(kv, nspin0, nbands, e_orb_kinetic, "kinetic", "", PARAM.globalv.global_out_dir); + write_term(e_orb_kinetic, "eband_kin.txt", "T"); } // 2. pp: local @@ -139,7 +205,7 @@ void write_eband_terms(const int nspin, e_orb_pp_local.emplace_back(orbital_energy(ik, nbands, cVc(v_pp_local_k_ao.get_hk(), &psi(ik, 0, 0), nbasis, nbands, *pv, p2d), p2d)); } - write_orb_energy(kv, nspin0, nbands, e_orb_pp_local, "vpp_local", "", PARAM.globalv.global_out_dir); + write_term(e_orb_pp_local, "eband_vl.txt", "V_loc"); } // 3. pp: nonlocal @@ -159,7 +225,7 @@ void write_eband_terms(const int nspin, e_orb_pp_nonlocal.emplace_back(orbital_energy(ik, nbands, cVc(v_pp_nonlocal_k_ao.get_hk(), &psi(ik, 0, 0), nbasis, nbands, *pv, p2d), p2d)); } - write_orb_energy(kv, nspin0, nbands, e_orb_pp_nonlocal, "vpp_nonlocal", "", PARAM.globalv.global_out_dir); + write_term(e_orb_pp_nonlocal, "eband_vnl.txt", "V_nonlocal"); } // 4. hartree @@ -192,7 +258,7 @@ void write_eband_terms(const int nspin, cVc(v_hartree_k_ao.get_hk(), &psi(ik, 0, 0), nbasis, nbands, *pv, p2d), p2d)); } for (auto& op : v_hartree_op) { delete op; } - write_orb_energy(kv, nspin0, nbands, e_orb_hartree, "vhartree", "", PARAM.globalv.global_out_dir); + write_term(e_orb_hartree, "eband_hart.txt", "V_H"); } // 5. xc (including exx) @@ -233,7 +299,7 @@ void write_eband_terms(const int nspin, // Explicit template instantiations template void write_eband_terms( - const int, const int, const int, const Parallel_Orbitals*, const psi::Psi&, + const int, const int, const double, const int, const int, const int, const Parallel_Orbitals*, const psi::Psi&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const ModuleBase::matrix&, Grid_Driver&, const std::vector&, const TwoCenterBundle&, const Exx_Info& @@ -244,7 +310,7 @@ template void write_eband_terms( ); template void write_eband_terms, double>( - const int, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, + const int, const int, const double, const int, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const ModuleBase::matrix&, Grid_Driver&, const std::vector&, const TwoCenterBundle&, const Exx_Info& @@ -255,7 +321,7 @@ template void write_eband_terms, double>( ); template void write_eband_terms, std::complex>( - const int, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, + const int, const int, const double, const int, const int, const int, const Parallel_Orbitals*, const psi::Psi>&, const UnitCell&, Structure_Factor&, surchem&, const ModulePW::PW_Basis&, const ModulePW::PW_Basis&, const ModuleBase::matrix&, const Charge&, const K_Vectors&, const ModuleBase::matrix&, Grid_Driver&, const std::vector&, const TwoCenterBundle&, const Exx_Info& diff --git a/source/source_io/module_energy/write_eband_terms.h b/source/source_io/module_energy/write_eband_terms.h index 54d980a8701..bd78c40a39a 100644 --- a/source/source_io/module_energy/write_eband_terms.h +++ b/source/source_io/module_energy/write_eband_terms.h @@ -42,10 +42,17 @@ namespace ModuleIO /// @brief Write the band-decomposed energy of each Hamiltonian term /// (kinetic, local/nonlocal pp, Hartree, XC) in KS orbital representation. +/// @param nspin Number of spin components (1, 2 or 4) +/// @param istep Zero-based ionic step index; written one-based in the header +/// @param etot Total energy of the current frame in Ry +/// @param my_rank World rank; only rank 0 writes the files /// @tparam TK K-point data type (double or std::complex) /// @tparam TR Real-space data type (double or std::complex) template void write_eband_terms(const int nspin, + const int istep, + const double etot, + const int my_rank, const int nbasis, const int drank, const Parallel_Orbitals* pv, diff --git a/source/source_io/module_hs/angmom_op_mat.cpp b/source/source_io/module_hs/angmom_op_mat.cpp index 16f8dc7e44d..a0cfa54e57a 100644 --- a/source/source_io/module_hs/angmom_op_mat.cpp +++ b/source/source_io/module_hs/angmom_op_mat.cpp @@ -380,7 +380,8 @@ void ModuleIO::Angmom_op::calculate( const UnitCell& ucell, const int precision, const int rank, - const int istep) + const int istep, + const int out_app_flag) { ModuleBase::TITLE("Angmom_op", "calculate"); ModuleBase::timer::start("Angmom_op", "calculate"); @@ -392,9 +393,11 @@ void ModuleIO::Angmom_op::calculate( } std::ofstream ofout; const std::string dir = "xyz"; - /// @brief Filename suffix: if istep >= 0, append "g{istep+1}" (e.g., "g1") - /// to follow the out_freq_ion convention; otherwise no suffix. - const std::string step_suffix = (istep >= 0) ? "g" + std::to_string(istep + 1) : ""; + /// @brief Filename suffix: in append mode (out_app_flag == 1) the fixed + /// name is reused and each ionic step appends; modes 0 and 2 carry + /// "g{istep+1}" (e.g., "g1") following the out_freq_ion convention. + const std::string step_suffix = (istep >= 0 && out_app_flag != 1) ? "g" + std::to_string(istep + 1) : ""; + const bool append_step = (out_app_flag == 1) && (istep >= 0); const std::string title = "# it ia il iz im iRx iRy iRz jt ja jl jz jm Re[] Im[]\n" "# it: atomtype index of the first atom\n" "# ia: atomic index of the first atom within the atomtype\n" @@ -413,7 +416,12 @@ void ModuleIO::Angmom_op::calculate( for (char d : dir) { std::string fn = outdir + "l" + d + step_suffix + "_nao.txt"; - ofout.open(fn, std::ios::out); + std::ios_base::openmode mode = std::ios::out; + if (append_step) + { + mode |= std::ios::app; + } + ofout.open(fn, mode); ofout << title; this->kernel(&ofout, ucell, d, precision); ofout.close(); diff --git a/source/source_io/module_hs/angmom_op_mat.h b/source/source_io/module_hs/angmom_op_mat.h index c5000d68f32..c79a059ff46 100644 --- a/source/source_io/module_hs/angmom_op_mat.h +++ b/source/source_io/module_hs/angmom_op_mat.h @@ -226,19 +226,21 @@ namespace ModuleIO * filenames are fixed as l{x,y,z}[g{step}]_nao.txt) * @param outdir the output directory * @param ucell the unit cell object - * @param precision the precision of the output, default is 10 - * @param rank the MPI rank, default is 0 - * @param istep the ionic step index. If >= 0, the filename includes - * `g{istep+1}` (e.g., lxg1_nao.txt); if -1, the filename - * has no step suffix (e.g., lx_nao.txt). Default is -1 - * (single-step overwrite mode). + * @param precision the precision of the output + * @param rank the MPI rank + * @param istep the ionic step index; -1 for single-point runs + * @param out_app_flag append mode: with 1 the fixed filename + * l{x,y,z}_nao.txt is reused and each step appends; + * with 0 or 2 the filename carries `g{istep+1}` + * (e.g., lxg1_nao.txt) */ void calculate(const std::string& prefix, const std::string& outdir, const UnitCell& ucell, - const int precision = 10, - const int rank = 0, - const int istep = -1); + const int precision, + const int rank, + const int istep, + const int out_app_flag); private: std::ofstream fallback_ofs_; diff --git a/source/source_io/module_hs/dhs_sparse_writer.cpp b/source/source_io/module_hs/dhs_sparse_writer.cpp index 5c4a049546f..636938c5f14 100644 --- a/source/source_io/module_hs/dhs_sparse_writer.cpp +++ b/source/source_io/module_hs/dhs_sparse_writer.cpp @@ -21,10 +21,9 @@ void ModuleIO::save_dH_sparse(const int& istep, const bool& binary, const std::string& fileflag, const int precision, - const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nspin, const int nlocal, std::ofstream& ofs_running) { @@ -67,8 +66,9 @@ void ModuleIO::save_dH_sparse(const int& istep, const int total_R_num = static_cast(all_R_coor_ptr.size()); int output_R_number = 0; - // Use istep for filename/append decisions; clamp only the header STEP value. - const int header_step = std::max(istep, 0); + // Use istep for filename/append decisions; clamp only the header STEP + // value, printed one-based per governance rule 22. + const int header_step = std::max(istep, 0) + 1; int spin_loop = 1; if (nspin == 2) { @@ -112,20 +112,19 @@ void ModuleIO::save_dH_sparse(const int& istep, } } - const bool md_no_append = (calculation == "md") && !out_app_flag; for (auto& comp: comps) { for (int ispin = 0; ispin < 2; ++ispin) { if (istep >= 0) { - comp.fname[ispin] << (md_no_append ? global_matrix_dir : global_out_dir) + comp.fname[ispin] << global_matrix_dir << "d" << fileflag << "r" << comp.axis << "s" << (ispin + 1) << "g" << (istep + 1) << "_nao.csr"; } else { - comp.fname[ispin] << global_out_dir + comp.fname[ispin] << global_matrix_dir << "d" << fileflag << "r" << comp.axis << "s" << (ispin + 1) << "_nao.csr"; } @@ -134,7 +133,9 @@ void ModuleIO::save_dH_sparse(const int& istep, if (GlobalV::DRANK == 0) { - const bool open_in_append = (calculation == "md") && out_app_flag && istep >= 0; + // Append for any multi-step run (MD or relax) in append mode; + // single-point SCF passes istep = -1 and starts a fresh file. + const bool open_in_append = (out_app_flag == 1) && istep >= 0; for (auto& comp: comps) { const std::string label = std::string("dH") + comp.axis; diff --git a/source/source_io/module_hs/dhs_sparse_writer.h b/source/source_io/module_hs/dhs_sparse_writer.h index 8aab9c8007e..59677d8f6ce 100644 --- a/source/source_io/module_hs/dhs_sparse_writer.h +++ b/source/source_io/module_hs/dhs_sparse_writer.h @@ -17,10 +17,9 @@ void save_dH_sparse(const int& istep, const bool& binary, const std::string& fileflag, const int precision, - const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nspin, const int nlocal, std::ofstream& ofs_running); diff --git a/source/source_io/module_hs/hs_sparse_io.cpp b/source/source_io/module_hs/hs_sparse_io.cpp index f645893164e..8f02178944e 100644 --- a/source/source_io/module_hs/hs_sparse_io.cpp +++ b/source/source_io/module_hs/hs_sparse_io.cpp @@ -33,7 +33,9 @@ void open_sparse_file(std::ofstream& ofs, const ModuleIO::SparseWriteOptions& op { mode |= std::ios::binary; } - const bool append_on_restart = (options.calculation == "md") && options.out_app_flag && options.istep >= 0; + // Append for any multi-step run (MD or relax) in append mode; + // single-point SCF passes istep = -1 and always starts a fresh file. + const bool append_on_restart = (options.out_app_flag == 1) && (options.istep >= 0); if (append_on_restart) { mode |= std::ios::app; @@ -51,7 +53,9 @@ void write_sparse_header(std::ofstream& ofs, const int nlocal, const int output_R_number) { - const int step = std::max(options.istep, 0); + // ionic step printed one-based per governance rule 22, consistent + // with the g{N} filename suffix (g1 holds the first ionic step) + const int step = std::max(options.istep, 0) + 1; if (options.binary) { ofs.write(reinterpret_cast(&step), sizeof(int)); diff --git a/source/source_io/module_hs/hs_sparse_io.h b/source/source_io/module_hs/hs_sparse_io.h index d8bdb089989..3371e78628f 100644 --- a/source/source_io/module_hs/hs_sparse_io.h +++ b/source/source_io/module_hs/hs_sparse_io.h @@ -31,7 +31,7 @@ struct SparseWriteOptions // Runtime flags that decide file-open mode (append on md restart). // Must be provided explicitly by the caller instead of reading PARAM. std::string calculation; - bool out_app_flag = false; + int out_app_flag = 0; }; template diff --git a/source/source_io/module_hs/hsk_writer.cpp b/source/source_io/module_hs/hsk_writer.cpp index 263635b573b..8b0242cf0b6 100644 --- a/source/source_io/module_hs/hsk_writer.cpp +++ b/source/source_io/module_hs/hsk_writer.cpp @@ -14,7 +14,7 @@ void ModuleIO::write_hsk( hamilt::Hamilt* p_hamilt, const Parallel_Orbitals &pv, const bool gamma_only, - const bool out_app_flag, + const int out_app_flag, const int istep, const int out_type, const int precision, @@ -40,6 +40,9 @@ void ModuleIO::write_hsk( { p_hamilt->updateHk(ik); const bool binary = (out_type == 2); + // save_mat takes a Boolean append flag: only mode 1 appends, modes + // 0 and 2 overwrite their per-step files + const bool append_mat = (out_app_flag == 1); hamilt::MatrixBlock h_mat; hamilt::MatrixBlock s_mat; @@ -56,7 +59,7 @@ void ModuleIO::write_hsk( binary, precision, 1, - out_app_flag, + append_mat, h_fn, pv, drank, @@ -83,7 +86,7 @@ void ModuleIO::write_hsk( binary, precision, 1, - out_app_flag, + append_mat, s_fn, pv, drank, @@ -96,11 +99,11 @@ template void ModuleIO::write_hsk( const std::string&, const int, const int, const int, const std::vector&, const std::vector&, hamilt::Hamilt*, const Parallel_Orbitals&, - const bool, const bool, const int, const int, const int, + const bool, const int, const int, const int, const int, const int, const std::string&, const int, std::ofstream&); template void ModuleIO::write_hsk>( const std::string&, const int, const int, const int, const std::vector&, const std::vector&, hamilt::Hamilt>*, const Parallel_Orbitals&, - const bool, const bool, const int, const int, const int, + const bool, const int, const int, const int, const int, const int, const std::string&, const int, std::ofstream&); diff --git a/source/source_io/module_hs/hsk_writer.h b/source/source_io/module_hs/hsk_writer.h index af2bb6a8540..d80a5eb8839 100644 --- a/source/source_io/module_hs/hsk_writer.h +++ b/source/source_io/module_hs/hsk_writer.h @@ -25,7 +25,7 @@ void write_hsk(const std::string& global_out_dir, hamilt::Hamilt* p_hamilt, const Parallel_Orbitals& pv, const bool gamma_only, - const bool out_app_flag, + const int out_app_flag, const int istep, const int out_type, const int precision, diff --git a/source/source_io/module_hs/hsr_legacy.cpp b/source/source_io/module_hs/hsr_legacy.cpp index 0f1e37daa3e..d5b814214e9 100644 --- a/source/source_io/module_hs/hsr_legacy.cpp +++ b/source/source_io/module_hs/hsr_legacy.cpp @@ -40,7 +40,7 @@ void ModuleIO::output_dSR(const int& istep, // mohan update 2024-04-01 const std::string fileflag_s = "s"; ModuleIO::save_dH_sparse(istep, pv, HS_Arrays, options.sparse_threshold, options.binary, - fileflag_s, options.precision, options.global_out_dir, + fileflag_s, options.precision, options.global_matrix_dir, options.calculation, options.out_app_flag, options.nspin, nlocal, ofs_running); @@ -86,7 +86,7 @@ void ModuleIO::output_dHR(const int& istep, // mohan update 2024-04-01 const std::string fileflag_h = "h"; ModuleIO::save_dH_sparse(istep, pv, HS_Arrays, options.sparse_threshold, options.binary, - fileflag_h, options.precision, options.global_out_dir, + fileflag_h, options.precision, options.global_matrix_dir, options.calculation, options.out_app_flag, options.nspin, nlocal, ofs_running); @@ -107,7 +107,7 @@ void ModuleIO::output_SR(Parallel_Orbitals& pv, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nspin, std::ofstream& ofs_running) { @@ -174,17 +174,18 @@ void ModuleIO::output_TR(const int istep, ModuleBase::timer::start("ModuleIO", "output_TR"); std::stringstream sst; - const bool md_no_append = (options.calculation == "md") && !options.out_app_flag; - if (istep >= 0) + // In append mode (out_app_flag == 1) the file keeps the fixed name so + // the MD append logic in save_sparse can accumulate all ionic steps. + if (istep >= 0 && options.out_app_flag != 1) { // insert the 1-indexed ionic-step suffix right after the "tr" prefix, // i.e. tr_nao.csr -> trg{step+1}_nao.csr (aligned with lxg/rrg naming) const std::string base = TR_filename.substr(0, TR_filename.size() - std::string("_nao.csr").size()); - sst << (md_no_append ? options.global_matrix_dir : options.global_out_dir) << base << "g" << (istep + 1) << "_nao.csr"; + sst << options.global_matrix_dir << base << "g" << (istep + 1) << "_nao.csr"; } else { - sst << options.global_out_dir << TR_filename; + sst << options.global_matrix_dir << TR_filename; } ofs_running << " Write T(R) matrix in NAO basis to file: " << sst.str() << std::endl; sparse_format::cal_TR(ucell, pv, HS_Arrays, grid, two_center_bundle, orb, options.sparse_threshold); @@ -220,7 +221,7 @@ template void ModuleIO::output_SR(Parallel_Orbitals& pv, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nspin, std::ofstream& ofs_running); template void ModuleIO::output_SR>(Parallel_Orbitals& pv, @@ -233,6 +234,6 @@ template void ModuleIO::output_SR>(Parallel_Orbitals& pv, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nspin, std::ofstream& ofs_running); diff --git a/source/source_io/module_hs/hsr_legacy.h b/source/source_io/module_hs/hsr_legacy.h index 334374125e1..cddd93aed3f 100644 --- a/source/source_io/module_hs/hsr_legacy.h +++ b/source/source_io/module_hs/hsr_legacy.h @@ -29,7 +29,7 @@ struct MatROutputOptions std::string global_out_dir; std::string global_matrix_dir; std::string calculation; - bool out_app_flag = false; + int out_app_flag = 0; int nspin = 1; }; @@ -82,7 +82,7 @@ void output_SR(Parallel_Orbitals& pv, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nspin, std::ofstream& ofs_running); diff --git a/source/source_io/module_hs/hsr_writer.cpp b/source/source_io/module_hs/hsr_writer.cpp index 10b5218fa69..54975d9d1c3 100644 --- a/source/source_io/module_hs/hsr_writer.cpp +++ b/source/source_io/module_hs/hsr_writer.cpp @@ -219,7 +219,8 @@ void ModuleIO::write_hcontainer_csr_binary(const std::string& fname, } std::sort(R_coordinates.begin(), R_coordinates.end()); - const int step = std::max(istep, 0); + // ionic step written one-based per governance rule 22 + const int step = std::max(istep, 0) + 1; const int nbasis = mat_serial->get_nbasis(); const int nR = static_cast(R_coordinates.size()); write_native_value(ofs, step); diff --git a/source/source_io/module_hs/hterm_writer.cpp b/source/source_io/module_hs/hterm_writer.cpp index a4be8399b56..3e3c7a00204 100644 --- a/source/source_io/module_hs/hterm_writer.cpp +++ b/source/source_io/module_hs/hterm_writer.cpp @@ -78,9 +78,6 @@ static void gather_and_write(const std::string& prefix, const bool append, const int* iat2iwt, const int nat, - const std::string& calculation, - const bool out_app_flag, - const std::string& global_out_dir, const std::string& global_matrix_dir, std::ofstream* ofs_running) { @@ -94,9 +91,7 @@ static void gather_and_write(const std::string& prefix, if (GlobalV::MY_RANK == 0) #endif { - const bool md_no_append = (calculation == "md") && !out_app_flag; - const std::string& out_dir = md_no_append ? global_matrix_dir : global_out_dir; - const std::string fname = out_dir + hsr_gen_fname(prefix, ispin, append, istep); + const std::string fname = global_matrix_dir + hsr_gen_fname(prefix, ispin, append, istep); // H(R) component terms (T, V^NL, V^L, V^H, V^XC, V^EXX) are parts of // the Hamiltonian, not the full H(R): they do not carry the Fermi annotation const double no_efermi = 0.0; @@ -127,7 +122,7 @@ static void write_hk_common(hamilt::HContainer& hR, const int nlocal, const bool gamma_only, const std::string& global_out_dir, - const bool out_app_flag, + const int out_app_flag, const std::string& ks_solver, const int drank) { @@ -159,7 +154,7 @@ static void write_hk_common(hamilt::HContainer& hR, false, 8, false, - out_app_flag, + append, fname, pv, drank, @@ -200,7 +195,7 @@ static void write_h_term(WriteHParams& params, const HTermSpec& spec) const int nlocal = params.nlocal; const bool gamma_only = params.gamma_only_local; const std::string& global_out_dir = params.global_out_dir; - const bool out_app_flag = params.out_app_flag; + const int out_app_flag = params.out_app_flag; const int nspin_out = (nspin == 2 ? 2 : 1); @@ -215,8 +210,8 @@ static void write_h_term(WriteHParams& params, const HTermSpec& spec) if (also_hR) { gather_and_write(spec.r_prefix, spec.r_label, spec.term_name, hR_tmp, ucell, pv, nspin, ispin, - istep, append, iat2iwt, nat, params.calculation, params.out_app_flag, - params.global_out_dir, params.global_matrix_dir, params.ofs_running); + istep, append, iat2iwt, nat, + params.global_matrix_dir, params.ofs_running); } } @@ -378,7 +373,7 @@ static void write_h_exx_impl(const UnitCell& ucell, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const std::string& ks_solver, const int drank, const Exx_Info& exx_info, @@ -402,7 +397,7 @@ static void write_h_exx_impl(const UnitCell& ucell, if (also_hR) { gather_and_write("vexx", "V^EXX", "exx", hR_tmp, ucell, pv, nspin, ispin, istep, append, - iat2iwt, nat, calculation, out_app_flag, global_out_dir, global_matrix_dir, + iat2iwt, nat, global_matrix_dir, ofs_running); } } diff --git a/source/source_io/module_hs/hterm_writer.h b/source/source_io/module_hs/hterm_writer.h index 7aa2f721fc6..2cc9599548e 100644 --- a/source/source_io/module_hs/hterm_writer.h +++ b/source/source_io/module_hs/hterm_writer.h @@ -46,7 +46,7 @@ struct WriteHParams bool domag = false; bool domag_z = false; int gga_grad = 0; // used by the XC term - bool out_app_flag = false; + int out_app_flag = 0; std::string calculation; std::string global_out_dir; std::string global_matrix_dir; diff --git a/source/source_io/module_hs/output_mat_sparse.cpp b/source/source_io/module_hs/output_mat_sparse.cpp index 5179bd5477e..59e133574e7 100644 --- a/source/source_io/module_hs/output_mat_sparse.cpp +++ b/source/source_io/module_hs/output_mat_sparse.cpp @@ -25,7 +25,7 @@ void output_mat_sparse(const MatSparseOutputOptions& options, const std::string& global_out_dir = PARAM.globalv.global_out_dir; const std::string& global_matrix_dir = PARAM.globalv.global_matrix_dir; const std::string& calculation = PARAM.inp.calculation; - const bool out_app_flag = PARAM.inp.out_app_flag; + const int out_app_flag = PARAM.inp.out_app_flag; const int nspin = PARAM.inp.nspin; const bool gamma_only_local = PARAM.globalv.gamma_only_local; const int npol = PARAM.globalv.npol; diff --git a/source/source_io/module_hs/pos_op_mat.cpp b/source/source_io/module_hs/pos_op_mat.cpp index 04ec8d45e85..e37c7f1245e 100644 --- a/source/source_io/module_hs/pos_op_mat.cpp +++ b/source/source_io/module_hs/pos_op_mat.cpp @@ -89,7 +89,7 @@ void Position_op::out_rR(const UnitCell& ucell, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nlocal, const int npol, std::ofstream& ofs_running) diff --git a/source/source_io/module_hs/pos_op_mat.h b/source/source_io/module_hs/pos_op_mat.h index 96a6513c23e..d76d71e0c3e 100644 --- a/source/source_io/module_hs/pos_op_mat.h +++ b/source/source_io/module_hs/pos_op_mat.h @@ -76,7 +76,7 @@ class Position_op const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - const bool out_app_flag, + const int out_app_flag, const int nlocal, const int npol, std::ofstream& ofs_running); diff --git a/source/source_io/module_hs/pos_op_writer.cpp b/source/source_io/module_hs/pos_op_writer.cpp index 15e1fa201ea..8095abab207 100644 --- a/source/source_io/module_hs/pos_op_writer.cpp +++ b/source/source_io/module_hs/pos_op_writer.cpp @@ -22,7 +22,7 @@ void PosOpWriter::out_lat_r(const UnitCell& ucell, const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - bool out_app_flag, + int out_app_flag, int nlocal, int npol, double sparse_threshold, @@ -216,19 +216,23 @@ void PosOpWriter::out_lat_r(const UnitCell& ucell, if (GlobalV::DRANK == 0) { std::stringstream ssr; - const bool md_no_append = (calculation == "md") && !out_app_flag; - if (step >= 0) + // In append mode (out_app_flag == 1) keep the fixed filename so + // the append branch below can accumulate all ionic steps. + if (step >= 0 && out_app_flag != 1) { - ssr << (md_no_append ? global_matrix_dir : global_out_dir) << "rrg" << (step + 1) << "_nao.txt"; + ssr << global_matrix_dir << "rrg" << (step + 1) << "_nao.txt"; } else { - ssr << global_out_dir << "rr_nao.txt"; + ssr << global_matrix_dir << "rr_nao.txt"; } ofs_tem1.close(); - const bool open_in_append = (calculation == "md") && out_app_flag && step >= 0; - const int header_step = std::max(step, 0); + // Append for any multi-step run (MD or relax) in append mode; + // single-point SCF passes step = -1 and starts a fresh file. + const bool open_in_append = (out_app_flag == 1) && step >= 0; + // ionic step printed one-based per governance rule 22 + const int header_step = std::max(step, 0) + 1; ModuleIO::detail::assemble_csr(ssr.str(), tem1.str(), header_step, diff --git a/source/source_io/module_hs/pos_op_writer.h b/source/source_io/module_hs/pos_op_writer.h index 1fd22bcfc81..d2996e43890 100644 --- a/source/source_io/module_hs/pos_op_writer.h +++ b/source/source_io/module_hs/pos_op_writer.h @@ -35,7 +35,7 @@ class PosOpWriter const std::string& global_out_dir, const std::string& global_matrix_dir, const std::string& calculation, - bool out_app_flag, + int out_app_flag, int nlocal, int npol, double sparse_threshold, diff --git a/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp b/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp index 3885c7c9472..085f92a25c3 100644 --- a/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp +++ b/source/source_io/module_hs/unittests/test_dhs_sparse_writer.cpp @@ -40,11 +40,12 @@ TEST(DhsSparseWriter, TextCountsOnlyValuesAboveThreshold) arrays.dHRx_sparse[0][r_vector][1][0] = 0.0; arrays.dHRx_sparse[0][r_vector][1][1] = -2.0; - ModuleIO::save_dH_sparse(5, pv, arrays, 1e-10, false, "h", 8, "./", "./", "scf", false, 1, 2, ofs_running); + ModuleIO::save_dH_sparse(5, pv, arrays, 1e-10, false, "h", 8, "./", "scf", false, 1, 2, ofs_running); const std::vector lines = read_lines("dhrxs1g6_nao.csr"); ASSERT_GE(lines.size(), 7); - EXPECT_EQ(lines[0], "STEP: 5"); + // istep = 5 is the sixth ionic step, printed one-based as 6 + EXPECT_EQ(lines[0], "STEP: 6"); EXPECT_EQ(lines[1], "Matrix Dimension of dHx(R): 2"); EXPECT_EQ(lines[2], "Matrix number of dHx(R): 1"); EXPECT_EQ(lines[3], "0 0 0 2"); @@ -91,11 +92,12 @@ TEST(DhsSparseWriter, BinaryCountsOnlyValuesAboveThreshold) arrays.dHRx_sparse[0][r_vector][1][0] = 0.0; arrays.dHRx_sparse[0][r_vector][1][1] = -2.0; - ModuleIO::save_dH_sparse(6, pv, arrays, 1e-10, true, "h", 8, "./", "./", "scf", false, 1, 2, ofs_running); + ModuleIO::save_dH_sparse(6, pv, arrays, 1e-10, true, "h", 8, "./", "scf", false, 1, 2, ofs_running); std::ifstream ifs("dhrxs1g7_nao.csr", std::ios::binary); ASSERT_TRUE(ifs.is_open()); - EXPECT_EQ(read_binary_value(ifs), 6); + // istep = 6 -> one-based step 7 in the binary header + EXPECT_EQ(read_binary_value(ifs), 7); EXPECT_EQ(read_binary_value(ifs), 2); EXPECT_EQ(read_binary_value(ifs), 1); EXPECT_EQ(read_binary_value(ifs), 0); @@ -127,7 +129,7 @@ TEST(DhsSparseWriter, SocWritesAllDirections) arrays.dHRy_soc_sparse[r_vector][0][1] = std::complex(2.0, -1.0); arrays.dHRz_soc_sparse[r_vector][1][1] = std::complex(-3.0, 0.5); - ModuleIO::save_dH_sparse(7, pv, arrays, 1e-10, false, "s", 8, "./", "./", "scf", false, 4, 2, ofs_running); + ModuleIO::save_dH_sparse(7, pv, arrays, 1e-10, false, "s", 8, "./", "scf", false, 4, 2, ofs_running); const std::string x_output = read_file("dsrxs1g8_nao.csr"); const std::string y_output = read_file("dsrys1g8_nao.csr"); diff --git a/source/source_io/module_hs/unittests/test_hs_dense_io.cpp b/source/source_io/module_hs/unittests/test_hs_dense_io.cpp index 31410679def..0a54bcdf2f3 100644 --- a/source/source_io/module_hs/unittests/test_hs_dense_io.cpp +++ b/source/source_io/module_hs/unittests/test_hs_dense_io.cpp @@ -378,6 +378,44 @@ TEST(HsDenseIoSaveMat, BinaryAppendCompleteRecordsAndOverwrite) remove_test_file(filename); } +TEST(HsDenseIoSaveMat, AppendFalseOverwritesSameIstep) +{ + /// Protects against regression where an int-mode value (out_app_flag) is + /// passed to a bool append parameter. The older BinaryAppend test covers + /// cross-stepping; this one exercises the same-istep overwrite path. + const int dim = 2; + const std::string filename = "save_mat_overwrite_same_step.dat"; + remove_test_file(filename); + + Parallel_Orbitals pv; + initialize_distribution(pv, dim); + const std::vector first = {1.0, 2.0, 3.0, 4.0}; + const std::vector replacement = {9.0, 10.0, 11.0, 12.0}; + const std::vector first_local = distribute_matrix(pv, first, dim); + const std::vector replacement_local + = distribute_matrix(pv, replacement, dim); + + // Step 1: append=true writes the full record for istep=0. + ModuleIO::save_mat(0, first_local.data(), dim, true, 8, true, true, + filename, pv, test_rank, "cg"); + + // Step 2: append=false on the same istep must truncate the file, not + // append a second copy. + ModuleIO::save_mat(0, replacement_local.data(), dim, true, 8, true, false, + filename, pv, test_rank, "cg"); + + if (test_rank == 0) + { + std::ifstream ifs(filename.c_str(), std::ios::binary); + ASSERT_TRUE(ifs.is_open()); + // Exactly one record, containing the replacement values. + EXPECT_EQ(read_record(ifs, dim), upper_triangle(replacement, dim)); + EXPECT_EQ(ifs.peek(), std::ifstream::traits_type::eof()); + } + + remove_test_file(filename); +} + // --------------------------------------------------------------------------- // Main: initialize MPI before running tests (required by save_mat's MPI path) // --------------------------------------------------------------------------- diff --git a/source/source_io/module_hs/unittests/test_hs_sparse_io.cpp b/source/source_io/module_hs/unittests/test_hs_sparse_io.cpp index d3bd9fb8495..41544d286d2 100644 --- a/source/source_io/module_hs/unittests/test_hs_sparse_io.cpp +++ b/source/source_io/module_hs/unittests/test_hs_sparse_io.cpp @@ -41,7 +41,8 @@ TEST(HsSparseIo, SparseHeaderKeepsStepStyle) ModuleIO::save_sparse(sparse_matrix, all_R_coor, pv, options); const std::string output = read_file(filename); - EXPECT_TRUE(starts_with(output, "STEP: 0\n")); + // istep = 0 is the first ionic step, printed one-based as 1 + EXPECT_TRUE(starts_with(output, "STEP: 1\n")); EXPECT_THAT(output, testing::HasSubstr("Matrix Dimension of S(R): 2\n")); EXPECT_THAT(output, testing::HasSubstr("Matrix number of S(R): 1\n")); EXPECT_THAT(output, testing::HasSubstr("0 0 0 2\n")); @@ -77,7 +78,8 @@ TEST(HsSparseIo, SparseTextCountsOnlyValuesAboveThreshold) const std::vector lines = read_lines(filename); ASSERT_GE(lines.size(), 6); - EXPECT_EQ(lines[0], "STEP: 2"); + // istep = 2 is the third ionic step, printed one-based as 3 + EXPECT_EQ(lines[0], "STEP: 3"); EXPECT_EQ(lines[1], "Matrix Dimension of S(R): 2"); EXPECT_EQ(lines[2], "Matrix number of S(R): 1"); EXPECT_EQ(lines[3], "0 0 0 2"); @@ -137,8 +139,9 @@ TEST(HsSparseIo, SparseBinaryHeaderWritesConcreteStep) options.reduce = false; ModuleIO::save_sparse(sparse_matrix, all_R_coor, pv, options); + // istep = 3 -> one-based step 4 in the binary header const std::vector header_and_r = read_binary_ints(filename, 7); - EXPECT_THAT(header_and_r, testing::ElementsAre(3, 2, 1, 0, 0, 0, 2)); + EXPECT_THAT(header_and_r, testing::ElementsAre(4, 2, 1, 0, 0, 0, 2)); std::remove(filename.c_str()); } @@ -171,7 +174,8 @@ TEST(HsSparseIo, SparseBinaryCountsOnlyValuesAboveThreshold) std::ifstream ifs(filename.c_str(), std::ios::binary); ASSERT_TRUE(ifs.is_open()); - EXPECT_EQ(read_binary_value(ifs), 4); + // istep = 4 -> one-based step 5 in the binary header + EXPECT_EQ(read_binary_value(ifs), 5); EXPECT_EQ(read_binary_value(ifs), 2); EXPECT_EQ(read_binary_value(ifs), 1); EXPECT_EQ(read_binary_value(ifs), 0); diff --git a/source/source_io/module_hs/unittests/test_hsk_writer.cpp b/source/source_io/module_hs/unittests/test_hsk_writer.cpp index a812efb77f4..2cfea1b46f4 100644 --- a/source/source_io/module_hs/unittests/test_hsk_writer.cpp +++ b/source/source_io/module_hs/unittests/test_hsk_writer.cpp @@ -436,6 +436,93 @@ TEST(WriteHsk, WritesComplexKMatrix) remove_test_file("write_hsk_running_" + std::to_string(test_rank) + ".log"); } +// --------------------------------------------------------------------------- +// Regression guard: out_app_flag = 2 must write non-append records. +// The old code passed the integer out_app_flag directly to save_mat's +// boolean append argument, so a rerun in mode 2 would silently grow the +// file instead of replacing the snapshot. This test sets out_app_flag=2, +// writes one snapshot, swaps the FakeHamilt buffers, writes the same step +// again, and verifies the file contains exactly one record holding the +// second payload. +// --------------------------------------------------------------------------- + +TEST(WriteHsk, Mode2OverwritesSameIstep) +{ + const int dim = 2; + const int nspin = 1; + const int nks = 1; + const int nkstot = 1; + const std::vector ik2iktot = {0}; + const std::vector isk = {0}; + const bool gamma_only = false; + const int istep = 0; + const int out_app_flag = 2; // mode 2 => overwrite semantics + const int out_type = 2; // binary + const int precision = 8; + + Parallel_Orbitals pv; + initialize_distribution(pv, dim); + + FakeHamilt fake; + fake.dim = dim; + + // Round 1 payload: H = all 1.0, S = all -1.0 + std::vector hk_first(dim * dim, 1.0); + std::vector sk_first(dim * dim, -1.0); + fake.set_k_matrix(0, + distribute_matrix(pv, hk_first, dim), + distribute_matrix(pv, sk_first, dim)); + + // filename_output with out_app_flag=2 + istep=0 yields hk{ik}g{step+1}_nao.dat + const std::string h_fn = ModuleIO::filename_output( + "", "hk", "nao", 0, ik2iktot, nspin, nkstot, out_type, + out_app_flag, gamma_only, istep); + const std::string s_fn = ModuleIO::filename_output( + "", "sk", "nao", 0, ik2iktot, nspin, nkstot, out_type, + out_app_flag, gamma_only, istep); + remove_test_file(h_fn); + remove_test_file(s_fn); + + std::ofstream ofs = open_running_log(); + ModuleIO::write_hsk("", nspin, nks, nkstot, ik2iktot, isk, &fake, pv, + gamma_only, out_app_flag, istep, out_type, precision, dim, + "cg", test_rank, ofs); + + // Round 2 payload: different data, same out_app_flag and istep. + std::vector hk_second(dim * dim, 9.0); + std::vector sk_second(dim * dim, -9.0); + fake.set_k_matrix(0, + distribute_matrix(pv, hk_second, dim), + distribute_matrix(pv, sk_second, dim)); + +#ifdef __MPI + MPI_Barrier(DIAG_WORLD); +#endif + ModuleIO::write_hsk("", nspin, nks, nkstot, ik2iktot, isk, &fake, pv, + gamma_only, out_app_flag, istep, out_type, precision, dim, + "cg", test_rank, ofs); + + if (test_rank == 0) + { + std::ifstream hfs(h_fn.c_str(), std::ios::binary); + ASSERT_TRUE(hfs.is_open()) << h_fn; + // Exactly one record — second payload — not two records stacked. + EXPECT_EQ(read_record(hfs, dim), upper_triangle(hk_second, dim)); + EXPECT_EQ(hfs.peek(), std::ifstream::traits_type::eof()) + << "hk file should contain one overwrite record, not two"; + + std::ifstream sfs(s_fn.c_str(), std::ios::binary); + ASSERT_TRUE(sfs.is_open()) << s_fn; + EXPECT_EQ(read_record(sfs, dim), upper_triangle(sk_second, dim)); + EXPECT_EQ(sfs.peek(), std::ifstream::traits_type::eof()); + } + + remove_test_file(h_fn); + remove_test_file(s_fn); + ofs.close(); + remove_test_file("write_hsk_running_" + std::to_string(test_rank) + ".log"); +} + int main(int argc, char** argv) { #ifdef __MPI diff --git a/source/source_io/module_hs/unittests/test_hsr_writer.cpp b/source/source_io/module_hs/unittests/test_hsr_writer.cpp index a5f86e7c74e..d660e852f04 100644 --- a/source/source_io/module_hs/unittests/test_hsr_writer.cpp +++ b/source/source_io/module_hs/unittests/test_hsr_writer.cpp @@ -300,7 +300,8 @@ TEST(HsrWriterIo, HContainerBinaryWritesSortedAndEmptyRBlocks) std::ifstream ifs(filename.c_str(), std::ios::binary); ASSERT_TRUE(ifs.is_open()); const NativeDoubleRecord record = read_native_double_record(ifs); - EXPECT_EQ(record.step, 0); + // istep = -1 (single-point SCF) maps to one-based step 1 + EXPECT_EQ(record.step, 1); EXPECT_EQ(record.nbasis, 2); ASSERT_EQ(record.blocks.size(), 2); EXPECT_EQ(record.blocks[0].rx, -1); @@ -340,7 +341,8 @@ TEST(HsrWriterIo, HContainerBinaryWritesComplexValuesAsDoublePairs) std::ifstream ifs(filename.c_str(), std::ios::binary); ASSERT_TRUE(ifs.is_open()); - EXPECT_EQ(read_binary_value(ifs), 4); + // istep = 4 -> one-based step 5 in the binary header + EXPECT_EQ(read_binary_value(ifs), 5); EXPECT_EQ(read_binary_value(ifs), 2); EXPECT_EQ(read_binary_value(ifs), 1); EXPECT_EQ(read_binary_value(ifs), 0); @@ -377,15 +379,16 @@ TEST(HsrWriterIo, HContainerBinaryAppendsAndCanOverwrite) std::ifstream appended(filename.c_str(), std::ios::binary); ASSERT_TRUE(appended.is_open()); - EXPECT_EQ(read_native_double_record(appended).step, 0); + // istep 0/1/2 -> one-based steps 1/2/3 EXPECT_EQ(read_native_double_record(appended).step, 1); + EXPECT_EQ(read_native_double_record(appended).step, 2); EXPECT_EQ(appended.peek(), std::ifstream::traits_type::eof()); appended.close(); ModuleIO::write_hcontainer_csr_binary(filename, &matrix, 2, false); std::ifstream replaced(filename.c_str(), std::ios::binary); ASSERT_TRUE(replaced.is_open()); - EXPECT_EQ(read_native_double_record(replaced).step, 2); + EXPECT_EQ(read_native_double_record(replaced).step, 3); EXPECT_EQ(replaced.peek(), std::ifstream::traits_type::eof()); std::remove(filename.c_str()); diff --git a/source/source_io/module_hs/vxc_op_lip.cpp b/source/source_io/module_hs/vxc_op_lip.cpp index fcee73b6cd4..094015992d4 100644 --- a/source/source_io/module_hs/vxc_op_lip.cpp +++ b/source/source_io/module_hs/vxc_op_lip.cpp @@ -197,7 +197,7 @@ void write_Vxc_LIP(int nspin, // add-up and write const int istep = -1; const int out_label = 1; // 1 means .txt while 2 means .dat - const bool out_app_flag = 0; + const int out_app_flag = 0; std::string vxc_file = ModuleIO::filename_output( global_out_dir, "vxc", "nao", ik, kv.ik2iktot, nspin, kv.get_nkstot(), diff --git a/source/source_io/module_hs/vxc_op_mat.cpp b/source/source_io/module_hs/vxc_op_mat.cpp index c7a0b8fe2b4..a94ad96112c 100644 --- a/source/source_io/module_hs/vxc_op_mat.cpp +++ b/source/source_io/module_hs/vxc_op_mat.cpp @@ -115,7 +115,8 @@ void write_Vxc(const int nspin, // write const int istep = -1; const int out_label = 1; // 1 means .txt while 2 means .dat - const bool out_app_flag = 0; + const int out_app_flag = 0; + const bool append_vxc = (out_app_flag == 1); std::string vxc_file = ModuleIO::filename_output( global_out_dir, "vxc", "nao", ik, kv.ik2iktot, nspin, kv.get_nkstot(), @@ -127,7 +128,7 @@ void write_Vxc(const int nspin, false /*binary*/, out_ndigits, true /*triangle*/, - out_app_flag /*append*/, + append_vxc, vxc_file, p2d, drank, @@ -136,12 +137,12 @@ void write_Vxc(const int nspin, if (GlobalV::MY_RANK == 0) { - write_orb_energy(kv, nspin0, nbands, e_orb_tot, "vxc", "", global_out_dir); + write_orb_energy(kv, nspin0, nbands, e_orb_tot, "vxc.txt", "", global_out_dir); #ifdef __EXX if (cal_exx) { - write_orb_energy(kv, nspin0, nbands, e_orb_locxc, "vxc", "local", global_out_dir); - write_orb_energy(kv, nspin0, nbands, e_orb_exx, "vxc", "exx", global_out_dir); + write_orb_energy(kv, nspin0, nbands, e_orb_locxc, "vxc_local.txt", "", global_out_dir); + write_orb_energy(kv, nspin0, nbands, e_orb_exx, "vxc_exx.txt", "", global_out_dir); } #endif } diff --git a/source/source_io/module_hs/vxc_op_tools.cpp b/source/source_io/module_hs/vxc_op_tools.cpp index 0862ae1557d..2b33eda76a3 100644 --- a/source/source_io/module_hs/vxc_op_tools.cpp +++ b/source/source_io/module_hs/vxc_op_tools.cpp @@ -102,19 +102,21 @@ void write_orb_energy(const K_Vectors& kv, const int nspin0, const int nbands, const std::vector>& e_orb, - const std::string& term, - const std::string& label, + const std::string& out_name, + const std::string& header, const std::string& global_out_dir, const bool app) { assert(e_orb.size() == kv.get_nks()); + assert(!out_name.empty()); const int nk = kv.get_nks() / nspin0; std::ofstream ofs; - const std::string out_name = term == "vxc" - ? (label.empty() ? "vxc.txt" : "vxc_" + label + ".txt") - : term + "_" + (label.empty() ? "out.dat" : label + "_out.dat"); ofs.open(global_out_dir + out_name, app ? std::ios::app : std::ios::out); + if (!header.empty()) + { + ofs << header; + } ofs << nk << "\n" << nspin0 << "\n" << nbands << "\n"; ofs << std::scientific << std::setprecision(16); for (int ik = 0; ik < nk; ++ik) diff --git a/source/source_io/module_hs/vxc_op_tools.h b/source/source_io/module_hs/vxc_op_tools.h index 4e66d632fb0..0aa66dceabc 100644 --- a/source/source_io/module_hs/vxc_op_tools.h +++ b/source/source_io/module_hs/vxc_op_tools.h @@ -80,16 +80,17 @@ double all_band_energy(const int ik, /// @param nspin0 Number of independent spin channels (1 or 2) /// @param nbands Number of bands /// @param e_orb Orbital energies per k-point -/// @param term Term label (e.g., "vxc", "kinetic") -/// @param label Additional label (e.g., "local", "exx") +/// @param out_name Output file name relative to the output directory +/// (e.g., "vxc.txt", "eband_vl.txt") +/// @param header Comment block written before the numeric data; empty to omit /// @param global_out_dir Output directory /// @param app Append mode flag void write_orb_energy(const K_Vectors& kv, const int nspin0, const int nbands, const std::vector>& e_orb, - const std::string& term, - const std::string& label, + const std::string& out_name, + const std::string& header, const std::string& global_out_dir, const bool app = false); diff --git a/source/source_io/module_output/cube_io.h b/source/source_io/module_output/cube_io.h index 6f554fef949..26496b8407c 100644 --- a/source/source_io/module_output/cube_io.h +++ b/source/source_io/module_output/cube_io.h @@ -52,6 +52,9 @@ bool read_vdata_palgrid(const Parallel_Grid& pgrid, const int nat); /// write volumetric data on the parallized grid into a .cube file +/// @param append append a complete frame to an existing cube file instead of +/// truncating it. Used by out_app_flag == 1, where successive ionic steps +/// reuse a fixed filename and each written step contributes one frame. void write_vdata_palgrid(const Parallel_Grid& pgrid, const double* const data, const int is, @@ -64,6 +67,7 @@ void write_vdata_palgrid(const Parallel_Grid& pgrid, const int out_fermi, const bool two_fermi, const bool reduce_all_pool, + const bool append, std::ofstream& ofs_running, const std::string& data_desc); @@ -84,6 +88,8 @@ bool read_cube(const std::string& file, std::vector& data); /// write a cube file +/// @param append append a complete frame to an existing file instead of +/// truncating it (out_app_flag == 1 multi-step trajectories). void write_cube(const std::string& file, const std::vector& comment, const int& natom, @@ -99,6 +105,7 @@ void write_cube(const std::string& file, const std::vector>& atom_pos, const std::vector& data, const int precision, + const bool append, const int ndata_line = 6); /** diff --git a/source/source_io/module_output/write_cube.cpp b/source/source_io/module_output/write_cube.cpp index 5c3d8e9af54..6fc55dc7954 100644 --- a/source/source_io/module_output/write_cube.cpp +++ b/source/source_io/module_output/write_cube.cpp @@ -23,6 +23,7 @@ void ModuleIO::write_vdata_palgrid(const Parallel_Grid& pgrid, const int out_fermi, const bool two_fermi, const bool reduce_all_pool, + const bool append, std::ofstream& ofs_running, const std::string& data_desc) { @@ -173,7 +174,8 @@ void ModuleIO::write_vdata_palgrid(const Parallel_Grid& pgrid, atom_charge, atom_pos, data_xyz_full, - precision); + precision, + append); ofs_running << " Write " << data_desc << " to file: " << fn << std::endl; @@ -199,6 +201,7 @@ void ModuleIO::write_cube(const std::string& file, const std::vector>& atom_pos, const std::vector& data, const int precision, + const bool append, const int ndata_line) { assert(comment.size() >= 2); @@ -222,7 +225,16 @@ void ModuleIO::write_cube(const std::string& file, assert(data.size() >= nx * ny * nz); - std::ofstream ofs(file); + // In append mode (out_app_flag == 1) each written ionic step contributes + // one complete cube frame; the previous frame always ends with a newline, + // so the appended frame starts on a fresh line. Otherwise the file is + // truncated and holds a single frame. + std::ios_base::openmode open_mode = std::ios::out | std::ios::trunc; + if (append) + { + open_mode = std::ios::out | std::ios::app; + } + std::ofstream ofs(file, open_mode); for (int i = 0; i < 2; ++i) { diff --git a/source/source_io/module_output/write_orb_info.cpp b/source/source_io/module_output/write_orb_info.cpp index 665e12e9f86..8f6117a6a9c 100644 --- a/source/source_io/module_output/write_orb_info.cpp +++ b/source/source_io/module_output/write_orb_info.cpp @@ -7,7 +7,7 @@ void ModuleIO::write_orb_info(const UnitCell* ucell) { std::stringstream os; - os << PARAM.globalv.global_out_dir << "Orbital"; + os << PARAM.globalv.global_out_dir << "nao.txt"; std::ofstream out(os.str().c_str()); out << std::setw(5) << "#io" << std::setw(8) << "spec" diff --git a/source/source_io/module_parameter/input_parameter.h b/source/source_io/module_parameter/input_parameter.h index 1c624aa5a02..6ee46bc9f5a 100644 --- a/source/source_io/module_parameter/input_parameter.h +++ b/source/source_io/module_parameter/input_parameter.h @@ -490,8 +490,9 @@ struct Input_para bool out_hr_npz = false; ///< output H(R) matrix in npz format bool out_hsr_npz = false; ///< output H(R) and S(R) matrices in npz format bool out_dm_npz = false; ///< output DM(R) matrix in npz format - bool out_app_flag = true; ///< whether output r(R), H(R), S(R), T(R), and dH(R) matrices - ///< in an append manner during MD liuyu 2023-03-20 + int out_app_flag = 1; ///< 0: no append, no subdirectory; 1: append to same file; + ///< 2: write to subdirectory (PSI/, CHG/, POT/, ELF/, DOS/, DIP/, MAT/) + ///< liuyu 2023-03-20, modified 2025-10-16 int out_ndigits = 8; ///< Assuming 8 digits precision is needed for matrices output std::vector out_mat_t = {0, 8}; ///< output T(R) matrix with precision bool out_element_info = false; ///< output information of all elements diff --git a/source/source_io/module_parameter/read_inp_out.cpp b/source/source_io/module_parameter/read_inp_out.cpp index 80a55319466..d57d95074a6 100644 --- a/source/source_io/module_parameter/read_inp_out.cpp +++ b/source/source_io/module_parameter/read_inp_out.cpp @@ -91,7 +91,7 @@ void ReadInput::item_output() - nspin = 2: `chgs1g{geom_step}_ini.cube` and `chgs2g{geom_step}_ini.cube`; - nspin = 4: `chgs1g{geom_step}_ini.cube`, `chgs2g{geom_step}_ini.cube`, `chgs3g{geom_step}_ini.cube`, and `chgs4g{geom_step}_ini.cube`. - output every out_freq_ion steps - Here, {geom_step} denotes the geometry step index, starting from 1 (geom_step = istep + 1). + Here, {geom_step} denotes the geometry step index, starting from 1 (geom_step = istep + 1). With out_app_flag = 1, the geometry-step index is omitted and each snapshot overwrites the same fixed file. - -1: Disable the charge density auto-back-up file `{suffix}-CHARGE-DENSITY.restart`, useful for large systems. The second integer controls the precision of the charge density output. If not given, `3` is used as default. For restarting from this file and other high-precision calculations, `10` is recommended. @@ -152,7 +152,7 @@ In molecular dynamics simulations, the output frequency is controlled by out_fre * nspin = 2: `pots1g{geom_step}_ini.cube` and `pots2g{geom_step}_ini.cube`; * nspin = 4: `pots1g{geom_step}_ini.cube`, `pots2g{geom_step}_ini.cube`, `pots3g{geom_step}_ini.cube`, and `pots4g{geom_step}_ini.cube`. * output every out_freq_ion steps - Here, {geom_step} denotes the geometry step index, starting from 1 (geom_step = istep + 1). + Here, {geom_step} denotes the geometry step index, starting from 1 (geom_step = istep + 1). With out_app_flag = 1, the geometry-step index is omitted and each snapshot overwrites the same fixed file. The optional second integer controls the output precision. If not provided, the default precision is 8. @@ -271,9 +271,9 @@ Available values are: The file-name pattern is `wfk{k}[s{spin}][g{geometry step}][e{electronic iteration}]_pw.txt` for `out_wfc_pw=1` and `wfk{k}[s{spin}][g{geometry step}][e{electronic iteration}]_pw.dat` for `out_wfc_pw=2`. All PW output files include a `k*` label, including Gamma-only calculations. Without geometry-step or electronic-iteration indices, representative names are `wfk1_pw.txt` or `wfk1_pw.dat` for `nspin=1`, `wfk1s1_pw.txt` and `wfk1s2_pw.txt` or their `.dat` equivalents for `nspin=2`, and `wfk1s4_pw.txt` or `wfk1s4_pw.dat` for `nspin=4`. -With `out_freq_ion=0`, files are written only when the electronic calculation converges or reaches `scf_nmax`; no `g*` or `e*` index is added. During structural relaxation or molecular dynamics, later ionic steps overwrite the same unindexed files. With `out_freq_ion` > 0, output is restricted to the ionic steps selected by `out_freq_ion` and is written when the electronic iteration is a multiple of `out_freq_elec`, when the calculation converges, or when it reaches `scf_nmax`. Both `g*` and `e*` indices are then added, including for a static `calculation=scf` or `calculation=nscf` run. +With `out_freq_ion=0`, files are written only when the electronic calculation converges or reaches `scf_nmax`; no `g*` or `e*` index is added. During structural relaxation or molecular dynamics with `out_app_flag=1`, the first written step truncates the unindexed files and later ionic steps append one frame per step (mirroring `out_wfc_lcao`); with `out_app_flag=0` the unindexed files are overwritten each step. With `out_freq_ion` > 0, output is restricted to the ionic steps selected by `out_freq_ion` and is written when the electronic iteration is a multiple of `out_freq_elec`, when the calculation converges, or when it reaches `scf_nmax`. Both `g*` and `e*` indices are then added (except that `g*` is omitted when `out_app_flag=1`), including for a static `calculation=scf` or `calculation=nscf` run. -With `init_wfc file binary`, ABACUS reads only unindexed binary `wf*_pw.dat` files from `read_file_dir`. Such directly reusable files are normally generated with `out_wfc_pw=2` and `out_freq_ion=0`. Text `wf*_pw.txt` files and files containing `g*` or `e*` indices are not matched automatically. +With `init_wfc file binary`, ABACUS reads only unindexed binary `wf*_pw.dat` files from `read_file_dir`, starting from the first frame. Such directly reusable files are normally generated with `out_wfc_pw=2` and `out_freq_ion=0`; multi-frame files accumulated under `out_app_flag=1` are trajectories and only their first frame is visible to the reader. Text `wf*_pw.txt` files and files containing `g*` or `e*` indices are not matched automatically. [NOTE] In the 3.10-LTS version, the binary files are named `WAVEFUNC1.dat`, `WAVEFUNC2.dat`, etc.)"; item.default_value = "0"; @@ -297,7 +297,7 @@ With `init_wfc file binary`, ABACUS reads only unindexed binary `wf*_pw.dat` fil item.annotation = "ouput LCAO wave functions, 0, no output 1: text, 2: binary"; item.category = "Output information"; item.type = "Integer"; - item.description = R"(Whether to output the electronic wavefunction coefficients into files and store them in the folder OUT.${suffix}. The files are named as wf{s}{spin index}{k(optional)}{k-point index}{g(optional)}{geometry index1}{_nao} + {".txt"/".dat"}. Here, 's' refers to spin, where s1 means spin up channel while s2 means spin down channel, and 's12' refer to spinor wave functions that contains both spin channels with spin-orbital coupling or noncollinear calculations enabled. In addition, if 'gamma_only' is set to 0, then the optinoal k-point sampling index appears with the k-point index attached to the electronic wave function file names. Finally, if out_app_flag is set to false, the file name contains the optional 'g' index for each ionic step that may have different geometries, and if out_app_flag is set to true, the wave functions accumulate during ionic steps. If the out_app_flag is set to false, a new folder named WFC will be created, and the wave function files will be saved into it. + item.description = R"(Whether to output the electronic wavefunction coefficients into files and store them in the folder OUT.${suffix} (or its PSI/ subdirectory when out_app_flag is 2). The files are named as wf{s}{spin index}{k(optional)}{k-point index}{g(optional)}{geometry index1}{_nao} + {".txt"/".dat"}. Here, 's' refers to spin, where s1 means spin up channel while s2 means spin down channel, and 's12' refer to spinor wave functions that contains both spin channels with spin-orbital coupling or noncollinear calculations enabled. In addition, if 'gamma_only' is set to 0, then the optinoal k-point sampling index appears with the k-point index attached to the electronic wave function file names. Finally, if out_app_flag is not 1, the file name contains the optional 'g' index for each ionic step that may have different geometries, and if out_app_flag is 1, the wave functions accumulate during ionic steps. When out_app_flag is 2, the wave function files are saved into the PSI/ subdirectory of OUT.${suffix}. * 0: no output * 1: (txt format) * gamma-only: wfs1_nao.txt or wfs2_nao.txt, ...; @@ -341,8 +341,9 @@ Also controled by out_freq_ion and out_app_flag. item.description = R"(Whether to output the density of states (DOS) and projected density of states (PDOS). For more information, refer to the dos.md. * 0: no output * 1: output the density of states (DOS) and projected density of states (PDOS, LCAO only) - * nspin=1 or 4: doss1g{geom}.txt and pdoss1g{geom}.txt (PDOS is LCAO only), where geom is the geometry index when cell changes or ions move. PW and LCAO calculations use the same naming. - * nspin=2: doss1/doss2 and pdoss1/pdoss2 files for two spin channels. + * With out_app_flag = 0 or 2: per-step files are named `doss{s}g{geom}.txt` and `pdoss{s}g{geom}.txt` where `{geom}` is the one-based geometry index (e.g. `doss1g3.txt` for the third ionic step). + * With out_app_flag = 1: fixed-name files (`doss{s}.txt`, `pdoss{s}.txt`) are reused; the first written step truncates them and later steps append one record per step (prefixed with a `# ionic step N` header, one-based). + * nspin=1 or 4: one spin channel (`s1`). nspin=2: two channels (`s1`, `s2`). PW and LCAO use the same naming. Note: values 2 and 3 are no longer supported. Setting out_dos to 2 or 3 will raise an error.)"; item.default_value = "0"; item.unit = ""; @@ -456,10 +457,10 @@ Note: values 2 and 3 are no longer supported. Setting out_dos to 2 or 3 will rai item.annotation = "output the structure files per ion step"; item.category = "Output information"; item.type = "Integer"; - item.description = "Controls the output of structure files per ionic step. The files are written to the OUT.{suffix}/ directory. Each file corresponds to the structure at RELAX STEP ${istep} (for scf/nscf this is the single step), i.e., the structure for which that step's energy was computed (before the relax move), and includes a header comment with the ABACUS version, timestamp, energy, and stress tensor. When out_freq_ion is positive, the numbered files STRU{istep} (or STRU{istep}.cif) are written every out_freq_ion steps during geometry relaxation; when out_freq_ion is 0, no numbered files are output. This parameter is effective for scf/nscf/relax/cell-relax; for scf/nscf only STRU_FINAL (or STRU_FINAL.cif) is written, and structure output is disabled by default unless out_stru is set explicitly. Molecular dynamics structure output is instead controlled by md_restartfreq (STRU_MD_*).\n" + item.description = "Controls the output of structure files per ionic step. The files are written to the OUT.{suffix}/ directory. Each file corresponds to the structure at the current ionic step, and includes a header comment with the ABACUS version, timestamp, energy, and stress tensor. When out_freq_ion is positive, the numbered files STRU{istep} (or STRU{istep}.cif) are written every out_freq_ion steps; when out_freq_ion is 0, no numbered files are output. This parameter is effective for scf/nscf/relax/cell-relax and md. For scf/nscf only STRU_FINAL (or STRU_FINAL.cif) is written. For MD, structure output follows the same out_freq_ion frequency as the restart file.\n" " - 0: No structure files are output.\n" - " - 1: ABACUS STRU format files are output. The latest structure is written to STRU_NOW (overwritten each step), the numbered file STRU{istep} (e.g., STRU1, STRU2) is written every out_freq_ion steps (when out_freq_ion is positive), and the final converged structure is written to STRU_FINAL. No CIF files are output.\n" - " - 2: CIF format files are output. The latest structure is written to STRU_NOW.cif (overwritten each step), the numbered file STRU{istep}.cif (e.g., STRU1.cif, STRU2.cif) is written every out_freq_ion steps (when out_freq_ion is positive), and the final converged structure is written to STRU_FINAL.cif. No non-CIF files are output.\n" + " - 1: ABACUS STRU format files are output. The latest structure is written to STRU_NOW (overwritten each step), the numbered file STRU{istep} (e.g., STRU1, STRU2) is written every out_freq_ion steps (when out_freq_ion is positive), and the final converged structure is written to STRU_FINAL. No CIF files are output. When out_app_flag is 1, the numbered STRU{istep} files are replaced by a single fixed-name STRU file that appends one structure block (with a one-based \"# Ionic Step N\" or \"# MD step N\" comment) per written step.\n" + " - 2: CIF format files are output. The latest structure is written to STRU_NOW.cif (overwritten each step), the numbered file STRU{istep}.cif (e.g., STRU1.cif, STRU2.cif) is written every out_freq_ion steps (when out_freq_ion is positive), and the final converged structure is written to STRU_FINAL.cif. No non-CIF files are output. When out_app_flag is 1, the numbered files are replaced by a fixed-name STRU.cif overwritten each written step.\n" "[NOTE] For backward compatibility, true/false (case insensitive) are accepted and converted to 1/0."; item.default_value = "1"; item.unit = ""; @@ -562,8 +563,8 @@ The output is also controlled by out_freq_ion and out_app_flag. For more informa * Multi-k, nspin = 2: hk1s1_nao.txt and hk1s2_nao.txt for the two spin channels of the Hamiltonian matrix, and sk1_nao.txt for the overlap matrix at the first k-point. Only one overlap matrix is written because it is identical for both spin channels. * Multi-k, nspin = 4: hk1s4_nao.txt for the spinor Hamiltonian matrix and sk1_nao.txt for the spinor overlap matrix at the first k-point. For binary output, the same names use the .dat suffix. Each native binary record contains the matrix dimension as an int followed by the row-major upper triangle. Gamma-only elements are doubles; multi-k and spinor elements are pairs of doubles containing the real and imaginary parts. Native integer representation and byte order are used. -When out_app_flag is true, the first ionic step truncates the file and later steps append complete records. -When out_app_flag is false, g followed by the one-based ionic-step index is inserted before _nao, for example hk1s1g1_nao.txt. +When out_app_flag is 1, the first ionic step truncates the file and later steps append complete records. +When out_app_flag is 0 or 2, g followed by the one-based ionic-step index is inserted before _nao, for example hk1s1g1_nao.txt. [NOTE] In the 3.10-LTS version, the file names are data-0-H and data-0-S, etc.)"; item.default_value = "0 8"; @@ -636,7 +637,7 @@ When out_app_flag is false, g followed by the one-based ionic-step index is inse For multi-k calculations, the output contains the individual real-space blocks stored for the Bravais lattice vectors R. For gamma-only calculations, the internal real-space contributions are folded into a single R = (0, 0, 0) block. This folded result cannot recover the original R-resolved contributions or interpolate arbitrary k points. Terms added only while constructing H(k) are not guaranteed to be present. -For binary output, each file uses the same basename as text output with a .dat suffix. Every native record contains the zero-based ionic step, matrix dimension, and number of R blocks as ints. Each R block contains three int coordinates, an int nonzero count, native double values (real/imaginary double pairs for complex matrices), int column indices, and long long row pointers. Native integer representation and byte order are used. When out_app_flag is true, the first ionic step truncates the file and later steps append complete records. +For binary output, each file uses the same basename as text output with a .dat suffix. Every native record contains the one-based ionic step, matrix dimension, and number of R blocks as ints. Each R block contains three int coordinates, an int nonzero count, native double values (real/imaginary double pairs for complex matrices), int column indices, and long long row pointers. Native integer representation and byte order are used. When out_app_flag is true, the first ionic step truncates the file and later steps append complete records. [NOTE] In the 3.10-LTS version, the file names are data-HR-sparse_SPIN0.csr and data-SR-sparse_SPIN0.csr, etc.)"; item.default_value = "0 8"; @@ -1296,7 +1297,7 @@ The circle order of the charge density on real space grids is: x is the outer lo item.annotation = "output the band energy terms separately"; item.category = "Output information"; item.type = "Boolean"; - item.description = "Whether to print the band energy terms separately in the file OUT.{term}_out.dat. The terms include the kinetic, pseudopotential (local + nonlocal), Hartree and exchange-correlation (including exact exchange if calculated)."; + item.description = "Whether to print the band-decomposed energy of each Hamiltonian term in the files eband_kin.txt, eband_vl.txt, eband_vnl.txt, eband_hart.txt and vxc.txt under the output directory. Each file starts with comment lines giving the ABACUS version, the timestamp, the ionic step and total energy, the expectation-value formula, and the units. The terms include the kinetic, pseudopotential (local + nonlocal), Hartree and exchange-correlation (including exact exchange if calculated)."; item.default_value = "False"; item.unit = ""; item.set_availability("basis_type==lcao"); @@ -1391,15 +1392,47 @@ The circle order of the charge density on real space grids is: x is the outer lo } { Input_Item item("out_app_flag"); - item.annotation = "whether output r(R), H(R), S(R), T(R), and dH(R) " - "matrices in an append manner during MD"; + item.annotation = "output directory layout and append behavior " + "during multi-step calculations (0/1/2)"; item.category = "Output information"; - item.type = "Boolean"; - item.description = "Whether to output r(R), H(R), S(R), T(R), dH(R), dS(R), and wfc matrices in an append manner during molecular dynamics calculations. Check input parameters out_mat_r, out_hsr, out_mat_t, out_mat_dh, out_hsk and out_wfc_lcao for more information."; - item.default_value = "true"; + item.type = "Integer"; + item.description = R"(Controls output file organization during multi-step calculations (MD, relax, RT-TDDFT). The directory routing applies to both LCAO and PW basis calculations. +* 0: Write to OUT directory directly, no append (each step's file carries a g{step} suffix) +* 1: Reuse fixed filenames across steps (default, backward compatible): text outputs append new records, cube outputs accumulate one frame per written step +* 2: Write to subdirectory (PSI/, CHG/, POT/, ELF/, DOS/, DIP/, MAT/), files carry a g{step} suffix +The legacy Boolean spellings (true/false, yes/no, on/off, t/f, y/n, and the dotted forms) are still accepted and map to modes 1/0 as before. +For more information, refer to the documentation of out_mat_r, out_hsr, out_mat_t, out_mat_dh, out_hsk, out_wfc_lcao, out_chg, out_pot, out_elf, out_dos, out_dipole, out_stru.)"; + item.default_value = "1"; item.unit = ""; - item.set_availability("basis_type==lcao and gamma_only==0"); - read_sync_bool(input.out_app_flag); + // out_app_flag applies to both LCAO and PW bases, and gamma-only runs + // follow the same directory routing rules as multi-k runs. An empty + // availability expression means "always available". + item.set_availability(""); + // Keep the previously accepted Boolean spellings working: the legacy + // parser treated out_app_flag as a Boolean, so INPUT files containing + // "true"/"false"/"yes"/"no"/"on"/"off" (case-insensitive) must keep + // parsing instead of reaching std::stoi and throwing. + item.read_value = [](const Input_Item& item, Parameter& para) { + try + { + para.input.out_app_flag = std::stoi(item.str_values[0]); + } + catch (const std::invalid_argument&) + { + para.input.out_app_flag = assume_as_boolean(item.str_values[0]) ? 1 : 0; + } + catch (const std::out_of_range&) + { + para.input.out_app_flag = assume_as_boolean(item.str_values[0]) ? 1 : 0; + } + }; + sync_int(input.out_app_flag); + item.check_value = [](const Input_Item& item, const Parameter& para) { + if (para.input.out_app_flag < 0 || para.input.out_app_flag > 2) + { + ModuleBase::WARNING_QUIT("ReadInput", "out_app_flag should be 0, 1, or 2"); + } + }; this->add_item(item); } { @@ -1632,6 +1665,8 @@ Here $\tilde{\psi}$ is the pseudo-wavefunction, $\beta_i^I$ are the atomic proje When `out_freq_ion > 0`, a geometry step suffix `g{#}` is appended to the file names (e.g., `elftotg1.cube`, `elfs1g1.cube`). +With `out_app_flag=1` the geometry step suffix is omitted and the same ELF files accumulate one complete cube frame per written ionic step (`Ionic_Step {#}` as the first comment line, one-based); the first written step truncates them. With `out_app_flag=0` every step writes its own suffixed file, and with `out_app_flag=2` the frames are routed to the `ELF/` subdirectory. + The second integer controls the precision of the kinetic energy density output, if not given, will use 3 as default. For purpose restarting from this file and other high-precision involved calculation, recommend to use 10. In molecular dynamics calculations, the output frequency is controlled by out_freq_ion.)"; diff --git a/source/source_io/module_parameter/read_input.cpp b/source/source_io/module_parameter/read_input.cpp index 2de46279d8b..f87002d843a 100644 --- a/source/source_io/module_parameter/read_input.cpp +++ b/source/source_io/module_parameter/read_input.cpp @@ -13,6 +13,7 @@ #include #include #include "source_base/formatter.h" +#include "source_base/fs_compat.h" #include "source_base/global_file.h" #include "source_base/global_function.h" #include "source_base/tool_quit.h" @@ -276,7 +277,7 @@ void ReadInput::create_directory(const Parameter& param) // NAME : Run::make_dir( dir name : OUT.suffix) //---------------------------------------------------------- bool out_dir = false; - if (!param.input.out_app_flag + if (param.input.out_app_flag == 0 && ((param.input.out_hsr[0] == 1 || param.input.out_hsr[0] == 2) || param.input.out_mat_r[0] || param.input.out_mat_t[0] || param.input.out_mat_dh[0] || param.input.out_mat_ds[0])) @@ -284,7 +285,7 @@ void ReadInput::create_directory(const Parameter& param) out_dir = true; } bool out_wfc_dir = false; - if (param.input.out_wfc_lcao && !param.input.out_app_flag) + if (param.input.out_wfc_lcao && param.input.out_app_flag == 0) { out_wfc_dir = true; } @@ -298,7 +299,6 @@ void ReadInput::create_directory(const Parameter& param) param.input.mdp.md_restart, param.input.out_alllog, param.globalv.global_out_dir, - param.globalv.global_stru_dir, param.globalv.global_matrix_dir, param.globalv.global_wfc_dir, param.globalv.global_mlkedf_descriptor_dir, @@ -306,6 +306,47 @@ void ReadInput::create_directory(const Parameter& param) param.globalv.log_file, param.input.of_ml_gene_data, param.input.deepks_out_freq_elec > 0); // xiaohui add 2013-09-01 + + // Create unified subdirectories when out_app_flag == 2, only for outputs the user actually requested + if (param.input.out_app_flag == 2) + { + const std::vector> subdir_flags = { + {param.input.out_stru > 0, param.globalv.global_stru_dir}, + {param.input.out_wfc_lcao > 0 || param.input.out_wfc_pw > 0, param.globalv.global_psi_dir}, + {param.input.out_chg[0] > 0, param.globalv.global_chg_dir}, + {param.input.out_pot[0] > 0, param.globalv.global_pot_dir}, + {param.input.out_elf[0] > 0, param.globalv.global_elf_dir}, + {param.input.out_dos > 0 + || (!param.input.out_ldos.empty() && param.input.out_ldos[0] > 0), + param.globalv.global_dos_dir}, + {param.input.out_dipole, param.globalv.global_dip_dir}, + {param.input.out_dmk[0] > 0 || param.input.out_dmr[0] > 0 || param.input.out_hsk[0] > 0 + || param.input.out_mat_hs2[0] > 0 || param.input.out_mat_r[0] > 0 || param.input.out_mat_t[0] > 0 + || param.input.out_mat_tk[0] > 0 || param.input.out_mat_l[0] > 0 + || param.input.out_mat_dh[0] > 0 || param.input.out_mat_ds[0] > 0 + || param.input.out_mat_xc || param.input.out_mat_xc2[0] > 0 || param.input.out_hr_npz + || param.input.out_hsr_npz || param.input.out_dm_npz + || param.input.out_mat_h_t[0] > 0 || param.input.out_mat_h_vnl[0] > 0 || param.input.out_mat_h_vl[0] > 0 + || param.input.out_mat_h_vh[0] > 0 || param.input.out_mat_h_vxc[0] > 0 || param.input.out_mat_h_exx[0] > 0 + || param.input.out_mat_dh_t[0] > 0 || param.input.out_mat_dh_vl[0] > 0 || param.input.out_mat_dh_vnl[0] > 0 + || param.input.out_mat_dh_vh[0] > 0 || param.input.out_mat_dh_vxc[0] > 0, + param.globalv.global_mat_dir} + }; + for (const auto& flag_dir : subdir_flags) + { + const bool need_create = flag_dir.first; + const std::string& dir = flag_dir.second; + if (need_create && !dir.empty() && dir != param.globalv.global_out_dir) + { + int ret = ModuleBase::make_directory(dir); + if (ret != 0 && errno != EEXIST) + { + std::cout << " CAN NOT MAKE THE DIR: " << dir << std::endl; + } + } + } + } + //const std::string ss = "test -d " + PARAM.inp.read_file_dir; struct stat st; if (stat(PARAM.inp.read_file_dir.c_str(), &st) != 0 || !S_ISDIR(st.st_mode)) diff --git a/source/source_io/module_parameter/read_input_item_md.cpp b/source/source_io/module_parameter/read_input_item_md.cpp index d5f6dc6bd67..eb6ac3db167 100644 --- a/source/source_io/module_parameter/read_input_item_md.cpp +++ b/source/source_io/module_parameter/read_input_item_md.cpp @@ -169,7 +169,9 @@ Note that md_tlast is only used in NVT/NPT simulations. If md_tlast is unset or item.annotation = "The period to output MD restart information"; item.category = "Molecular dynamics"; item.type = "Integer"; - item.description = "The output frequency of OUT.{suffix}/STRU_MD_*, which are used to restart molecular dynamics calculations, see md_restart in detail. Set to 0 to disable MD restart output."; + item.description = "Deprecated: use out_freq_ion to control MD output frequency instead. " + "This parameter is kept for backward compatibility as a fallback when " + "out_freq_ion is not set (0)."; item.default_value = "5"; item.unit = ""; read_sync_int(input.mdp.md_restartfreq); diff --git a/source/source_io/module_parameter/read_set_globalv.cpp b/source/source_io/module_parameter/read_set_globalv.cpp index 3b395e6b8f5..5a74f8b41b8 100644 --- a/source/source_io/module_parameter/read_set_globalv.cpp +++ b/source/source_io/module_parameter/read_set_globalv.cpp @@ -3,6 +3,35 @@ #include "source_base/global_variable.h" #include "source_base/tool_quit.h" #include "source_io/module_parameter/parameter.h" + +namespace +{ +/// @brief Find the structure file for restarting a molecular dynamics run. +/// @param stru_dir directory where the previous run wrote its structure files +/// @param istep number of completed ionic steps recorded in Restart_md.txt +/// @return path of the first existing structure file, empty if none exists +/// @details Two naming schemes are accepted, in order: +/// - "STRU{istep + 1}": written by run_md since the ionic-step numbering +/// was unified with relax (1-based); +/// - "STRU_MD_{istep}": written by older ABACUS versions. +std::string find_md_restart_stru(const std::string& stru_dir, const int istep) +{ + const std::string stru_new = stru_dir + "STRU" + std::to_string(istep + 1); + std::ifstream file_new(stru_new.c_str()); + if (file_new.good()) + { + return stru_new; + } + const std::string stru_old = stru_dir + "STRU_MD_" + std::to_string(istep); + std::ifstream file_old(stru_old.c_str()); + if (file_old.good()) + { + return stru_old; + } + return ""; +} +} // namespace + namespace ModuleIO { /// @note Here para.inp has been synchronized of all ranks. @@ -85,17 +114,36 @@ void ReadInput::set_global_dir(const Input_para& inp, System_para& sys) sys.global_out_dir = prefix + inp.suffix + "/"; sys.global_out_dir = to_dir(sys.global_out_dir); - /// get the global output directory - sys.global_stru_dir = sys.global_out_dir + "STRU/"; - sys.global_stru_dir = to_dir(sys.global_stru_dir); - - /// get the global output directory - sys.global_matrix_dir = sys.global_out_dir + "matrix/"; - sys.global_matrix_dir = to_dir(sys.global_matrix_dir); + /// Set subdirectories based on out_app_flag + /// out_app_flag == 2: use unified uppercase subdirectories + /// otherwise: keep backward-compatible behavior (all point to global_out_dir) + if (inp.out_app_flag == 2) + { + sys.global_stru_dir = sys.global_out_dir + "STRU/"; + sys.global_psi_dir = sys.global_out_dir + "PSI/"; + sys.global_chg_dir = sys.global_out_dir + "CHG/"; + sys.global_pot_dir = sys.global_out_dir + "POT/"; + sys.global_elf_dir = sys.global_out_dir + "ELF/"; + sys.global_dos_dir = sys.global_out_dir + "DOS/"; + sys.global_dip_dir = sys.global_out_dir + "DIP/"; + sys.global_mat_dir = sys.global_out_dir + "MAT/"; + } + else + { + sys.global_stru_dir = sys.global_out_dir; + sys.global_psi_dir = sys.global_out_dir; + sys.global_chg_dir = sys.global_out_dir; + sys.global_pot_dir = sys.global_out_dir; + sys.global_elf_dir = sys.global_out_dir; + sys.global_dos_dir = sys.global_out_dir; + sys.global_dip_dir = sys.global_out_dir; + sys.global_mat_dir = sys.global_out_dir; + } - /// get the global output directory - sys.global_wfc_dir = sys.global_out_dir + "WFC/"; - sys.global_wfc_dir = to_dir(sys.global_wfc_dir); + /// Map legacy directory names to new unified names + /// WFC -> PSI, matrix -> MAT + sys.global_wfc_dir = sys.global_psi_dir; + sys.global_matrix_dir = sys.global_mat_dir; /// get the global ML KEDF descriptor directory sys.global_mlkedf_descriptor_dir = sys.global_out_dir + "MLKEDF_Descriptors/"; @@ -112,15 +160,36 @@ void ReadInput::set_global_dir(const Input_para& inp, System_para& sys) /// get the stru file for md restart case if (inp.calculation == "md" && inp.mdp.md_restart) { - int istep = current_md_step(sys.global_readin_dir); + const int istep = current_md_step(sys.global_readin_dir); if (inp.read_file_dir == to_dir("OUT." + inp.suffix)) { - sys.global_in_stru = sys.global_stru_dir + "STRU_MD_" + std::to_string(istep); + // Restart from the default output directory: search the current + // STRU output directory, which follows out_app_flag. + const std::string stru_dir = sys.global_stru_dir; + sys.global_in_stru = find_md_restart_stru(stru_dir, istep); + if (sys.global_in_stru.empty()) + { + // Releases before the out_app_flag rework always wrote + // STRU_MD_{istep} into a fixed STRU/ subdirectory of the + // output directory; keep those outputs restartable. + const std::string legacy_dir = sys.global_out_dir + "STRU/"; + sys.global_in_stru = find_md_restart_stru(legacy_dir, istep); + } } else { - sys.global_in_stru = inp.read_file_dir + "STRU_MD_" + std::to_string(istep); + // Restart from a user-provided directory: the previous run may + // have written either naming scheme there. + const std::string stru_dir = inp.read_file_dir; + sys.global_in_stru = find_md_restart_stru(stru_dir, istep); + } + + if (sys.global_in_stru.empty()) + { + const std::string message = "no STRU file found for MD restart in " + sys.global_readin_dir + + "; expected STRU_MD_{step} or STRU{step+1}"; + ModuleBase::WARNING_QUIT("ReadInput::set_global_dir", message); } } else @@ -154,6 +223,13 @@ void ReadInput::set_global_dir(const Input_para& inp, System_para& sys) Parallel_Common::bcast_string(sys.global_stru_dir); Parallel_Common::bcast_string(sys.global_matrix_dir); Parallel_Common::bcast_string(sys.global_wfc_dir); + Parallel_Common::bcast_string(sys.global_psi_dir); + Parallel_Common::bcast_string(sys.global_chg_dir); + Parallel_Common::bcast_string(sys.global_pot_dir); + Parallel_Common::bcast_string(sys.global_elf_dir); + Parallel_Common::bcast_string(sys.global_dos_dir); + Parallel_Common::bcast_string(sys.global_dip_dir); + Parallel_Common::bcast_string(sys.global_mat_dir); Parallel_Common::bcast_string(sys.global_in_stru); #endif } diff --git a/source/source_io/module_parameter/system_parameter.h b/source/source_io/module_parameter/system_parameter.h index 438bd32c145..4705c33f4ea 100644 --- a/source/source_io/module_parameter/system_parameter.h +++ b/source/source_io/module_parameter/system_parameter.h @@ -53,8 +53,16 @@ struct System_para std::string global_stru_dir = ""; ///< global structure directory std::string global_matrix_dir = ""; ///< global matrix directory std::string global_wfc_dir = ""; ///< global wavefunction directory + // Subdirectories used when out_app_flag == 2 + std::string global_psi_dir = ""; ///< wavefunction directory (PSI/) + std::string global_chg_dir = ""; ///< charge density directory (CHG/) + std::string global_pot_dir = ""; ///< potential directory (POT/) + std::string global_elf_dir = ""; ///< ELF directory (ELF/) + std::string global_dos_dir = ""; ///< DOS directory (DOS/) + std::string global_dip_dir = ""; ///< dipole directory (DIP/) + std::string global_mat_dir = ""; ///< matrix directory (MAT/) std::string global_mlkedf_descriptor_dir = ""; ///< global ML KEDF descriptor directory - std::string global_deepks_label_elec_dir = ""; ///< global directory for DeePKS labels during electronic steps + std::string global_deepks_label_elec_dir = ""; ///< global directory for DeePKS labels during electronic steps std::string log_file = "log"; ///< log file name bool deepks_setorb = false; ///< true if "deepks" is set diff --git a/source/source_io/module_wf/get_wf_lcao.cpp b/source/source_io/module_wf/get_wf_lcao.cpp index 70b1e46784f..f982738b54b 100644 --- a/source/source_io/module_wf/get_wf_lcao.cpp +++ b/source/source_io/module_wf/get_wf_lcao.cpp @@ -76,7 +76,7 @@ void Get_wf_lcao::begin_gamma(const UnitCell& ucell, std::stringstream ss_out; ss_out << global_out_dir << ss_file.str(); - ModuleIO::write_vdata_palgrid(pgrid, wfc_norm.data(), is, nspin_, 0, ss_out.str(), 0.0, &ucell, precision, 0, false, false, ofs_running, desc_wfc_norm); + ModuleIO::write_vdata_palgrid(pgrid, wfc_norm.data(), is, nspin_, 0, ss_out.str(), 0.0, &ucell, precision, 0, false, false, false, ofs_running, desc_wfc_norm); } if (re_im_bands_picked[ib]) @@ -95,6 +95,7 @@ void Get_wf_lcao::begin_gamma(const UnitCell& ucell, 0, false, false, + false, ofs_running, desc_wfc_real); @@ -112,6 +113,7 @@ void Get_wf_lcao::begin_gamma(const UnitCell& ucell, 0, false, false, + false, ofs_running, desc_wfc_imag); } @@ -199,6 +201,7 @@ void Get_wf_lcao::begin_k(const UnitCell& ucell, 0, false, false, + false, ofs_running, desc_wfc_norm); } @@ -235,6 +238,7 @@ void Get_wf_lcao::begin_k(const UnitCell& ucell, 0, false, false, + false, ofs_running, desc_wfc_real); @@ -252,6 +256,7 @@ void Get_wf_lcao::begin_k(const UnitCell& ucell, 0, false, false, + false, ofs_running, desc_wfc_imag); } diff --git a/source/source_io/module_wf/get_wf_pw.cpp b/source/source_io/module_wf/get_wf_pw.cpp index c3d3401c2d9..39b17bc18f0 100644 --- a/source/source_io/module_wf/get_wf_pw.cpp +++ b/source/source_io/module_wf/get_wf_pw.cpp @@ -338,7 +338,7 @@ void Get_wf_pw::write_cube(const int band, } // collinear spin channel (nspin=2); spinor components (nspin=4) are not spin channels data_desc += ModuleIO::make_spin_tag(component, nspin_); - ModuleIO::write_vdata_palgrid(pgrid, values.data(), component, nspin_, 0, filename.str(), 0.0, &ucell, 11, 0, false, true, ofs_running, data_desc); + ModuleIO::write_vdata_palgrid(pgrid, values.data(), component, nspin_, 0, filename.str(), 0.0, &ucell, 11, 0, false, true, false, ofs_running, data_desc); } // Explicit instantiation emits both precisions for each supported device from this .cpp file. diff --git a/source/source_io/module_wf/read_wf2rho_pw.cpp b/source/source_io/module_wf/read_wf2rho_pw.cpp index 50e31e733a3..20d0415e874 100644 --- a/source/source_io/module_wf/read_wf2rho_pw.cpp +++ b/source/source_io/module_wf/read_wf2rho_pw.cpp @@ -128,8 +128,13 @@ void ModuleIO::read_wf2rho_pw_impl(const ModulePW::PW_Basis_K* pw_wfc, // mohan add 2025-05-17 // .dat file + // NOTE: reads the fixed-name binary file from its first frame. Files + // accumulated across ionic steps under out_app_flag == 1 hold one + // frame per step (MD trajectories); only the first frame is seen + // here, so use out_app_flag 0/2 (per-step files) when the charge + // density of a specific step is to be reconstructed. const int out_type = 2; - const bool out_app_flag = false; + const int out_app_flag = 0; const bool gamma_only = false; const int istep = -1; diff --git a/source/source_io/module_wf/read_wfc_nao.cpp b/source/source_io/module_wf/read_wfc_nao.cpp index 2d26e9390ec..d0fe553bd32 100644 --- a/source/source_io/module_wf/read_wfc_nao.cpp +++ b/source/source_io/module_wf/read_wfc_nao.cpp @@ -7,7 +7,9 @@ #include "source_base/tool_title.h" // use title #include "source_base/global_function.h" // use READ_VALUE +#include #include +#include namespace { @@ -19,6 +21,36 @@ bool read_binary_value(std::ifstream& ifs, T& value) return static_cast(ifs); } +/** + * @brief skip leading comment lines starting with '#' in a text stream. + * + * Text wavefunction files may carry a provenance header (ABACUS version, + * timestamp, ionic step) before the data block. The stream is left positioned + * at the first non-comment character. Must not be used on binary streams, + * where '#' is legitimate record data. + * + * @param ifs text input stream positioned at the start of the file + */ +void skip_comment_lines(std::ifstream& ifs) +{ + std::string line; + while (ifs.peek() != std::char_traits::eof()) + { + const std::streampos pos = ifs.tellg(); + ifs >> std::ws; + if (ifs.peek() == '#') + { + std::getline(ifs, line); + } + else + { + ifs.seekg(pos); + ifs.clear(); + break; + } + } +} + template bool read_record_value(std::ifstream& ifs, T& value, const bool binary) { @@ -101,6 +133,10 @@ bool ModuleIO::read_wfc_nao_spin_populations(const std::string& readin_dir, ik, ik2iktot, nspin, nkstot, read_type, false, gamma_only, -1); const std::ios_base::openmode mode = binary ? std::ios::in | std::ios::binary : std::ios::in; std::ifstream ifs(file.c_str(), mode); + if (!binary) + { + skip_comment_lines(ifs); + } if (!gamma_only) { int ik_file = 0; @@ -261,6 +297,11 @@ bool ModuleIO::read_wfc_nao( std::cout << " Read NAO wave functions from " << ss << std::endl; } + if (!binary) + { + skip_comment_lines(ifs); + } + const auto incomplete_file = [&](const std::string& field) { error_message << "The wave function file is incomplete or corrupted while reading " << field << ": " << ss << std::endl; @@ -384,18 +425,78 @@ bool ModuleIO::read_wfc_nao( { if (myrank == 0) { - const bool out_app_flag = false; std::stringstream error_message; - std::string readin_dir = global_readin_dir; - if(istep >= 0) + const int read_type = binary ? 2 : 1; + // Candidate file locations, in priority order: + // 1. readin_dir + step-suffixed name: layout written by runs with + // out_app_flag 0 (files in the OUT directory root) + // 2. readin_dir + "PSI/" + step-suffixed name: layout written by + // runs with out_app_flag 2 + // 3. readin_dir + "WFC/" + step-suffixed name: layout written by + // releases before the out_app_flag rework + // 4. readin_dir + name without the step suffix: written in append + // mode (out_app_flag 1) + std::vector candidates; + if (istep >= 0) { - readin_dir = readin_dir + "WFC/"; + const int suffix_app_flag = 0; + candidates.push_back(ModuleIO::filename_output(global_readin_dir, + "wf", + "nao", + ik, + ik2iktot, + nspin, + nkstot, + read_type, + suffix_app_flag, + gamma_only, + istep)); + const std::string psi_dir = global_readin_dir + "PSI/"; + candidates.push_back(ModuleIO::filename_output(psi_dir, + "wf", + "nao", + ik, + ik2iktot, + nspin, + nkstot, + read_type, + suffix_app_flag, + gamma_only, + istep)); + const std::string legacy_dir = global_readin_dir + "WFC/"; + candidates.push_back(ModuleIO::filename_output(legacy_dir, + "wf", + "nao", + ik, + ik2iktot, + nspin, + nkstot, + read_type, + suffix_app_flag, + gamma_only, + istep)); + } + const int append_app_flag = 1; + candidates.push_back(ModuleIO::filename_output(global_readin_dir, + "wf", + "nao", + ik, + ik2iktot, + nspin, + nkstot, + read_type, + append_app_flag, + gamma_only, + istep)); + + for (size_t ic = 0; ic < candidates.size(); ++ic) + { + read_success = read_one_file(candidates[ic], error_message, ik, ctot); + if (read_success) + { + break; + } } - const int read_type = binary ? 2 : 1; - std::string ss = ModuleIO::filename_output(readin_dir,"wf","nao", - ik,ik2iktot,nspin,nkstot,read_type,out_app_flag,gamma_only,istep); - - read_success = read_one_file(ss, error_message, ik, ctot); errors = error_message.str(); } #ifdef __MPI diff --git a/source/source_io/module_wf/write_wfc_nao.cpp b/source/source_io/module_wf/write_wfc_nao.cpp index 2d28e96e60d..5e98bc0b2be 100644 --- a/source/source_io/module_wf/write_wfc_nao.cpp +++ b/source/source_io/module_wf/write_wfc_nao.cpp @@ -10,6 +10,89 @@ #include "source_base/global_function.h" #include "source_base/module_out/binstream.h" #include "source_base/module_out/filename.h" +#include "source_base/formatter.h" +#include "source_main/version.h" + +#include + +namespace +{ + +/** + * @brief compact spin-channel label for the per-file provenance header. + * + * The header goes into a per-spin file (e.g. wfk1s1_nao.txt), so the compact + * form " (spin up)" is used, matching the bands1.txt/bands2.txt header. + * + * @param is spin channel index, -1 when undefined + * @param nspin number of spins; the label is dropped unless nspin == 2 + * @return " (spin up)", " (spin down)" or an empty string + */ +std::string wfc_spin_tag(const int is, const int nspin) +{ + if (nspin != 2 || is < 0) + { + return ""; + } + return (is == 0) ? " (spin up)" : " (spin down)"; +} + +/** + * @brief spin channel of a global k-point index, consistent with the s{N} + * block of filename_output. + * + * @param ik0 global k-point index (the ik2iktot value) + * @param nspin number of spins + * @param nkstot total number of k-points including the spin multiplicity + * @return 0 for spin up, 1 for spin down, -1 when nspin != 2 + */ +int wfc_spin_index(const int ik0, const int nspin, const int nkstot) +{ + if (nspin != 2) + { + return -1; + } + const int half_k = nkstot / 2; + if (ik0 >= half_k) + { + return 1; + } + return 0; +} + +/** + * @brief build the provenance header that precedes the wavefunction data + * block, mirroring the bands1.txt/bands2.txt header style. + * + * In append mode (out_app_flag == 1) every accumulated frame carries its own + * header, so readers can tell the ionic steps apart. + * + * @param istep zero-based ionic step index, printed one-based; the ionic-step + * line is omitted when istep < 0 (no step context in the run) + * @param spin_tag label of the collinear spin channel, empty when nspin != 2 + * @return two or three comment lines terminated by newlines + */ +std::string build_wfc_nao_header(const int istep, const std::string& spin_tag) +{ + const std::time_t now = std::time(nullptr); + std::tm tm_local; + localtime_r(&now, &tm_local); + char time_buf[64]; + std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &tm_local); + if (istep < 0) + { + return FmtCore::format("# ABACUS version: %s\n# Written at %s\n", + VERSION, + time_buf); + } + return FmtCore::format("# ABACUS version: %s\n# Written at %s\n# IONIC STEP %d%s\n", + VERSION, + time_buf, + istep + 1, + spin_tag.c_str()); +} + +} // namespace namespace ModuleIO { @@ -21,7 +104,8 @@ void wfc_nao_write2file(const std::string& name, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, const bool& writeBinary, - const bool& append_flag) + const bool& append_flag, + const std::string& header) { ModuleBase::TITLE("ModuleIO", "wfc_nao_write2file"); ModuleBase::timer::start("ModuleIO", "wfc_nao_write2file"); @@ -75,6 +159,12 @@ void wfc_nao_write2file(const std::string& name, { ModuleBase::WARNING_QUIT("ModuleIO::wfc_nao_write2file", "Can't write local orbital wave functions."); } + // provenance header precedes the data block; the binary branch above + // keeps its fixed record layout and ignores the header + if (!header.empty()) + { + ofs << header; + } ofs << nbands << " (number of bands)" << std::endl; ofs << nlocal << " (number of orbitals)"; ofs << std::setprecision(8); @@ -112,7 +202,8 @@ void wfc_nao_write2file_complex(const std::string& name, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, const bool& writeBinary, - const bool& append_flag) + const bool& append_flag, + const std::string& header) { ModuleBase::TITLE("ModuleIO","wfc_nao_write2file_complex"); ModuleBase::timer::start("ModuleIO","wfc_nao_write2file_complex"); @@ -169,6 +260,12 @@ void wfc_nao_write2file_complex(const std::string& name, { ModuleBase::WARNING_QUIT("ModuleIO::wfc_nao_write2file_complex", "Can't write local orbital wave functions."); } + // provenance header precedes the k-point record; the binary branch + // above keeps its fixed record layout and ignores the header + if (!header.empty()) + { + ofs << header; + } ofs << std::setprecision(8); ofs << ik + 1 << " (index of k points)" << std::endl; ofs << kvec_c.x << " " << kvec_c.y << " " << kvec_c.z << std::endl; @@ -202,7 +299,7 @@ void wfc_nao_write2file_complex(const std::string& name, template void write_wfc_nao(const int out_type, - const bool out_app_flag, + const int out_app_flag, const psi::Psi& psi, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, @@ -270,7 +367,7 @@ void write_wfc_nao(const int out_type, if (myid == 0) { std::string wfc_dir; - if(out_app_flag) + if(out_app_flag == 1) { wfc_dir = PARAM.globalv.global_out_dir; } @@ -281,7 +378,13 @@ void write_wfc_nao(const int out_type, std::string fn = filename_output(wfc_dir,"wf","nao",ik,ik2iktot,nspin,nkstot, out_type,out_app_flag,gamma_only,istep); - bool append_flag = (istep > 0 && out_app_flag); + bool append_flag = (istep > 0 && out_app_flag == 1); + // spin channel of the current k-point and the per-frame + // provenance header (version, timestamp, ionic step, spin tag) + const int ik0 = ik2iktot[ik]; + const int is = wfc_spin_index(ik0, nspin, nkstot); + const std::string spin_tag = wfc_spin_tag(is, nspin); + const std::string header = build_wfc_nao_header(istep, spin_tag); if (std::is_same::value) { wfc_nao_write2file(fn, @@ -291,7 +394,8 @@ void write_wfc_nao(const int out_type, ekb, wg, writeBinary, - append_flag); + append_flag, + header); } else { @@ -303,7 +407,8 @@ void write_wfc_nao(const int out_type, ekb, wg, writeBinary, - append_flag); + append_flag, + header); } } } @@ -311,7 +416,7 @@ void write_wfc_nao(const int out_type, } template void write_wfc_nao(const int out_type, - const bool out_app_flag, + const int out_app_flag, const psi::Psi& psi, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, @@ -323,7 +428,7 @@ template void write_wfc_nao(const int out_type, const int istep); template void write_wfc_nao>(const int out_type, - const bool out_app_flag, + const int out_app_flag, const psi::Psi>& psi, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, diff --git a/source/source_io/module_wf/write_wfc_nao.h b/source/source_io/module_wf/write_wfc_nao.h index 152723b5109..a1421404b6d 100644 --- a/source/source_io/module_wf/write_wfc_nao.h +++ b/source/source_io/module_wf/write_wfc_nao.h @@ -4,6 +4,7 @@ #include "source_basis/module_ao/parallel_orbitals.h" #include "source_psi/psi.h" #include "source_base/vector3.h" +#include #include namespace ModuleIO @@ -25,7 +26,7 @@ namespace ModuleIO */ template void write_wfc_nao(const int out_type, - const bool out_app_flag, + const int out_app_flag, const psi::Psi& psi, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, @@ -36,6 +37,20 @@ void write_wfc_nao(const int out_type, const int nspin, const int istep=-1) ; +/** + * @brief write real-valued Gamma-only wavefunction coefficients to a file. + * + * @param name output file name + * @param ctot global wavefunction coefficient array, size nbands*nlocal + * @param nlocal number of orbitals (basis functions) + * @param ik local k-point index, used to index ekb/wg rows + * @param ekb eigenvalue matrix + * @param wg occupation matrix + * @param writeBinary true for the binary format, false for text + * @param append_flag true to append to an existing file, false to truncate + * @param header provenance comment block written before the data in text + * mode; ignored in binary mode + */ void wfc_nao_write2file(const std::string& name, const double* ctot, const int nlocal, @@ -43,8 +58,24 @@ void wfc_nao_write2file(const std::string& name, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, const bool& writeBinary, - const bool& append_flag = false); + const bool& append_flag, + const std::string& header); +/** + * @brief write complex wavefunction coefficients to a file. + * + * @param name output file name + * @param ctot global wavefunction coefficient array, size nbands*nlocal + * @param nlocal number of orbitals (basis functions) + * @param ik local k-point index, used to index ekb/wg rows + * @param kvec_c Cartesian k-vector of the k-point + * @param ekb eigenvalue matrix + * @param wg occupation matrix + * @param writeBinary true for the binary format, false for text + * @param append_flag true to append to an existing file, false to truncate + * @param header provenance comment block written before the data in text + * mode; ignored in binary mode + */ void wfc_nao_write2file_complex(const std::string& name, const std::complex* ctot, const int nlocal, @@ -52,7 +83,8 @@ void wfc_nao_write2file_complex(const std::string& name, const ModuleBase::Vector3& kvec_c, const ModuleBase::matrix& ekb, const ModuleBase::matrix& wg, - const bool& writeBinary = false, - const bool& append_flag = false); + const bool& writeBinary, + const bool& append_flag, + const std::string& header); }// namespace ModuleIO #endif diff --git a/source/source_io/module_wf/write_wfc_pw.cpp b/source/source_io/module_wf/write_wfc_pw.cpp index 450bb596988..f7433de42ef 100644 --- a/source/source_io/module_wf/write_wfc_pw.cpp +++ b/source/source_io/module_wf/write_wfc_pw.cpp @@ -23,6 +23,7 @@ void ModuleIO::write_wfc_pw( const int rank_in_pool, const int nproc_in_pool, const int out_wfc_pw, + const int out_app_flag, const double& ecutwfc, const std::string& global_out_dir, const psi::Psi>& psi, @@ -43,7 +44,6 @@ void ModuleIO::write_wfc_pw( assert(nkstot>0); assert(nks>0); - bool out_app_flag = false; // need to modify later, mohan 2025-05-17 bool gamma_only = false; // need to modify later, mohan 2025-05-17 std::string* wfilename = new std::string[nks]; @@ -65,15 +65,23 @@ void ModuleIO::write_wfc_pw( if (rank_in_pool == 0) { - if (out_wfc_pw == 1) + // Mirrors write_wfc_nao's append_flag: the first step of a + // run truncates the file, later steps append a new frame + // when out_app_flag == 1, so the file accumulates one frame + // per ionic step instead of being rewritten each step. + const bool append_frame = (istep > 0 && out_app_flag == 1); + if (!append_frame) { - std::ofstream ofs(fn.c_str()); // clear all wavefunc files. - ofs.close(); - } - else if (out_wfc_pw == 2) - { - Binstream wfs(fn, "w"); - wfs.close(); + if (out_wfc_pw == 1) + { + std::ofstream ofs(fn.c_str()); // clear all wavefunc files. + ofs.close(); + } + else if (out_wfc_pw == 2) + { + Binstream wfs(fn, "w"); + wfs.close(); + } } } } diff --git a/source/source_io/module_wf/write_wfc_pw.h b/source/source_io/module_wf/write_wfc_pw.h index 7e035bf82cd..6877179eed7 100644 --- a/source/source_io/module_wf/write_wfc_pw.h +++ b/source/source_io/module_wf/write_wfc_pw.h @@ -19,6 +19,7 @@ void write_wfc_pw( const int rank_in_pool, const int nproc_in_pool, const int out_wfc_pw, + const int out_app_flag, const double& ecutwfc, const std::string& global_out_dir, const psi::Psi>& psi, diff --git a/source/source_io/test/output_mulliken_test.cpp b/source/source_io/test/output_mulliken_test.cpp index 2d4b0800d02..6e7ab943a19 100644 --- a/source/source_io/test/output_mulliken_test.cpp +++ b/source/source_io/test/output_mulliken_test.cpp @@ -27,11 +27,11 @@ TYPED_TEST(OutputMullikenTest, OrbInfo) { CellIndex cell_index = CellIndex(this->atomLabels, this->atomCounts, this->lnchiCounts, 1); cell_index.write_orb_info("./"); - std::ifstream ifs("./Orbital"); + std::ifstream ifs("./nao.txt"); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); EXPECT_THAT(str, testing::HasSubstr("#io spec l m z sym")); EXPECT_THAT(str, testing::HasSubstr("0 Si 2 3 1 dx^2-y^2")); - remove("./Orbital"); + remove("./nao.txt"); } #ifdef __MPI diff --git a/source/source_io/test/read_wf2rho_pw_test.cpp b/source/source_io/test/read_wf2rho_pw_test.cpp index 991a46a8f26..eb43411bc61 100644 --- a/source/source_io/test/read_wf2rho_pw_test.cpp +++ b/source/source_io/test/read_wf2rho_pw_test.cpp @@ -298,6 +298,7 @@ TEST_F(ReadWfcRhoTest, ReadWfcRho) GlobalV::RANK_IN_POOL, GlobalV::NPROC_IN_POOL, TestParameters::input().out_wfc_pw, + 0, ecutwfc, out_dir, *psi, diff --git a/source/source_io/test/read_wfc_nao_test.cpp b/source/source_io/test/read_wfc_nao_test.cpp index c28f4b09c7e..8c0b9bbb18a 100644 --- a/source/source_io/test/read_wfc_nao_test.cpp +++ b/source/source_io/test/read_wfc_nao_test.cpp @@ -186,7 +186,8 @@ TEST_F(ReadWfcNaoTest, ReadBinaryGamma) ekb_source, wg_source, true, - false); + false, + ""); } #ifdef __MPI MPI_Barrier(MPI_COMM_WORLD); @@ -215,6 +216,64 @@ TEST_F(ReadWfcNaoTest, ReadBinaryGamma) } } +TEST_F(ReadWfcNaoTest, ReadTextWithCommentHeader) +{ + // Text files carrying the provenance header (version/timestamp/step) + // must stay readable: the reader skips leading '#' comment lines. + const int nbands = 2; + const int nlocal = 3; + Parallel_Orbitals para; + initialize_parallel_orbitals(para, nlocal, nbands); + + const std::vector coefficients = {0.1, 0.2, 0.3, 1.1, 1.2, 1.3}; + ModuleBase::matrix ekb_source(1, nbands); + ModuleBase::matrix wg_source(1, nbands); + ekb_source(0, 0) = -0.5; + ekb_source(0, 1) = 0.7; + wg_source(0, 0) = 2.0; + wg_source(0, 1) = 0.0; + if (my_rank == 0) + { + const std::string header = "# ABACUS version: test\n" + "# Written at 2026-01-01 00:00:00\n" + "# IONIC STEP 1 (spin up)\n"; + ModuleIO::wfc_nao_write2file(binary_test_dir + "wf_nao.txt", + coefficients.data(), + nlocal, + 0, + ekb_source, + wg_source, + false, + false, + header); + } +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif + + psi::Psi psi_read; + ModuleBase::matrix ekb(1, nbands); + ModuleBase::matrix wg(1, nbands); + const std::vector ik2iktot = {0}; + EXPECT_TRUE(ModuleIO::read_wfc_nao(binary_test_dir, + para, + psi_read, + ekb, + wg, + ik2iktot, + 1, + 1, + false)); + EXPECT_DOUBLE_EQ(ekb(0, 0), ekb_source(0, 0)); + EXPECT_DOUBLE_EQ(ekb(0, 1), ekb_source(0, 1)); + EXPECT_DOUBLE_EQ(wg(0, 0), wg_source(0, 0)); + EXPECT_DOUBLE_EQ(wg(0, 1), wg_source(0, 1)); + if (my_rank == 0) + { + EXPECT_DOUBLE_EQ(psi_read(0, 0, 0), coefficients[0]); + } +} + TEST_F(ReadWfcNaoTest, ReadBinaryGammaFloat) { const int nbands = 2; @@ -238,7 +297,8 @@ TEST_F(ReadWfcNaoTest, ReadBinaryGammaFloat) ekb_source, wg_source, true, - false); + false, + ""); } #ifdef __MPI MPI_Barrier(MPI_COMM_WORLD); @@ -292,7 +352,8 @@ TEST_F(ReadWfcNaoTest, ReadBinaryComplex) ekb_source, wg_source, true, - false); + false, + ""); } #ifdef __MPI MPI_Barrier(MPI_COMM_WORLD); @@ -349,7 +410,8 @@ TEST_F(ReadWfcNaoTest, ReadBinaryPart) ekb_source, wg_source, true, - false); + false, + ""); } #ifdef __MPI MPI_Barrier(MPI_COMM_WORLD); @@ -398,7 +460,8 @@ TEST_F(ReadWfcNaoTest, BinaryDoesNotFallBackToText) ekb_source, wg_source, false, - false); + false, + ""); } #ifdef __MPI MPI_Barrier(MPI_COMM_WORLD); @@ -451,6 +514,120 @@ TEST_F(ReadWfcNaoTest, RejectTruncatedBinary) true)); } +TEST_F(ReadWfcNaoTest, ReadPsiDirectoryLayout) +{ + // RT-TDDFT restart with out_app_flag = 2: the snapshot lives in the + // PSI/ subdirectory of the read-in directory and carries the one-based + // g{step} suffix in its name. + const int nbands = 2; + const int nlocal = 3; + Parallel_Orbitals para; + initialize_parallel_orbitals(para, nlocal, nbands); + + const std::string psi_dir = binary_test_dir + "PSI/"; + if (my_rank == 0) + { + ModuleBase::GlobalFunc::MAKE_DIR(psi_dir); + } +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif + + const std::vector coefficients = {0.1, 0.2, 0.3, 1.1, 1.2, 1.3}; + ModuleBase::matrix ekb_source(1, nbands); + ModuleBase::matrix wg_source(1, nbands); + ekb_source(0, 0) = -0.6; + ekb_source(0, 1) = 0.9; + wg_source(0, 0) = 2.0; + wg_source(0, 1) = 0.0; + + const int istep = 0; + const std::vector ik2iktot = {0}; + const int read_type = 2; + const int suffix_app_flag = 0; + // The real-valued reader builds candidate names through the gamma-only + // branch of filename_output, so the snapshot must use the same naming. + const bool gamma_only_for_name = true; + const std::string snapshot = ModuleIO::filename_output(psi_dir, + "wf", + "nao", + 0, + ik2iktot, + 1, + 1, + read_type, + suffix_app_flag, + gamma_only_for_name, + istep); + if (my_rank == 0) + { + ModuleIO::wfc_nao_write2file(snapshot, + coefficients.data(), + nlocal, + 0, + ekb_source, + wg_source, + true, + false, + ""); + } +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif + + psi::Psi psi_read; + ModuleBase::matrix ekb(1, nbands); + ModuleBase::matrix wg(1, nbands); + EXPECT_TRUE(ModuleIO::read_wfc_nao(binary_test_dir, + para, + psi_read, + ekb, + wg, + ik2iktot, + 1, + 1, + true, + 0, + istep)); + EXPECT_DOUBLE_EQ(ekb(0, 0), ekb_source(0, 0)); + EXPECT_DOUBLE_EQ(ekb(0, 1), ekb_source(0, 1)); + if (my_rank == 0) + { + EXPECT_DOUBLE_EQ(psi_read(0, 0, 0), coefficients[0]); + } + + // Rewriting the snapshot must replace it, not append another record. + if (my_rank == 0) + { + std::ifstream ifs(snapshot.c_str(), std::ios::binary); + ifs.seekg(0, std::ios::end); + const std::streamoff size_after_first_write = ifs.tellg(); + ifs.close(); + + ModuleIO::wfc_nao_write2file(snapshot, + coefficients.data(), + nlocal, + 0, + ekb_source, + wg_source, + true, + false, + ""); + + std::ifstream ifs_rewritten(snapshot.c_str(), std::ios::binary); + ifs_rewritten.seekg(0, std::ios::end); + const std::streamoff size_after_second_write = ifs_rewritten.tellg(); + ifs_rewritten.close(); + EXPECT_EQ(size_after_second_write, size_after_first_write); + + std::remove(snapshot.c_str()); + std::remove(psi_dir.c_str()); + } +#ifdef __MPI + MPI_Barrier(MPI_COMM_WORLD); +#endif +} + TEST_F(ReadWfcNaoTest, CompleteSpinPopulations) @@ -484,13 +661,13 @@ TEST_F(ReadWfcNaoTest, CompleteSpinPopulations) if (gamma_only) { ModuleIO::wfc_nao_write2file(file, real_coefficients.data(), nlocal, - ik, energies, occupations, binary, false); + ik, energies, occupations, binary, false, ""); } else { const ModuleBase::Vector3 kpoint(0.25, 0.0, 0.0); ModuleIO::wfc_nao_write2file_complex(file, complex_coefficients.data(), nlocal, - ik, kpoint, energies, occupations, binary, false); + ik, kpoint, energies, occupations, binary, false, ""); } } } @@ -533,7 +710,7 @@ TEST_F(ReadWfcNaoTest, CompleteGlobalKPointPopulations) { const ModuleBase::Vector3 kpoint(0.25 * ik, 0.0, 0.0); ModuleIO::wfc_nao_write2file_complex(file, coefficients.data(), nlocal, - ik, kpoint, energies, occupations, false, false); + ik, kpoint, energies, occupations, false, false, ""); } } std::vector populations; diff --git a/source/source_io/test/write_bands_test.cpp b/source/source_io/test/write_bands_test.cpp index 4fdcbdbb7e5..79990fc0118 100644 --- a/source/source_io/test/write_bands_test.cpp +++ b/source/source_io/test/write_bands_test.cpp @@ -5,6 +5,7 @@ #include "source_base/module_parallel/para_world.h" #include "source_base/parallel_comm.h" #include "source_base/parallel_global.h" +#include "source_main/version.h" #include "gtest/gtest.h" @@ -227,6 +228,61 @@ class BandOutputTest : public testing::TestWithParam input_.out_band = {1, 8}; } + /// Read the next data line of a band file, skipping the provenance header. + static bool read_data_line(std::istream& stream, std::string& line) + { + while (static_cast(std::getline(stream, line))) + { + const std::size_t first = line.find_first_not_of(" \t"); + if (first != std::string::npos && line[first] == '#') + { + continue; + } + return true; + } + return false; + } + + /// Collect the comment lines of a band file in the order they were written. + std::vector comment_lines(const std::string& name) const + { + std::vector comments; + if (world_->rank() != 0) + { + return comments; + } + std::ifstream file(directory_ + name); + EXPECT_TRUE(file.is_open()) << name; + std::string line; + while (static_cast(std::getline(file, line))) + { + const std::size_t first = line.find_first_not_of(" \t"); + if (first != std::string::npos && line[first] == '#') + { + comments.push_back(line); + } + } + return comments; + } + + /// Check the three comment lines preceding k-point block number `block`. + void expect_header(const std::string& name, int block, int spin, int channels) const + { + if (world_->rank() != 0) + { + return; + } + SCOPED_TRACE(name); + const std::vector comments = comment_lines(name); + ASSERT_GE(comments.size(), static_cast(3 * block + 3)); + EXPECT_EQ(comments[3 * block], "# ABACUS version: " + std::string(VERSION)); + const std::string written = "# Written at "; + EXPECT_EQ(comments[3 * block + 1].compare(0, written.size(), written), 0) + << comments[3 * block + 1]; + const std::string tag = (channels == 2) ? (spin == 0 ? " (spin up)" : " (spin down)") : ""; + EXPECT_EQ(comments[3 * block + 2], "# IONIC STEP " + std::to_string(block + 1) + tag); + } + void expect_output(const std::string& name, int spin, int nbands, double fermi, int precision, int copies) const { @@ -244,7 +300,7 @@ class BandOutputTest : public testing::TestWithParam { for (int ik = 0; ik < 5; ++ik) { - ASSERT_TRUE(static_cast(std::getline(file, line))) << "Missing k-point " << ik; + ASSERT_TRUE(read_data_line(file, line)) << "Missing k-point " << ik; std::istringstream row(line); int index = 0; ASSERT_TRUE(static_cast(row >> index)); @@ -271,7 +327,7 @@ class BandOutputTest : public testing::TestWithParam EXPECT_FALSE(static_cast(row >> extra)) << "Unexpected column: " << extra; } } - EXPECT_FALSE(static_cast(std::getline(file, line))) << "Unexpected line: " << line; + EXPECT_FALSE(read_data_line(file, line)) << "Unexpected line: " << line; } std::string read_text(const std::string& name) const @@ -304,18 +360,21 @@ class BandOutputTest : public testing::TestWithParam { SCOPED_TRACE(distributed ? "distributed" : "replicated"); prepare(nspin, 3, distributed); - ModuleIO::write_bands(input_, ekb_, kv_, ofs_running); + ModuleIO::write_bands(input_, ekb_, kv_, ofs_running, 0); if (nspin == 2) { expect_output("bands1.txt", 0, 3, 0.0, 8, 1); expect_output("bands2.txt", 1, 3, 0.0, 8, 1); expect_missing("band.txt"); + expect_header("bands1.txt", 0, 0, 2); + expect_header("bands2.txt", 0, 1, 2); } else { expect_output("band.txt", 0, 3, 0.0, 8, 1); expect_missing("bands1.txt"); expect_missing("bands2.txt"); + expect_header("band.txt", 0, 0, 1); } // Root must finish reading before another collective call overwrites output. Parallel::barrier(*world_); @@ -326,7 +385,7 @@ class BandOutputTest : public testing::TestWithParam TEST_P(BandOutputTest, DirectSingleSpin) { prepare(1, 3, false); - ModuleIO::nscf_bands(0, directory_ + "direct.txt", 3, 0.0, 8, ekb_, kv_); + ModuleIO::nscf_bands(0, directory_ + "direct.txt", 3, 0.0, 8, ekb_, kv_, ""); expect_output("direct.txt", 0, 3, 0.0, 8, 1); if (world_->rank() == 0) { @@ -339,8 +398,8 @@ TEST_P(BandOutputTest, DirectSingleSpin) TEST_P(BandOutputTest, DirectBothSpins) { prepare(2, 3, false); - ModuleIO::nscf_bands(0, directory_ + "up.txt", 3, 0.0, 8, ekb_, kv_); - ModuleIO::nscf_bands(1, directory_ + "down.txt", 3, 0.0, 8, ekb_, kv_); + ModuleIO::nscf_bands(0, directory_ + "up.txt", 3, 0.0, 8, ekb_, kv_, ""); + ModuleIO::nscf_bands(1, directory_ + "down.txt", 3, 0.0, 8, ekb_, kv_, ""); expect_output("up.txt", 0, 3, 0.0, 8, 1); expect_output("down.txt", 1, 3, 0.0, 8, 1); } @@ -350,7 +409,7 @@ TEST_P(BandOutputTest, PrecisionAndFermiShift) prepare(1, 3, false); for (const int precision : {4, 8}) { - ModuleIO::nscf_bands(0, directory_ + "direct.txt", 3, 0.25, precision, ekb_, kv_); + ModuleIO::nscf_bands(0, directory_ + "direct.txt", 3, 0.25, precision, ekb_, kv_, ""); expect_output("direct.txt", 0, 3, 0.25, precision, 1); if (world_->rank() == 0 && precision == 4) { @@ -366,23 +425,25 @@ TEST_P(BandOutputTest, OverwriteAndAppend) prepare(1, 3, true); seed_file("band.txt", "stale output\n"); Parallel::barrier(*world_); - ModuleIO::write_bands(input_, ekb_, kv_, ofs_running); + ModuleIO::write_bands(input_, ekb_, kv_, ofs_running, 0); expect_output("band.txt", 0, 3, 0.0, 8, 1); + expect_header("band.txt", 0, 0, 1); Parallel::barrier(*world_); parameters_->set_output(directory_, true); - ModuleIO::write_bands(input_, ekb_, kv_, ofs_running); + ModuleIO::write_bands(input_, ekb_, kv_, ofs_running, 1); expect_output("band.txt", 0, 3, 0.0, 8, 2); + expect_header("band.txt", 1, 0, 1); } TEST_P(BandOutputTest, OutputDisabled) { prepare(1, 3, true); input_.out_band[0] = 0; - ModuleIO::write_bands(input_, ekb_, kv_, ofs_running); + ModuleIO::write_bands(input_, ekb_, kv_, ofs_running, 0); expect_missing("band.txt"); seed_file("band.txt", "keep this content\n"); Parallel::barrier(*world_); - ModuleIO::write_bands(input_, ekb_, kv_, ofs_running); + ModuleIO::write_bands(input_, ekb_, kv_, ofs_running, 0); if (world_->rank() == 0) { EXPECT_EQ(read_text("band.txt"), "keep this content\n"); @@ -413,9 +474,11 @@ TEST_P(BandOutputTest, EmptyBandShard) { EXPECT_EQ(ekb_.nc, 0); } - ModuleIO::write_bands(input_, ekb_, kv_, ofs_running); + ModuleIO::write_bands(input_, ekb_, kv_, ofs_running, 0); expect_output("bands1.txt", 0, 1, 0.0, 8, 1); expect_output("bands2.txt", 1, 1, 0.0, 8, 1); + expect_header("bands1.txt", 0, 0, 2); + expect_header("bands2.txt", 0, 1, 2); } TEST_P(BandOutputTest, RunningLogNamesSpinChannels) @@ -425,7 +488,7 @@ TEST_P(BandOutputTest, RunningLogNamesSpinChannels) + std::to_string(world_->rank()) + ".log"; std::ofstream log(log_name); EXPECT_TRUE(log.is_open()); - ModuleIO::write_bands(input_, ekb_, kv_, log); + ModuleIO::write_bands(input_, ekb_, kv_, log, 0); log.close(); std::ifstream in(log_name); diff --git a/source/source_io/test/write_elf_logic_test.cpp b/source/source_io/test/write_elf_logic_test.cpp index 25269172f73..6bbf65c5f5a 100644 --- a/source/source_io/test/write_elf_logic_test.cpp +++ b/source/source_io/test/write_elf_logic_test.cpp @@ -170,6 +170,101 @@ TEST_F(ElfLogicTest, Nspin4MixedSigns) } } +// --------------------------------------------------------------------------- +// Zero-grid partition regression tests +// +// When the real-space FFT grid is decomposed across more MPI processes than +// it has z-slabs, some ranks receive nrxx == 0, which is a legitimate empty +// partition (see Parallel_Grid::reduce assert contract). write_elf does NOT +// skip the FFT path on those ranks: PW_Basis::real2recip / recip2real call +// gatherp_scatters, which performs an MPI_Alltoallv requiring every rank in +// POOL_WORLD to participate. Only the per-grid-point loops execute zero +// iterations; the FFT itself must still run so the Alltoallv matches across +// ranks. Every rank also enters write_vdata_palgrid so Parallel_Grid::reduce +// (MPI_Gatherv) sees a collective call. These tests document the zero-length +// container and zero-iteration invariants that keep empty partitions safe. +// --------------------------------------------------------------------------- + +TEST_F(ElfLogicTest, ZeroGridHasLocalGrid) +{ + // A rank holds local grid data only when both nrxx and npw are positive. + // write_elf no longer branches on this (every rank runs the FFT path), + // but the predicate is kept here to document the empty-partition + // condition that motivates the invariant checks below. + auto has_local_grid = [](int nrxx, int npw) { + return nrxx > 0 && npw > 0; + }; + + EXPECT_FALSE(has_local_grid(0, 0)); + EXPECT_FALSE(has_local_grid(0, 100)); + EXPECT_FALSE(has_local_grid(100, 0)); + EXPECT_TRUE(has_local_grid(100, 50)); + EXPECT_TRUE(has_local_grid(1, 1)); +} +TEST_F(ElfLogicTest, ZeroLengthVectorIsLegal) +{ + // std::vector(0) is well-defined and .data() is non-null only + // when size() > 0. Empty partitions pass a null pointer into the MPI + // reduce call, which Parallel_Grid::reduce explicitly tolerates when + // nrxx == 0 (sendcount 0 ignores the buffer). + std::vector empty(0, 0.0); + EXPECT_EQ(empty.size(), 0u); + EXPECT_EQ(empty.data(), nullptr); + + // All-zero non-empty vector for comparison: .data() is non-null. + std::vector zeros(5, 0.0); + EXPECT_EQ(zeros.size(), 5u); + EXPECT_NE(zeros.data(), nullptr); +} + +TEST_F(ElfLogicTest, ZeroGridLoopSkipsAllIterations) +{ + // Every for-loop in write_elf is bounded by `ir < nrxx` or `ip < npw`; + // with nrxx == 0 the loop body must execute zero times. + int iterations = 0; + const int nrxx = 0; + for (int ir = 0; ir < nrxx; ++ir) + { + ++iterations; + } + EXPECT_EQ(iterations, 0); + + iterations = 0; + const int npw = 0; + for (int ip = 0; ip < npw; ++ip) + { + ++iterations; + } + EXPECT_EQ(iterations, 0); +} + +TEST_F(ElfLogicTest, ZeroGridElfValuesRemainInitialised) +{ + // On an empty partition, `elf` and `tau_vw` are all-zero initialised by + // the std::vector constructor and never updated, so every element stays + // at the supplied default. + const int nrxx = 0; + const int nspin_eff = 1; + + std::vector> elf(nspin_eff, std::vector(nrxx, 0.0)); + + EXPECT_EQ(elf.size(), 1u); + EXPECT_EQ(elf[0].size(), 0u); + EXPECT_EQ(elf[0].data(), nullptr); + + // For a non-empty partition the vector still initialises to zero, then + // the computation fills it, documenting the contract contrast. + const int nrxx_nonempty = 8; + std::vector> elf_nonempty(nspin_eff, + std::vector(nrxx_nonempty, 0.0)); + EXPECT_EQ(elf_nonempty[0].size(), 8u); + EXPECT_NE(elf_nonempty[0].data(), nullptr); + for (int i = 0; i < nrxx_nonempty; ++i) + { + EXPECT_DOUBLE_EQ(elf_nonempty[0][i], 0.0); + } +} + int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); diff --git a/source/source_io/test/write_orb_info_test.cpp b/source/source_io/test/write_orb_info_test.cpp index 6795439f743..2f8d4876dc6 100644 --- a/source/source_io/test/write_orb_info_test.cpp +++ b/source/source_io/test/write_orb_info_test.cpp @@ -50,7 +50,7 @@ TEST(OrbInfo,WriteOrbInfo) unitcell::cal_nwfc(ofs,*ucell,ucell->atoms, nspin, nlocal, npol, basis_type, esolver_type, init_wfc, nbands); ModuleIO::write_orb_info(ucell); ofs.close(); - std::ifstream ifs("Orbital"); + std::ifstream ifs("nao.txt"); std::string str((std::istreambuf_iterator(ifs)),std::istreambuf_iterator()); EXPECT_THAT(str, testing::HasSubstr("#io spec l m z sym")); EXPECT_THAT(str, testing::HasSubstr("0 Si 0 0 1 s")); @@ -58,7 +58,7 @@ TEST(OrbInfo,WriteOrbInfo) EXPECT_THAT(str, testing::HasSubstr("#sym =Symmetry name of real orbital")); ifs.close(); delete ucell; - std::remove("Orbital"); + std::remove("nao.txt"); std::remove("running.log"); } diff --git a/source/source_io/test/write_wfc_nao_test.cpp b/source/source_io/test/write_wfc_nao_test.cpp index b5a64f2a336..73790bf442d 100644 --- a/source/source_io/test/write_wfc_nao_test.cpp +++ b/source/source_io/test/write_wfc_nao_test.cpp @@ -35,9 +35,9 @@ TEST(GenWfcLcaoFnameTest, OutType1GammaOnlyOutAppFlagTrue) const int nspin = 1; const int nkstot = 1; const int out_type = 1; - const bool out_app_flag = true; + const int out_app_flag = 1; const bool gamma_only = true; - // if out_app_flag = true, then the 'g' label will not show up + // if out_app_flag = 1 (append), then the 'g' label will not show up const int istep = 0; std::string result = ModuleIO::filename_output( @@ -60,9 +60,9 @@ TEST(GenWfcLcaoFnameTest, OutType2GammaOnlyOutAppFlagFalse) const int nspin = 2; const int nkstot = 2; const int out_type = 2; - const bool out_app_flag = false; + const int out_app_flag = 0; const bool gamma_only = true; - // if out_app_flag = false, then the 'g' label appears + // if out_app_flag = 0 (no append), then the 'g' label appears const int istep = 2; std::string result = ModuleIO::filename_output( @@ -84,7 +84,7 @@ TEST(GenWfcLcaoFnameTest, OutTypeInvalid) const int nspin = 1; const int nkstot = 3; const int out_type = 3; - const bool out_app_flag = true; + const int out_app_flag = 1; const bool gamma_only = false; const int istep = 3; @@ -102,6 +102,33 @@ TEST(GenWfcLcaoFnameTest, OutTypeInvalid) EXPECT_EQ(result, expected_output); } +// --------------------------------------------------------------------------- +// out_app_flag = 2 (write to subdirectory) keeps the g{N} suffix, identical +// to mode 0 at the filename level; directory routing is handled by the caller. +// --------------------------------------------------------------------------- +TEST(GenWfcLcaoFnameTest, OutAppFlagMode2SameNamingAsMode0) +{ + const std::string directory = ""; + const std::string property = "wf"; + const std::string basis = "nao"; + const int ik = 1; + const std::vector ik2iktot = {0, 1}; + const int nspin = 2; + const int nkstot = 2; + const int out_type = 2; + const int out_app_flag = 2; + const bool gamma_only = true; + const int istep = 2; + + std::string result = ModuleIO::filename_output( + directory, property, basis, ik, ik2iktot, nspin, + nkstot, out_type, out_app_flag, gamma_only, istep); + + // Same g-suffixed naming as mode 0. + std::string expected_output = "wfs2g3_nao.dat"; + EXPECT_EQ(result, expected_output); +} + template void read_bin(const std::string& name, std::vector& data) { @@ -151,7 +178,7 @@ class WriteWfcLcaoTest : public testing::Test // mohan add 2025-05-11 std::vector ik2iktot = {0,1}; int nkstot = 2; - bool out_app_flag = true; + int out_app_flag = 1; void SetUp() override { @@ -299,6 +326,51 @@ TEST_F(WriteWfcLcaoTest, WriteWfcLcaoComplex) } } +TEST_F(WriteWfcLcaoTest, WriteWfcLcaoTextHeader) +{ + const std::string out_dir = testing::TempDir(); + TestParameters::sys().global_out_dir = out_dir; + + const std::string property = "wf"; + const std::string basis = "nao"; + const int nspin = 2; + const int out_type = 1; + const int out_app_flag = 1; + const bool gamma_only = true; + const int istep = 0; + + psi::Psi my_psi(psi_local_double.data(), nk, nbands_local, nbasis_local, nbasis_local, true); + + ModuleIO::write_wfc_nao(out_type, out_app_flag, my_psi, ekb, wg, kvec_c, + ik2iktot, nkstot, pv, nspin, istep); + + if (GlobalV::MY_RANK == 0) + { + // ik2iktot[0] == 0 belongs to the first spin half of nkstot == 2, + // so the header must carry the one-based step and the spin tag + const std::string fname = ModuleIO::filename_output( + out_dir, property, basis, 0, ik2iktot, nspin, + nkstot, out_type, out_app_flag, gamma_only, istep); + + std::ifstream file1(fname); + ASSERT_TRUE(file1.good()); + std::string first_line; + std::getline(file1, first_line); + EXPECT_THAT(first_line, testing::HasSubstr("# ABACUS version:")); + std::string rest((std::istreambuf_iterator(file1)), std::istreambuf_iterator()); + EXPECT_THAT(rest, testing::HasSubstr("# IONIC STEP 1 (spin up)")); + EXPECT_THAT(rest, testing::HasSubstr("(number of bands)")); + file1.close(); + + std::remove(fname.c_str()); + // the second k-point belongs to the spin-down half; clean it up too + const std::string fname_down = ModuleIO::filename_output( + out_dir, property, basis, 1, ik2iktot, nspin, + nkstot, out_type, out_app_flag, gamma_only, istep); + std::remove(fname_down.c_str()); + } +} + TEST(ModuleIOTest, WriteWfcNao) { if (GlobalV::MY_RANK == 0) @@ -323,7 +395,7 @@ TEST(ModuleIOTest, WriteWfcNao) wg(1, 1) = 1.2; // Call the function to be tested - ModuleIO::wfc_nao_write2file(filename, ctot.data(), TestParameters::sys().nlocal, 0, ekb, wg, false); + ModuleIO::wfc_nao_write2file(filename, ctot.data(), TestParameters::sys().nlocal, 0, ekb, wg, false, false, ""); // Check the output file std::ifstream ifs(filename); @@ -345,6 +417,50 @@ TEST(ModuleIOTest, WriteWfcNao) } } +TEST(ModuleIOTest, WriteWfcNaoHeader) +{ + if (GlobalV::MY_RANK == 0) + { + // Set up GlobalV + GlobalV::DRANK = 0; + TestParameters::input().nbands = 2; + TestParameters::sys().nlocal = 2; + + const std::string filename = std::string(testing::TempDir()) + "test_wfc_nao_header.txt"; + std::vector ctot = {0.1, 0.2, 0.3, 0.4}; + ModuleBase::matrix ekb(2, 2); + ekb(0, 0) = 0.5; + ekb(1, 0) = 0.6; + ekb(0, 1) = 0.7; + ekb(1, 1) = 0.8; + ModuleBase::matrix wg(2, 2); + wg(0, 0) = 0.9; + wg(1, 0) = 1.0; + wg(0, 1) = 1.1; + wg(1, 1) = 1.2; + + const std::string header = "# ABACUS version: test\n" + "# Written at 2026-01-01 00:00:00\n" + "# IONIC STEP 1 (spin up)\n"; + + // Call the function to be tested + ModuleIO::wfc_nao_write2file(filename, ctot.data(), TestParameters::sys().nlocal, 0, ekb, wg, false, false, header); + + // Check the output file: the provenance header precedes the data block + std::ifstream ifs(filename); + std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + EXPECT_THAT(str, testing::HasSubstr("# ABACUS version: test\n")); + EXPECT_THAT(str, testing::HasSubstr("# Written at 2026-01-01 00:00:00\n")); + EXPECT_THAT(str, testing::HasSubstr("# IONIC STEP 1 (spin up)\n")); + EXPECT_THAT(str, testing::HasSubstr("2 (number of bands)")); + EXPECT_THAT(str, testing::HasSubstr("1.00000000e-01 2.00000000e-01")); + ifs.close(); + + // clean up + std::remove(filename.c_str()); + } +} + TEST(ModuleIOTest, WriteWfcNaoBinary) { if (GlobalV::MY_RANK == 0) @@ -369,7 +485,7 @@ TEST(ModuleIOTest, WriteWfcNaoBinary) wg(1, 1) = 1.2; // Call the function to be tested - ModuleIO::wfc_nao_write2file(filename, ctot.data(), TestParameters::sys().nlocal, 0, ekb, wg, true); + ModuleIO::wfc_nao_write2file(filename, ctot.data(), TestParameters::sys().nlocal, 0, ekb, wg, true, false, ""); // Check the output file Binstream wfc(filename, "r"); @@ -402,6 +518,46 @@ TEST(ModuleIOTest, WriteWfcNaoBinary) } } +TEST(ModuleIOTest, WriteWfcNaoBinaryIgnoresHeader) +{ + if (GlobalV::MY_RANK == 0) + { + // Set up GlobalV + GlobalV::DRANK = 0; + TestParameters::input().nbands = 2; + TestParameters::sys().nlocal = 2; + + const std::string filename = std::string(testing::TempDir()) + "test_wfc_nao_header.dat"; + std::vector ctot = {0.1, 0.2, 0.3, 0.4}; + ModuleBase::matrix ekb(2, 2); + ekb(0, 0) = 0.5; + ekb(1, 0) = 0.6; + ekb(0, 1) = 0.7; + ekb(1, 1) = 0.8; + ModuleBase::matrix wg(2, 2); + wg(0, 0) = 0.9; + wg(1, 0) = 1.0; + wg(0, 1) = 1.1; + wg(1, 1) = 1.2; + + const std::string header = "# ABACUS version: test\n"; + ModuleIO::wfc_nao_write2file(filename, ctot.data(), TestParameters::sys().nlocal, 0, ekb, wg, true, false, header); + + // the binary record must start with the band count, not the header + Binstream wfc(filename, "r"); + int nbands = 0; + int nlocal = 0; + wfc >> nbands; + wfc >> nlocal; + EXPECT_EQ(nbands, 2); + EXPECT_EQ(nlocal, 2); + wfc.close(); + + // clean up + std::remove(filename.c_str()); + } +} + TEST(ModuleIOTest, WriteWfcNaoComplex) { if (GlobalV::MY_RANK == 0) @@ -427,7 +583,7 @@ TEST(ModuleIOTest, WriteWfcNaoComplex) std::complex(0.0, 3.0)}; // Call the function - ModuleIO::wfc_nao_write2file_complex(name, ctot.data(), TestParameters::sys().nlocal, ik, kvec_c, ekb, wg); + ModuleIO::wfc_nao_write2file_complex(name, ctot.data(), TestParameters::sys().nlocal, ik, kvec_c, ekb, wg, false, false, ""); // Check the output file std::ifstream ifs(name); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); @@ -470,7 +626,7 @@ TEST(ModuleIOTest, WriteWfcNaoComplexBinary) std::complex(6.0, 8.0)}; // Call the function - ModuleIO::wfc_nao_write2file_complex(name, ctot.data(), TestParameters::sys().nlocal, ik, kvec_c, ekb, wg, true); + ModuleIO::wfc_nao_write2file_complex(name, ctot.data(), TestParameters::sys().nlocal, ik, kvec_c, ekb, wg, true, false, ""); // Check the output file Binstream wfc(name, "r"); @@ -529,7 +685,7 @@ TEST(ModuleIOTest, WriteWfcNaoBinaryOpenFail) ModuleBase::matrix wg(2, 2); testing::internal::CaptureStdout(); - EXPECT_EXIT(ModuleIO::wfc_nao_write2file(filename, ctot.data(), nlocal, 0, ekb, wg, true), + EXPECT_EXIT(ModuleIO::wfc_nao_write2file(filename, ctot.data(), nlocal, 0, ekb, wg, true, false, ""), ::testing::ExitedWithCode(1), ""); std::string output = testing::internal::GetCapturedStdout(); @@ -560,7 +716,7 @@ TEST(ModuleIOTest, WriteWfcNaoComplexBinaryOpenFail) std::complex(0.0, 3.0)}; testing::internal::CaptureStdout(); - EXPECT_EXIT(ModuleIO::wfc_nao_write2file_complex(name, ctot.data(), nlocal, ik, kvec_c, ekb, wg, true), + EXPECT_EXIT(ModuleIO::wfc_nao_write2file_complex(name, ctot.data(), nlocal, ik, kvec_c, ekb, wg, true, false, ""), ::testing::ExitedWithCode(1), ""); std::string output = testing::internal::GetCapturedStdout(); diff --git a/source/source_io/test_serial/io_sys_var_test.cpp b/source/source_io/test_serial/io_sys_var_test.cpp index 678ee6a81b4..2fe0fa4ba6d 100644 --- a/source/source_io/test_serial/io_sys_var_test.cpp +++ b/source/source_io/test_serial/io_sys_var_test.cpp @@ -5,6 +5,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "source_base/fs_compat.h" #include "source_base/tool_quit.h" #include "source_io/module_parameter/input_parameter.h" #define private public @@ -67,8 +68,18 @@ TEST_F(InputTest, Item_test) TestParameters::set_global_dir(readinput, input, sys); EXPECT_EQ(sys.global_out_dir, "OUT.test/"); - EXPECT_EQ(sys.global_stru_dir, "OUT.test/STRU/"); - EXPECT_EQ(sys.global_matrix_dir, "OUT.test/matrix/"); + // out_app_flag defaults to 1, so stru dir maps to global out dir + EXPECT_EQ(sys.global_stru_dir, "OUT.test/"); + // out_app_flag defaults to 1, so matrix dir maps to global out dir + EXPECT_EQ(sys.global_matrix_dir, "OUT.test/"); + EXPECT_EQ(sys.global_wfc_dir, "OUT.test/"); + EXPECT_EQ(sys.global_psi_dir, "OUT.test/"); + EXPECT_EQ(sys.global_chg_dir, "OUT.test/"); + EXPECT_EQ(sys.global_pot_dir, "OUT.test/"); + EXPECT_EQ(sys.global_elf_dir, "OUT.test/"); + EXPECT_EQ(sys.global_dos_dir, "OUT.test/"); + EXPECT_EQ(sys.global_dip_dir, "OUT.test/"); + EXPECT_EQ(sys.global_mat_dir, "OUT.test/"); TestParameters::set_globalv(readinput, input, sys); @@ -100,3 +111,83 @@ TEST_F(InputTest, Item_test) EXPECT_EQ(sys.npol, 1); } } + +TEST_F(InputTest, SetGlobalDirMdRestart) +{ + readinput.check_ntype_flag = false; + + const std::string case_dir = testing::TempDir() + "/abacus_md_restart_io/"; + ModuleBase::make_directory(case_dir); + + // Restart from a user-provided read_file_dir holding the legacy + // "STRU_MD_{istep}" naming: the structure file must be picked up. + const std::string legacy_dir = case_dir + "legacy/"; + ModuleBase::make_directory(legacy_dir); + const std::string restart_txt = legacy_dir + "Restart_md.txt"; + std::ofstream restart_file(restart_txt.c_str()); + restart_file << 5 << std::endl; + restart_file << 0.03 << std::endl; + restart_file.close(); + const std::string stru_legacy = legacy_dir + "STRU_MD_5"; + std::ofstream stru_file_legacy(stru_legacy.c_str()); + stru_file_legacy << "ATOMIC_SPECIES" << std::endl; + stru_file_legacy.close(); + + input.suffix = "autotest"; + input.calculation = "md"; + input.mdp.md_restart = true; + input.read_file_dir = legacy_dir; + TestParameters::set_global_dir(readinput, input, sys); + EXPECT_EQ(sys.global_in_stru, stru_legacy); + + // Restart from a read_file_dir holding the current "STRU{istep + 1}" + // naming written by run_md (1-based, unified with relax). + const std::string newnaming_dir = case_dir + "newnaming/"; + ModuleBase::make_directory(newnaming_dir); + const std::string restart_txt_new = newnaming_dir + "Restart_md.txt"; + std::ofstream restart_file_new(restart_txt_new.c_str()); + restart_file_new << 5 << std::endl; + restart_file_new << 0.03 << std::endl; + restart_file_new.close(); + const std::string stru_new = newnaming_dir + "STRU6"; + std::ofstream stru_file_new(stru_new.c_str()); + stru_file_new << "ATOMIC_SPECIES" << std::endl; + stru_file_new.close(); + + input.read_file_dir = newnaming_dir; + TestParameters::set_global_dir(readinput, input, sys); + EXPECT_EQ(sys.global_in_stru, stru_new); + + // restore the shared input state so other tests are not affected + input.calculation = "scf"; + input.mdp.md_restart = false; + input.read_file_dir = "OUT.$suffix"; +} + +TEST_F(InputTest, SetGlobalDirMode2) +{ + readinput.check_ntype_flag = false; + + // out_app_flag = 2 routes every output class to its unified + // subdirectory below OUT./ + input.suffix = "modetwo"; + input.out_app_flag = 2; + TestParameters::set_global_dir(readinput, input, sys); + + EXPECT_EQ(sys.global_out_dir, "OUT.modetwo/"); + EXPECT_EQ(sys.global_stru_dir, "OUT.modetwo/STRU/"); + EXPECT_EQ(sys.global_psi_dir, "OUT.modetwo/PSI/"); + EXPECT_EQ(sys.global_chg_dir, "OUT.modetwo/CHG/"); + EXPECT_EQ(sys.global_pot_dir, "OUT.modetwo/POT/"); + EXPECT_EQ(sys.global_elf_dir, "OUT.modetwo/ELF/"); + EXPECT_EQ(sys.global_dos_dir, "OUT.modetwo/DOS/"); + EXPECT_EQ(sys.global_dip_dir, "OUT.modetwo/DIP/"); + EXPECT_EQ(sys.global_mat_dir, "OUT.modetwo/MAT/"); + // legacy aliases follow the unified directories + EXPECT_EQ(sys.global_wfc_dir, sys.global_psi_dir); + EXPECT_EQ(sys.global_matrix_dir, sys.global_mat_dir); + + // restore the shared input state so other tests are not affected + input.suffix = "test"; + input.out_app_flag = 1; +} diff --git a/source/source_io/test_serial/read_input_test.cpp b/source/source_io/test_serial/read_input_test.cpp index e4390c60c29..e85bf6815d0 100644 --- a/source/source_io/test_serial/read_input_test.cpp +++ b/source/source_io/test_serial/read_input_test.cpp @@ -39,7 +39,6 @@ void make_dir_out(const std::string& suffix, const bool& restart, const bool out_alllog, const std::string& global_out_dir, - const std::string& global_stru_dir, const std::string& global_matrix_dir, const std::string& global_wfc_dir, const std::string& global_mlkedf_descriptor_dir, @@ -423,6 +422,46 @@ TEST_F(InputTest, Check) EXPECT_TRUE(std::remove("./INPUT.ref") == 0); } +TEST_F(InputTest, OutAppFlagParsing) +{ + // numeric modes 0/1/2 are parsed as integers + { + Parameter param; + read_parameters("app_flag_numeric_INPUT", "basis_type lcao\nout_app_flag 2\n", param); + EXPECT_EQ(param.inp.out_app_flag, 2); + } + { + Parameter param; + read_parameters("app_flag_zero_INPUT", "basis_type lcao\nout_app_flag 0\n", param); + EXPECT_EQ(param.inp.out_app_flag, 0); + } + // the legacy Boolean spellings remain valid: true-like maps to mode 1, + // false-like maps to mode 0 + const std::vector> spellings + = {{"true", 1}, + {"TRUE", 1}, + {"Yes", 1}, + {"ON", 1}, + {".true.", 1}, + {"t", 1}, + {"false", 0}, + {"False", 0}, + {"NO", 0}, + {"off", 0}, + {".false.", 0}, + {"F", 0}}; + for (const auto& spelling : spellings) + { + Parameter param; + read_parameters("app_flag_bool_INPUT", "basis_type lcao\nout_app_flag " + spelling.first + "\n", param); + EXPECT_EQ(param.inp.out_app_flag, spelling.second) << "spelling: " << spelling.first; + } + // values outside 0/1/2 are rejected + expect_invalid_input("app_flag_oob_INPUT", + "basis_type lcao\nout_app_flag 3\n", + "out_app_flag should be 0, 1, or 2"); +} + TEST_F(InputTest, ExplicitNoccSurvivesUnresolvedElectronNumber) { Parameter parameters; diff --git a/source/source_io/test_serial/rho_io_test.cpp b/source/source_io/test_serial/rho_io_test.cpp index 38b226fc994..5a63f4fd485 100644 --- a/source/source_io/test_serial/rho_io_test.cpp +++ b/source/source_io/test_serial/rho_io_test.cpp @@ -7,6 +7,10 @@ #include "prepare_unitcell.h" #include "source_base/parallel_grid.h" +#include +#include +#include + Magnetism::Magnetism() { this->tot_mag = 0.0; @@ -97,7 +101,7 @@ TEST_F(RhoIOTest, Write) ucell->atoms[0].ncpp.zv = 4; Parallel_Grid pgrid(nx, ny, nz, nz, nrxx, nz, 1); ModuleIO::read_vdata_palgrid(pgrid, my_rank, ofs_running, "support/chg.cube", rho[0], ucell->nat); - ModuleIO::write_vdata_palgrid(pgrid, rho[0], 0, nspin, 0, "test_write_vdata_palgrid.cube", 0.461002, ucell, 11, 1, false, false, ofs_running, "charge density"); + ModuleIO::write_vdata_palgrid(pgrid, rho[0], 0, nspin, 0, "test_write_vdata_palgrid.cube", 0.461002, ucell, 11, 1, false, false, false, ofs_running, "charge density"); std::ifstream ifs1("test_write_vdata_palgrid.cube", std::ifstream::binary | std::ifstream::ate); std::ifstream ifs2("support/chg.cube", std::ifstream::binary | std::ifstream::ate); EXPECT_EQ(ifs1.tellg(), ifs2.tellg()); @@ -217,10 +221,80 @@ TEST_F(CubeIOTest, WriteCube) comment, natom, origin, nx_read, ny_read, nz_read, dx, dy, dz, atom_type, - atom_charge, atom_pos, data_read, 11); + atom_charge, atom_pos, data_read, 11, false); std::ifstream ifs1("test_write.cube", std::ifstream::binary | std::ifstream::ate); std::ifstream ifs2("./support/chg.cube", std::ifstream::binary | std::ifstream::ate); EXPECT_EQ(ifs1.tellg(), ifs2.tellg()); ifs1.close(); ifs2.close(); } + +/** + * - Tested Functions: + * - write_cube() + * - append == false truncates the file, leaving exactly one frame + * - append == true accumulates one complete frame per call, which is how + * out_app_flag == 1 writes successive ionic steps into a fixed filename + */ +TEST_F(CubeIOTest, WriteCubeAppendFrames) +{ + ModuleIO::read_cube(fn, comment, natom, origin, + nx_read, ny_read, nz_read, + dx, dy, dz, + atom_type, atom_charge, atom_pos, data_read); + + const std::string fname = std::string(testing::TempDir()) + "abacus_cube_append.cube"; + + auto count_frames = [&fname, this]() { + std::ifstream ifs(fname.c_str()); + const std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + std::size_t frames = 0; + std::size_t pos = content.find(comment[0]); + while (pos != std::string::npos) + { + ++frames; + pos = content.find(comment[0], pos + comment[0].size()); + } + return frames; + }; + + // The first frame of a run truncates stale content from any previous run. + ModuleIO::write_cube(fname, + comment, natom, origin, + nx_read, ny_read, nz_read, + dx, dy, dz, atom_type, + atom_charge, atom_pos, data_read, 11, false); + EXPECT_EQ(count_frames(), 1u); + + // Two appended frames stack a second and a third complete cube block. + for (int iframe = 0; iframe < 2; ++iframe) + { + ModuleIO::write_cube(fname, + comment, natom, origin, + nx_read, ny_read, nz_read, + dx, dy, dz, atom_type, + atom_charge, atom_pos, data_read, 11, true); + } + EXPECT_EQ(count_frames(), 3u); + + // Every frame ends with a newline, so the next frame starts on a fresh line. + { + std::ifstream ifs(fname.c_str()); + const std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + ifs.close(); + EXPECT_EQ(content.back(), '\n'); + } + + // Overwriting once more drops the two accumulated frames. + ModuleIO::write_cube(fname, + comment, natom, origin, + nx_read, ny_read, nz_read, + dx, dy, dz, atom_type, + atom_charge, atom_pos, data_read, 11, false); + EXPECT_EQ(count_frames(), 1u); + + std::remove(fname.c_str()); +} diff --git a/source/source_lcao/module_lr/lr_density.cpp b/source/source_lcao/module_lr/lr_density.cpp index f2186ed806f..ecf931298bb 100644 --- a/source/source_lcao/module_lr/lr_density.cpp +++ b/source/source_lcao/module_lr/lr_density.cpp @@ -87,7 +87,7 @@ void LR_Density::write_density_single_state(const double* const* const densit { const std::string data_desc = "LR-TDDFT electron-hole density difference (electrons/Bohr^3)"; ModuleIO::write_vdata_palgrid(pgrid_, density[0], 0, 1, 0, filepath, 0.0, &ucell_, - this->out_chg_precision_, 0, false, true, ofs_running, data_desc); + this->out_chg_precision_, 0, false, true, false, ofs_running, data_desc); } template diff --git a/source/source_lcao/module_lr/test/test_lr_util.cpp b/source/source_lcao/module_lr/test/test_lr_util.cpp index 7aaae902029..928fd4f3414 100644 --- a/source/source_lcao/module_lr/test/test_lr_util.cpp +++ b/source/source_lcao/module_lr/test/test_lr_util.cpp @@ -237,7 +237,8 @@ TEST_F(LRDMTest, SparseWriterKeepsSpinFilesAndDisjointRankKeys) ASSERT_TRUE(input.is_open()); std::string line; std::getline(input, line); - EXPECT_EQ(line, "STEP: 0"); + // Header step is one-based per governance rule 22 (writer receives istep = 0). + EXPECT_EQ(line, "STEP: 1"); std::getline(input, line); EXPECT_EQ(line, "Matrix Dimension of EDM(R): " + std::to_string(dimension)); std::getline(input, line); diff --git a/source/source_lcao/module_lr/utils/exciton_plotter.cpp b/source/source_lcao/module_lr/utils/exciton_plotter.cpp index 037ba0586d7..5bfe7091a76 100644 --- a/source/source_lcao/module_lr/utils/exciton_plotter.cpp +++ b/source/source_lcao/module_lr/utils/exciton_plotter.cpp @@ -510,6 +510,7 @@ void ExcitonPlotter::plot_average_density(const int istate, const std::string 0, false, /*two_fermi*/ false, + false, GlobalV::ofs_running, "exciton density"); } diff --git a/source/source_md/run_md.cpp b/source/source_md/run_md.cpp index 114f9cbb3a5..f7d49433d2c 100644 --- a/source/source_md/run_md.cpp +++ b/source/source_md/run_md.cpp @@ -25,6 +25,7 @@ #include "source_md/module_plumed/plumed_interface.h" #include +#include #ifdef __MPI #include #endif @@ -188,6 +189,32 @@ void md_line(MDCell& mdcell, apply_plumed_forces(mdcell, plumed_forces); }; + /// Effective ionic-step output frequency. + /// Prefer out_freq_ion; fall back to md_restartfreq for backward + /// compatibility when out_freq_ion is not set. + const int eff_output_freq = (param_in.inp.out_freq_ion > 0) + ? param_in.inp.out_freq_ion + : param_in.mdp.md_restartfreq; + + /// out_app_flag == 1 writes one fixed-name "STRU" file. A previous + /// release kept per-step STRU_MD_{step} files inside a STRU/ + /// subdirectory of the output directory, so an MD restarted from those + /// files still sees that directory. Writing the fixed name on top of a + /// directory aborts the run, therefore the legacy numbered layout is + /// kept whenever such a directory is present. + const bool append_stru_allowed = (param_in.inp.out_app_flag == 1); + bool has_legacy_stru_dir = false; + std::string stru_out_dir = PARAM.globalv.global_stru_dir; + if (append_stru_allowed) + { + struct stat stru_path; + if (stat((stru_out_dir + "STRU").c_str(), &stru_path) == 0 && S_ISDIR(stru_path.st_mode)) + { + has_legacy_stru_dir = true; + stru_out_dir += "STRU/"; + } + } + /// md cycle, mohan update 2026-01-04, change '<=' to '<' while ((mdrun->step_ + mdrun->step_rst_) < param_in.mdp.md_nstep && !mdrun->stop) { @@ -280,16 +307,33 @@ void md_line(MDCell& mdcell, mdrun->virial); } - if (param_in.mdp.md_restartfreq > 0 - && (mdrun->step_ + mdrun->step_rst_) % param_in.mdp.md_restartfreq == 0) + const int current_step = mdrun->step_ + mdrun->step_rst_; + const bool do_output_step = eff_output_freq > 0 + && current_step % eff_output_freq == 0; + if (do_output_step) { if (mdcell.has_backing_unitcell()) { mdcell.sync_backing_unitcell(); } - std::stringstream file; - file << PARAM.globalv.global_stru_dir << "STRU_MD_" << mdrun->step_ + mdrun->step_rst_; - mdcell::print_stru_file(mdcell, mdcell.stru_meta(), file.str()); + if (param_in.inp.out_stru > 0) + { + const int stru_step = current_step + 1; + // out_app_flag == 1: single fixed-name STRU accumulates one + // frame per step with a one-based "# MD step N" comment; + // modes 0/2 keep the numbered STRU{stru_step} files. + const bool append_stru = append_stru_allowed && !has_legacy_stru_dir; + const std::string frame_comment = append_stru + ? "# MD step " + std::to_string(stru_step) + "\n\n" + : ""; + std::stringstream file; + file << stru_out_dir << "STRU"; + if (!append_stru) + { + file << stru_step; + } + mdcell::print_stru_file(mdcell, mdcell.stru_meta(), file.str(), frame_comment, append_stru); + } mdrun->write_restart(PARAM.globalv.global_out_dir); } diff --git a/source/source_psi/psi_init_file.cpp b/source/source_psi/psi_init_file.cpp index 29a8619e30b..cc7086d8c8f 100644 --- a/source/source_psi/psi_init_file.cpp +++ b/source/source_psi/psi_init_file.cpp @@ -52,7 +52,7 @@ void psi_init_file::init_psig(T* psig, const int& ik) // mohan update, this is for plane wave, 2025-05-17 const int out_type = 2; - const bool out_app_flag = false; + const int out_app_flag = 0; const bool gamma_only = false; const int istep = -1; diff --git a/source/source_relax/relax_driver.cpp b/source/source_relax/relax_driver.cpp index 7a0b124ee0d..f9afb041374 100644 --- a/source/source_relax/relax_driver.cpp +++ b/source/source_relax/relax_driver.cpp @@ -21,7 +21,7 @@ void Relax_Driver::relax_driver(ModuleESolver::ESolver* p_esolver, // Cache global configuration once so downstream helpers do not each reach // into PARAM / GlobalV. - out_dir_ = PARAM.globalv.global_out_dir; + out_dir_ = PARAM.globalv.global_stru_dir; deepks_setorb_ = PARAM.globalv.deepks_setorb; my_rank_ = GlobalV::MY_RANK; @@ -188,7 +188,7 @@ void Relax_Driver::stru_out(const int istep, const ModuleBase::matrix& stress, const ModuleBase::matrix& force) { - // out_stru is effective for scf/nscf/relax/cell-relax (md writes STRU_MD_* via md_restartfreq) + // out_stru is effective for scf/nscf/relax/cell-relax; md handles STRU output in run_md.cpp if (inp.calculation != "relax" && inp.calculation != "cell-relax" && inp.calculation != "scf" && inp.calculation != "nscf") { @@ -225,13 +225,20 @@ void Relax_Driver::stru_out(const int istep, need_orb, deepks_setorb_, my_rank_, - inp.cal_force); + inp.cal_force, + false); } // Numbered files per out_freq_ion: only meaningful for relaxation calculations if (is_relax && freq_ok) { - const std::string step_file = out_dir_ + "STRU" + std::to_string(istep + 1) + (inp.out_stru == 1 ? "" : ".cif"); + // out_app_flag == 1: single fixed-name STRU accumulates one block per + // step (CIF format truncates to the latest frame instead); modes 0/2 + // keep the numbered STRU{istep+1} files. + const bool append_stru = (inp.out_app_flag == 1) && (inp.out_stru == 1); + const std::string step_file = (inp.out_app_flag == 1) + ? out_dir_ + (inp.out_stru == 1 ? "STRU" : "STRU.cif") + : out_dir_ + "STRU" + std::to_string(istep + 1) + (inp.out_stru == 1 ? "" : ".cif"); relax_stru_io::write_stru(ucell, inp, step_file, @@ -240,7 +247,8 @@ void Relax_Driver::stru_out(const int istep, need_orb, deepks_setorb_, my_rank_, - inp.cal_force); + inp.cal_force, + append_stru); } } @@ -291,7 +299,8 @@ void Relax_Driver::final_out(const int istep, need_orb, deepks_setorb_, my_rank_, - write_force); + write_force, + false); } // relax_nmax == 0 is a valid dry-run mode: no relaxation step was ever diff --git a/source/source_relax/relax_stru_io.cpp b/source/source_relax/relax_stru_io.cpp index 0f191ac644b..21883283cd2 100644 --- a/source/source_relax/relax_stru_io.cpp +++ b/source/source_relax/relax_stru_io.cpp @@ -117,7 +117,8 @@ void write_stru(UnitCell& ucell, const bool need_orb, const bool deepks_setorb, const int my_rank, - const bool has_force) + const bool has_force, + const bool append) { if (inp.out_stru == 1) { @@ -133,7 +134,8 @@ void write_stru(UnitCell& ucell, deepks_setorb, my_rank, force, - has_force); + has_force, + append); } else if (inp.out_stru == 2) { diff --git a/source/source_relax/relax_stru_io.h b/source/source_relax/relax_stru_io.h index 2830f4b09b7..87e37882b07 100644 --- a/source/source_relax/relax_stru_io.h +++ b/source/source_relax/relax_stru_io.h @@ -64,6 +64,8 @@ bool need_orbital(const Input_para& inp); * @param has_force true to emit per-atom f fields; forwarded to * print_stru_file. Callers must pass false when the forces do not * belong to the geometry being written (e.g. early relax exit). + * @param append true to append the structure block (STRU format only, + * for out_app_flag == 1 multi-step runs); CIF always truncates. */ void write_stru(UnitCell& ucell, const Input_para& inp, @@ -73,7 +75,8 @@ void write_stru(UnitCell& ucell, const bool need_orb, const bool deepks_setorb, const int my_rank, - const bool has_force); + const bool has_force, + const bool append); } // namespace relax_stru_io #endif diff --git a/tests/02_NAO_Gamma/scf_out_hxc/Vxc_R_spin0.ref b/tests/02_NAO_Gamma/scf_out_hxc/Vxc_R_spin0.ref index 87ee81fb2e7..e359703fc9e 100644 --- a/tests/02_NAO_Gamma/scf_out_hxc/Vxc_R_spin0.ref +++ b/tests/02_NAO_Gamma/scf_out_hxc/Vxc_R_spin0.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of Vxc_R_spin0(R): 26 Matrix number of Vxc_R_spin0(R): 1 0 0 0 132 diff --git a/tests/02_NAO_Gamma/scf_out_hxc/vhartree_out.ref b/tests/02_NAO_Gamma/scf_out_hxc/eband_hart.ref similarity index 100% rename from tests/02_NAO_Gamma/scf_out_hxc/vhartree_out.ref rename to tests/02_NAO_Gamma/scf_out_hxc/eband_hart.ref diff --git a/tests/02_NAO_Gamma/scf_out_hxc/kinetic_out.ref b/tests/02_NAO_Gamma/scf_out_hxc/eband_kin.ref similarity index 100% rename from tests/02_NAO_Gamma/scf_out_hxc/kinetic_out.ref rename to tests/02_NAO_Gamma/scf_out_hxc/eband_kin.ref diff --git a/tests/02_NAO_Gamma/scf_out_hxc/vpp_local_out.ref b/tests/02_NAO_Gamma/scf_out_hxc/eband_vl.ref similarity index 100% rename from tests/02_NAO_Gamma/scf_out_hxc/vpp_local_out.ref rename to tests/02_NAO_Gamma/scf_out_hxc/eband_vl.ref diff --git a/tests/02_NAO_Gamma/scf_out_hxc/vpp_nonlocal_out.ref b/tests/02_NAO_Gamma/scf_out_hxc/eband_vnl.ref similarity index 100% rename from tests/02_NAO_Gamma/scf_out_hxc/vpp_nonlocal_out.ref rename to tests/02_NAO_Gamma/scf_out_hxc/eband_vnl.ref diff --git a/tests/03_NAO_multik/get_s/dsrxs1.csr.ref b/tests/03_NAO_multik/get_s/dsrxs1.csr.ref index 304c2fc81a8..aaec88d6f49 100644 --- a/tests/03_NAO_multik/get_s/dsrxs1.csr.ref +++ b/tests/03_NAO_multik/get_s/dsrxs1.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of dHx(R): 112 Matrix number of dHx(R): 3 -1 0 0 268 diff --git a/tests/03_NAO_multik/get_s/dsrys1.csr.ref b/tests/03_NAO_multik/get_s/dsrys1.csr.ref index e596e253c0a..dfdfb209dce 100644 --- a/tests/03_NAO_multik/get_s/dsrys1.csr.ref +++ b/tests/03_NAO_multik/get_s/dsrys1.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of dHy(R): 112 Matrix number of dHy(R): 3 -1 0 0 192 diff --git a/tests/03_NAO_multik/get_s/dsrzs1.csr.ref b/tests/03_NAO_multik/get_s/dsrzs1.csr.ref index 8b4a094f573..16c5b952134 100644 --- a/tests/03_NAO_multik/get_s/dsrzs1.csr.ref +++ b/tests/03_NAO_multik/get_s/dsrzs1.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of dHz(R): 112 Matrix number of dHz(R): 3 -1 0 0 192 diff --git a/tests/03_NAO_multik/get_s/sr_nao.csr.ref b/tests/03_NAO_multik/get_s/sr_nao.csr.ref index 00c283dd399..3c6902b76a6 100644 --- a/tests/03_NAO_multik/get_s/sr_nao.csr.ref +++ b/tests/03_NAO_multik/get_s/sr_nao.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of S(R): 112 Matrix number of S(R): 3 -1 0 0 268 diff --git a/tests/03_NAO_multik/nscf_out_band_pband/refOrbital b/tests/03_NAO_multik/nscf_out_band_pband/refNaoTxt similarity index 100% rename from tests/03_NAO_multik/nscf_out_band_pband/refOrbital rename to tests/03_NAO_multik/nscf_out_band_pband/refNaoTxt diff --git a/tests/03_NAO_multik/nscf_out_hsr_tr_rr/rr_nao.txt.ref b/tests/03_NAO_multik/nscf_out_hsr_tr_rr/rr_nao.txt.ref index db8fe0b5461..42b80ea265e 100644 --- a/tests/03_NAO_multik/nscf_out_hsr_tr_rr/rr_nao.txt.ref +++ b/tests/03_NAO_multik/nscf_out_hsr_tr_rr/rr_nao.txt.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of r(R): 6 Matrix number of r(R): 9 -1 0 0 diff --git a/tests/03_NAO_multik/nscf_out_hsr_tr_rr/tr_nao.csr.ref b/tests/03_NAO_multik/nscf_out_hsr_tr_rr/tr_nao.csr.ref index 00e95fe978b..bb0639f3d07 100644 --- a/tests/03_NAO_multik/nscf_out_hsr_tr_rr/tr_nao.csr.ref +++ b/tests/03_NAO_multik/nscf_out_hsr_tr_rr/tr_nao.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of T(R): 6 Matrix number of T(R): 9 -1 0 0 15 diff --git a/tests/03_NAO_multik/scf_out_dh_t/dhrxs1_nao.csr.ref b/tests/03_NAO_multik/scf_out_dh_t/dhrxs1_nao.csr.ref index 1a624c49e90..90eb4f45d82 100644 --- a/tests/03_NAO_multik/scf_out_dh_t/dhrxs1_nao.csr.ref +++ b/tests/03_NAO_multik/scf_out_dh_t/dhrxs1_nao.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of dHx(R): 13 Matrix number of dHx(R): 1 0 0 0 24 diff --git a/tests/03_NAO_multik/scf_out_dh_t/dhrys1_nao.csr.ref b/tests/03_NAO_multik/scf_out_dh_t/dhrys1_nao.csr.ref index 1dc059da783..a5286bde5f3 100644 --- a/tests/03_NAO_multik/scf_out_dh_t/dhrys1_nao.csr.ref +++ b/tests/03_NAO_multik/scf_out_dh_t/dhrys1_nao.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of dHy(R): 13 Matrix number of dHy(R): 1 0 0 0 24 diff --git a/tests/03_NAO_multik/scf_out_dh_t/dhrzs1_nao.csr.ref b/tests/03_NAO_multik/scf_out_dh_t/dhrzs1_nao.csr.ref index ccfe6e99635..8c1fa290e1e 100644 --- a/tests/03_NAO_multik/scf_out_dh_t/dhrzs1_nao.csr.ref +++ b/tests/03_NAO_multik/scf_out_dh_t/dhrzs1_nao.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of dHz(R): 13 Matrix number of dHz(R): 1 0 0 0 20 diff --git a/tests/03_NAO_multik/scf_out_dh_t/tr_nao.csr.ref b/tests/03_NAO_multik/scf_out_dh_t/tr_nao.csr.ref index 1c241cae74c..52a639bfda5 100644 --- a/tests/03_NAO_multik/scf_out_dh_t/tr_nao.csr.ref +++ b/tests/03_NAO_multik/scf_out_dh_t/tr_nao.csr.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of T(R): 13 Matrix number of T(R): 1 0 0 0 21 diff --git a/tests/03_NAO_multik/scf_out_hsr_spin4/rr_nao.txt.ref b/tests/03_NAO_multik/scf_out_hsr_spin4/rr_nao.txt.ref index e735fc09448..26635300247 100644 --- a/tests/03_NAO_multik/scf_out_hsr_spin4/rr_nao.txt.ref +++ b/tests/03_NAO_multik/scf_out_hsr_spin4/rr_nao.txt.ref @@ -1,4 +1,4 @@ -STEP: 0 +STEP: 1 Matrix Dimension of r(R): 26 Matrix number of r(R): 7 -1 0 0 diff --git a/tests/08_RI/scf_out_xc_multik/vhartree_out.ref b/tests/08_RI/scf_out_xc_multik/eband_hart.ref similarity index 100% rename from tests/08_RI/scf_out_xc_multik/vhartree_out.ref rename to tests/08_RI/scf_out_xc_multik/eband_hart.ref diff --git a/tests/08_RI/scf_out_xc_multik/kinetic_out.ref b/tests/08_RI/scf_out_xc_multik/eband_kin.ref similarity index 100% rename from tests/08_RI/scf_out_xc_multik/kinetic_out.ref rename to tests/08_RI/scf_out_xc_multik/eband_kin.ref diff --git a/tests/08_RI/scf_out_xc_multik/vpp_local_out.ref b/tests/08_RI/scf_out_xc_multik/eband_vl.ref similarity index 100% rename from tests/08_RI/scf_out_xc_multik/vpp_local_out.ref rename to tests/08_RI/scf_out_xc_multik/eband_vl.ref diff --git a/tests/08_RI/scf_out_xc_multik/vpp_nonlocal_out.ref b/tests/08_RI/scf_out_xc_multik/eband_vnl.ref similarity index 100% rename from tests/08_RI/scf_out_xc_multik/vpp_nonlocal_out.ref rename to tests/08_RI/scf_out_xc_multik/eband_vnl.ref diff --git a/tests/integrate/validation_tools/check_librpa_producer.py b/tests/integrate/validation_tools/check_librpa_producer.py index ee8a47e6e98..3396870cdf9 100644 --- a/tests/integrate/validation_tools/check_librpa_producer.py +++ b/tests/integrate/validation_tools/check_librpa_producer.py @@ -63,8 +63,9 @@ def _numeric_tokens(path): def _compare_text(actual, reference, tolerance): - actual_lines = actual.read_text().splitlines() - reference_lines = reference.read_text().splitlines() + # ignore provenance comment lines starting with '#' on both sides + actual_lines = [line for line in actual.read_text().splitlines() if not line.lstrip().startswith("#")] + reference_lines = [line for line in reference.read_text().splitlines() if not line.lstrip().startswith("#")] # Some ABACUS text writers leave an extra blank record at EOF. It has no # parser-visible meaning, so keep the value comparison strict while # ignoring only trailing empty records. @@ -153,7 +154,8 @@ def _check_stru(path, minimum_symmetry_rows, spin_symmetry_rows=None): raise ProducerContractError(str(error)) def _check_wfc_nao(path): """Validate the text LCAO wavefunction format consumed by GW preprocessing.""" - lines = [line.strip() for line in path.read_text().splitlines() if line.strip()] + # ignore provenance comment lines starting with '#' + lines = [line.strip() for line in path.read_text().splitlines() if line.strip() and not line.strip().startswith("#")] if len(lines) < 6: raise ProducerContractError("{} is too short for an LCAO wavefunction".format(path)) diff --git a/tests/integrate/validation_tools/compare_hsr_binary.py b/tests/integrate/validation_tools/compare_hsr_binary.py index 2e3e295ed6e..b306a5f1569 100644 --- a/tests/integrate/validation_tools/compare_hsr_binary.py +++ b/tests/integrate/validation_tools/compare_hsr_binary.py @@ -20,8 +20,10 @@ def read_native(stream, fmt, description): def read_binary(filename, value_type): with open(filename, "rb") as stream: step, nbasis, nr = read_native(stream, "@iii", "record header") - if step != 0: - raise ValueError("expected zero-based ionic step 0, found {}".format(step)) + # The binary step field follows governance rule 22: the first ionic + # step is printed one-based as 1. + if step != 1: + raise ValueError("expected one-based ionic step 1, found {}".format(step)) if nbasis <= 0 or nr < 0: raise ValueError("invalid matrix dimensions nbasis={} nr={}".format(nbasis, nr)) diff --git a/tests/integrate/validation_tools/librpa_wavefunctions.py b/tests/integrate/validation_tools/librpa_wavefunctions.py index 15303a35ef3..f643b0d647d 100644 --- a/tests/integrate/validation_tools/librpa_wavefunctions.py +++ b/tests/integrate/validation_tools/librpa_wavefunctions.py @@ -41,7 +41,8 @@ def check_ks_nao(path, nao_dir, tolerance, spinor=False): else: name = "wfk{}_nao.txt".format(ik) if nspin == 1 else "wfk{}s{}_nao.txt".format(ik, spin) source = nao_dir / name - lines = [line.strip() for line in source.read_text().splitlines() if line.strip()] + # ignore provenance comment lines starting with '#' + lines = [line.strip() for line in source.read_text().splitlines() if line.strip() and not line.strip().startswith("#")] if len(lines) < 4 or "(index of k points)" not in lines[0]: raise ValueError("{} has an invalid text KS header".format(source)) text_index = ik if spinor else ik + (spin - 1) * nk diff --git a/tests/integrate/validation_tools/props_cube.sh b/tests/integrate/validation_tools/props_cube.sh index 1dfd83270ff..2b1b78526b5 100644 --- a/tests/integrate/validation_tools/props_cube.sh +++ b/tests/integrate/validation_tools/props_cube.sh @@ -154,7 +154,7 @@ if ! test -z "$has_lowf" && [ "$has_lowf" == 1 ]; then 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 + input_file=wfk1g3_nao else wfc_name=wfk2_nao input_file=wfk2_nao diff --git a/tests/integrate/validation_tools/props_mat.sh b/tests/integrate/validation_tools/props_mat.sh index 5128c3dd379..0765baaa6b0 100644 --- a/tests/integrate/validation_tools/props_mat.sh +++ b/tests/integrate/validation_tools/props_mat.sh @@ -37,6 +37,19 @@ compare_hr_csr(){ python3 $COMPARE_SCRIPT <(sed -n "$pick" "$ref") <(sed -n "$pick" "$cal") 3 } +# Band-energy-term files start with comment lines (version, timestamp, ionic +# step, formula, units) that legitimately change between runs, so the header +# is stripped and only the numeric body is compared. +compare_eband_energy(){ + local ref=$1 + local cal=$2 + if ! test -f "$ref" || ! test -f "$cal"; then + echo "Error: can not find file $ref or $cal" + return 1 + fi + python3 $COMPARE_SCRIPT <(sed '/^[[:space:]]*#/d' "$ref") <(sed '/^[[:space:]]*#/d' "$cal") 4 +} + run_mat_dm1_props(){ #------------------------------- @@ -69,8 +82,8 @@ fi # echo $out_pband #------------------------------- if ! test -z "$out_pband" && [ $out_pband == 1 ]; then - orbref=refOrbital - orbcal=OUT.autotest/Orbital + orbref=refNaoTxt + orbcal=OUT.autotest/nao.txt python3 $COMPARE_SCRIPT $orbref $orbcal 8 echo "CompareOrb_pass $?" >>$props_result_file fi @@ -106,8 +119,18 @@ fi 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 + # Every ionic step in band.txt is preceded by a provenance comment block + # ("# ABACUS version: ...", "# Written at ...", "# IONIC STEP n"), so the + # reference and the computed file are compared after those lines are + # stripped. A missing file must be reported explicitly, because process + # substitution would hand CompareFile.py an empty stream and pass silently. + if ! test -f "$bandref" || ! test -f "$bandcal"; then + echo "Error: can not find file $bandref or $bandcal" + echo "CompareBand_pass 1" >>$props_result_file + else + python3 $COMPARE_SCRIPT <(sed '/^[[:space:]]*#/d' "$bandref") <(sed '/^[[:space:]]*#/d' "$bandcal") 8 + echo "CompareBand_pass $?" >>$props_result_file + fi fi #-------------------------------- @@ -236,21 +259,13 @@ 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 + compare_eband_energy eband_kin.ref OUT.autotest/eband_kin.txt echo "CompareOrbKinetic_pass $?" >>$props_result_file - vlref=vpp_local_out.ref - vlcal=OUT.autotest/vpp_local_out.dat - python3 $COMPARE_SCRIPT $vlref $vlcal 4 + compare_eband_energy eband_vl.ref OUT.autotest/eband_vl.txt echo "CompareOrbVL_pass $?" >>$props_result_file - vnlref=vpp_nonlocal_out.ref - vnlcal=OUT.autotest/vpp_nonlocal_out.dat - python3 $COMPARE_SCRIPT $vnlref $vnlcal 4 + compare_eband_energy eband_vnl.ref OUT.autotest/eband_vnl.txt echo "CompareOrbVNL_pass $?" >>$props_result_file - vhref=vhartree_out.ref - vhcal=OUT.autotest/vhartree_out.dat - python3 $COMPARE_SCRIPT $vhref $vhcal 4 + compare_eband_energy eband_hart.ref OUT.autotest/eband_hart.txt echo "CompareOrbVHartree_pass $?" >>$props_result_file fi diff --git a/tools/01_NAO_generation/qo/postprocess.py b/tools/01_NAO_generation/qo/postprocess.py index d4b7aeae615..f90a2d7238d 100644 --- a/tools/01_NAO_generation/qo/postprocess.py +++ b/tools/01_NAO_generation/qo/postprocess.py @@ -72,7 +72,8 @@ def read_lowf(flowf): np.ndarray: k-vector """ with open(flowf, 'r') as f: - lines = [line.strip() for line in f.readlines()] + # ignore provenance comment lines starting with '#' + lines = [line.strip() for line in f.readlines() if line.strip() and not line.strip().startswith('#')] for line in lines: if line.endswith("(number of bands)"): nband = int(line.split()[0])