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/Makefile.Objects b/source/Makefile.Objects
index ced2b16b823..42792c9a29c 100644
--- a/source/Makefile.Objects
+++ b/source/Makefile.Objects
@@ -234,6 +234,7 @@ OBJS_CELL=atom_pseudo.o\
update_cell.o\
bcast_cell.o\
read_stru.o\
+ stru_frame_io.o\
read_atom_species.o\
sep.o\
sep_cell.o\
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..17b9f4c3167 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)
@@ -86,7 +86,11 @@ std::string filename_output(
else
{
// mohan add 20250921
- if(property=="hk" || property=="sk" || property=="tk")
+ // A property whose name already ends in 'k' denotes a k-space
+ // quantity (hk, sk, tk, vhk, vlk, vnlk, vxck, vexxk, ...); the
+ // k-point index then needs no extra 'k' prefix, otherwise the
+ // filename would contain a doubled 'k' (e.g. vhkk1).
+ if (!property.empty() && property.back() == 'k')
{
kpoint_block = std::to_string(ik0+1);
}
@@ -97,14 +101,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 4bff3c97cbd..d6eeb48b714 100644
--- a/source/source_base/test/CMakeLists.txt
+++ b/source/source_base/test/CMakeLists.txt
@@ -281,3 +281,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/CMakeLists.txt b/source/source_cell/CMakeLists.txt
index e35cb1025dc..2ec5c1541a2 100644
--- a/source/source_cell/CMakeLists.txt
+++ b/source/source_cell/CMakeLists.txt
@@ -38,6 +38,7 @@ add_library(
magnetism.cpp
bcast_cell.cpp
read_stru.cpp
+ stru_frame_io.cpp
print_cell.cpp
read_atom_species.cpp
sep.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/stru_frame_io.cpp b/source/source_cell/stru_frame_io.cpp
new file mode 100644
index 00000000000..250175cc96d
--- /dev/null
+++ b/source/source_cell/stru_frame_io.cpp
@@ -0,0 +1,59 @@
+/**
+ * @file stru_frame_io.cpp
+ * @brief Implementation of the cumulative STRU frame extraction helpers.
+ */
+#include "stru_frame_io.h"
+
+#include
+#include
+
+namespace stru_frame_io
+{
+bool extract_last_frame(const std::string& cumulative_path, const std::string& frame_path)
+{
+ std::ifstream ifs(cumulative_path.c_str());
+ if (!ifs)
+ {
+ return false;
+ }
+ const std::string frame_marker = "# MD step ";
+ std::string line;
+ std::string last_frame;
+ std::string current_frame;
+ bool in_frame = false;
+ while (std::getline(ifs, line))
+ {
+ const bool is_marker = line.compare(0, frame_marker.size(), frame_marker) == 0;
+ if (is_marker)
+ {
+ if (in_frame)
+ {
+ last_frame = current_frame;
+ }
+ current_frame.clear();
+ in_frame = true;
+ continue;
+ }
+ if (in_frame)
+ {
+ current_frame += line;
+ current_frame += '\n';
+ }
+ }
+ if (in_frame)
+ {
+ last_frame = current_frame;
+ }
+ if (last_frame.empty())
+ {
+ return false;
+ }
+ std::ofstream ofs(frame_path.c_str(), std::ios::out | std::ios::trunc);
+ if (!ofs)
+ {
+ return false;
+ }
+ ofs << last_frame;
+ return ofs.good();
+}
+} // namespace stru_frame_io
diff --git a/source/source_cell/stru_frame_io.h b/source/source_cell/stru_frame_io.h
new file mode 100644
index 00000000000..8f2456d7683
--- /dev/null
+++ b/source/source_cell/stru_frame_io.h
@@ -0,0 +1,31 @@
+#ifndef STRU_FRAME_IO_H
+#define STRU_FRAME_IO_H
+
+#include
+
+/**
+ * @file stru_frame_io.h
+ * @brief Helpers to read frames from fixed-name cumulative STRU files.
+ *
+ * In out_app_flag == 1 mode run_md appends one structure frame per output
+ * step to a single "STRU" file, each frame introduced by a one-based
+ * "# MD step N" comment line. Restart readers such as MDCellReader::read_stru
+ * expect a single-frame STRU file, so the frames written this way must be
+ * split before they can be consumed. This header owns that frame-splitting
+ * knowledge so callers outside source_cell do not parse STRU text themselves.
+ */
+namespace stru_frame_io
+{
+/// @brief Extract the last MD frame from a fixed-name cumulative STRU file.
+/// @param cumulative_path path of the mode-1 "STRU" file holding one frame per
+/// output step, each introduced by a "# MD step N" comment line
+/// @param frame_path path of the single-frame file to (over)write with the
+/// last frame; the caller passes this path to the structure readers
+/// @return true when at least one frame was found and written
+/// @details The last frame is copied verbatim, without its "# MD step N"
+/// comment line, into frame_path. The side file is rewritten on every call so
+/// repeated restarts always see the most recent frame.
+bool extract_last_frame(const std::string& cumulative_path, const std::string& frame_path);
+} // namespace stru_frame_io
+
+#endif // STRU_FRAME_IO_H
diff --git a/source/source_cell/test/CMakeLists.txt b/source/source_cell/test/CMakeLists.txt
index 77632f4f5f0..b64022f7c14 100644
--- a/source/source_cell/test/CMakeLists.txt
+++ b/source/source_cell/test/CMakeLists.txt
@@ -58,6 +58,16 @@ addtest(
test_cell_geometry.cpp
../cell_geometry.cpp)
+addtest(
+ TARGET
+ MODULE_CELL_stru_frame_io
+ LIBS
+ base
+ device
+ SOURCES
+ test_stru_frame_io.cpp
+ ../stru_frame_io.cpp)
+
if(NOT ENABLE_MPI)
return()
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_cell/test/test_stru_frame_io.cpp b/source/source_cell/test/test_stru_frame_io.cpp
new file mode 100644
index 00000000000..e674834775e
--- /dev/null
+++ b/source/source_cell/test/test_stru_frame_io.cpp
@@ -0,0 +1,89 @@
+/**
+ * @file test_stru_frame_io.cpp
+ * @brief Unit test for stru_frame_io: last-frame extraction from a fixed-name
+ * cumulative STRU file written in out_app_flag == 1 mode.
+ */
+#include "source_cell/stru_frame_io.h"
+
+#include
+#include
+#include
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source_base/fs_compat.h"
+
+class StruFrameIoTest : public testing::Test
+{
+ protected:
+ std::string case_dir;
+
+ void SetUp() override
+ {
+ case_dir = testing::TempDir() + "/abacus_stru_frame_io/";
+ ModuleBase::make_directory(case_dir);
+ }
+
+ std::string read_all(const std::string& path)
+ {
+ std::ifstream ifs(path.c_str());
+ std::stringstream content;
+ content << ifs.rdbuf();
+ return content.str();
+ }
+};
+
+TEST_F(StruFrameIoTest, ExtractLastFramePicksNewest)
+{
+ const std::string cumulative = case_dir + "STRU";
+ std::ofstream ofs(cumulative.c_str());
+ ofs << "# MD step 1\n\nATOMIC_SPECIES\nSi 28.0855\n";
+ ofs << "# MD step 2\n\nATOMIC_SPECIES\nSi 28.0855\n\nLATTICE_CONSTANT\n10.2\n";
+ ofs.close();
+
+ const std::string frame = case_dir + "STRU_restart_frame";
+ EXPECT_TRUE(stru_frame_io::extract_last_frame(cumulative, frame));
+
+ const std::string content = read_all(frame);
+ // the extracted file must hold only the last frame, not the earlier one
+ EXPECT_EQ(content.find("# MD step"), std::string::npos);
+ EXPECT_NE(content.find("LATTICE_CONSTANT"), std::string::npos);
+}
+
+TEST_F(StruFrameIoTest, ExtractLastFrameRewritesOnSecondCall)
+{
+ const std::string cumulative = case_dir + "STRU";
+ std::ofstream ofs(cumulative.c_str());
+ ofs << "# MD step 1\n\nATOMIC_SPECIES\nSi 28.0855\n";
+ ofs.close();
+
+ const std::string frame = case_dir + "STRU_restart_frame";
+ EXPECT_TRUE(stru_frame_io::extract_last_frame(cumulative, frame));
+
+ // simulate a later restart: append another frame and re-extract
+ std::ofstream app(cumulative.c_str(), std::ios::app);
+ app << "# MD step 2\n\nLATTICE_CONSTANT\n10.9\n";
+ app.close();
+ EXPECT_TRUE(stru_frame_io::extract_last_frame(cumulative, frame));
+
+ const std::string content = read_all(frame);
+ EXPECT_NE(content.find("10.9"), std::string::npos);
+}
+
+TEST_F(StruFrameIoTest, ExtractLastFrameFailsOnMissingFile)
+{
+ const std::string missing = case_dir + "NO_SUCH_STRU";
+ const std::string frame = case_dir + "STRU_restart_frame";
+ EXPECT_FALSE(stru_frame_io::extract_last_frame(missing, frame));
+}
+
+TEST_F(StruFrameIoTest, ExtractLastFrameFailsWithoutMarker)
+{
+ const std::string cumulative = case_dir + "STRU";
+ std::ofstream ofs(cumulative.c_str());
+ ofs << "ATOMIC_SPECIES\nSi 28.0855\n";
+ ofs.close();
+
+ const std::string frame = case_dir + "STRU_restart_frame";
+ EXPECT_FALSE(stru_frame_io::extract_last_frame(cumulative, frame));
+}
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 2622e593074..86733781d84 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..b4147245715 100644
--- a/source/source_esolver/esolver_ks_lcaopw.cpp
+++ b/source/source_esolver/esolver_ks_lcaopw.cpp
@@ -284,7 +284,12 @@ namespace ModuleESolver
bool cal_exx = false;
double hybrid_alpha = 0.0;
#endif
+ // one-shot output after the whole run; the single frame is step 1
+ const int istep_xc = 0;
+ const double etot_xc = this->pelec->f_en.etot;
ModuleIO::write_Vxc_LIP(this->inp_->nspin,
+ istep_xc,
+ etot_xc,
PARAM.globalv.nlocal,
GlobalV::DRANK,
*this->stp.template get_psi_t(),
@@ -299,7 +304,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..7b6206e0bd8 100644
--- a/source/source_io/module_ctrl/ctrl_output_pw.cpp
+++ b/source/source_io/module_ctrl/ctrl_output_pw.cpp
@@ -62,8 +62,13 @@ void ModuleIO::ctrl_iter_pw(const int istep,
if (out_wfc_flag && (inp.out_wfc_pw == 1 || inp.out_wfc_pw == 2))
{
stp.sync_cpu();
+ // istep_in is only the filename sentinel (-1 omits the step suffix);
+ // pass the real ionic step separately so append mode keeps
+ // accumulating frames even when the filename carries no step.
+ const int append_step = istep;
ModuleIO::write_wfc_pw(istep_in,
iter_in,
+ append_step,
GlobalV::KPAR,
GlobalV::MY_POOL,
GlobalV::MY_RANK,
@@ -73,8 +78,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 +180,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 +194,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 +300,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..7441f419572 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,19 +55,25 @@ 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);
}
- // 3) print out exchange-correlation potential
- if (inp.out_mat_xc)
+ // 3) print out exchange-correlation potential: the band-decomposed orbital
+ // energies (eband_vxc.txt etc.) are produced for either switch, while
+ // the full MO-basis matrices (vxc_nao.txt etc.) are only for out_mat_xc.
+ const bool out_vxc_orb = inp.out_mat_xc || inp.out_eband;
+ if (out_vxc_orb)
{
bool cal_exx = exx_info.info_global.cal_exx;
+ const double etot_xc = pelec->f_en.etot;
#ifdef __EXX
auto* hexxd_ptr = exx_nao.exd ? &exx_nao.exd->get_Hexxs() : nullptr;
auto* hexxc_ptr = exx_nao.exc ? &exx_nao.exc->get_Hexxs() : nullptr;
#endif
ModuleIO::write_Vxc(inp.nspin,
+ istep,
+ etot_xc,
PARAM.globalv.nlocal,
GlobalV::DRANK,
&pv,
@@ -84,6 +91,8 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell
gd,
inp.dft_plus_u,
PARAM.globalv.gamma_only_local,
+ inp.out_mat_xc,
+ PARAM.globalv.global_mat_dir,
PARAM.globalv.global_out_dir,
inp.out_ndigits,
inp.ks_solver,
@@ -119,7 +128,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
@@ -136,11 +145,14 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell
// write eband terms
- if (inp.out_eband_terms)
+ if (inp.out_eband)
{
+ const double etot = pelec->f_en.etot;
ModuleIO::write_eband_terms(inp.nspin,
+ istep,
+ etot,
+ GlobalV::MY_RANK,
PARAM.globalv.nlocal,
- GlobalV::DRANK,
&pv,
*psi,
ucell,
@@ -154,13 +166,7 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell
pelec->wg,
gd,
orb.cutoffs(),
- two_center_bundle,
- exx_info
-#ifdef __EXX
- ,
- exx_nao.exd ? &exx_nao.exd->get_Hexxs() : nullptr,
- exx_nao.exc ? &exx_nao.exc->get_Hexxs() : nullptr
-#endif
+ two_center_bundle
);
}
@@ -173,6 +179,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 +203,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 +227,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..132fd428433 100644
--- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp
+++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp
@@ -53,8 +53,8 @@ void setup_exx_h_params(ModuleIO::WriteHParams& h_params, Exx_NAO& exx_nao,
// EXX-H output is unsupported. Reject it explicitly here so the request cannot be silently
// dropped (the WARNING_QUIT inside write_h_exx is unreachable at multi-k).
ModuleBase::WARNING_QUIT("setup_exx_h_params",
- "out_mat_h_exx is only supported for gamma-only: the V^EXX(R) "
- "output is not available at multi-k. Use gamma_only.");
+ "out_mat_exxk (legacy out_mat_h_exx) is only supported for gamma-only: "
+ "the V^EXX(R) output is not available at multi-k. Use gamma_only.");
}
template <>
@@ -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,33 +455,34 @@ 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;
h_params.ofs_running = &ofs_running;
- if (inp.out_mat_h_t[0])
- {
- ModuleIO::write_h_t(h_params);
- }
- if (inp.out_mat_h_vnl[0])
+ // out_mat_h_t is a legacy alias for out_mat_tk (resolved at input
+ // normalization); the dedicated write_h_t path is removed because it
+ // duplicated the T(k) output of the out_mat_tk block below.
+ if (inp.out_mat_vnlk[0])
{
ModuleIO::write_h_vnl(h_params);
}
- if (inp.out_mat_h_vl[0])
+ if (inp.out_mat_vlk[0])
{
ModuleIO::write_h_vl(h_params);
}
- if (inp.out_mat_h_vh[0])
+ if (inp.out_mat_vhk[0])
{
ModuleIO::write_h_vh(h_params);
}
- if (inp.out_mat_h_vxc[0])
+ if (inp.out_mat_vxck[0])
{
ModuleIO::write_h_vxc(h_params);
}
#ifdef __EXX
- if (inp.out_mat_h_exx[0] && exx_info.info_global.cal_exx)
+ if (inp.out_mat_exxk[0] && exx_info.info_global.cal_exx)
{
// V^EXX(R) output is wired for the gamma (TK==double) exx interfaces.
setup_exx_h_params(h_params, exx_nao, exx_info);
@@ -509,7 +512,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 +530,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 +561,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..287ebd9c893 100644
--- a/source/source_io/module_dm/write_dmk.cpp
+++ b/source/source_io/module_dm/write_dmk.cpp
@@ -188,11 +188,23 @@ 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. The first written step
+ // (istep == 0) truncates so a fresh run drops records left by a previous
+ // run; only later steps (istep > 0) append.
+ 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 +251,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 +333,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 +369,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 +380,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..a61d6f191ea 100644
--- a/source/source_io/module_dos/write_pdos_text.cpp
+++ b/source/source_io/module_dos/write_pdos_text.cpp
@@ -17,24 +17,37 @@ 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.
+ // The first written step (istep == 0) truncates so a fresh run does not
+ // keep records from a previous run; only later steps (istep > 0) append.
+ 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..153795d3907 100644
--- a/source/source_io/module_energy/write_eband_terms.cpp
+++ b/source/source_io/module_energy/write_eband_terms.cpp
@@ -1,16 +1,17 @@
#include "write_eband_terms.h"
-#include "source_io/module_hs/vxc_op_mat.h"
+#include "source_io/module_hs/vxc_op_tools.h"
#include "source_io/module_parameter/parameter.h"
#include "source_lcao/module_operator_lcao/ekinetic.h"
#include "source_lcao/module_operator_lcao/nonlocal.h"
+#include "source_lcao/module_operator_lcao/veff_lcao.h"
#include "source_cell/module_neighbor/sltk_grid_driver.h"
+#include "source_base/constants.h"
#include "source_psi/psi.h"
#include "source_cell/unitcell.h"
#include "source_cell/klist.h"
#include "source_estate/module_pot/potential_new.h"
-#include "source_hamilt/module_xc/exx_info.h"
#include "source_basis/module_nao/two_center_bundle.h"
#include "source_basis/module_ao/parallel_orbitals.h"
#include "source_estate/module_charge/charge.h"
@@ -18,16 +19,16 @@
#include "source_hamilt/module_surchem/surchem.h"
#include "source_basis/module_pw/pw_basis.h"
-#ifdef __EXX
-#include "source_lcao/module_operator_lcao/op_exx_lcao.h"
-#endif
+#include
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,
const psi::Psi& psi,
const UnitCell& ucell,
@@ -41,18 +42,14 @@ void write_eband_terms(const int nspin,
const ModuleBase::matrix& wg,
Grid_Driver& gd,
const std::vector& orb_cutoff,
- const TwoCenterBundle& two_center_bundle,
- const Exx_Info& exx_info
-#ifdef __EXX
- ,
- std::vector>>>* Hexxd,
- std::vector>>>>* Hexxc
-#endif
+ const TwoCenterBundle& two_center_bundle
)
{
// 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 +65,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 +118,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 +150,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 +170,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,77 +203,33 @@ 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);
- }
-
- // 5. xc (including exx)
- if (!PARAM.inp.out_mat_xc) // avoid duplicate output
- {
- bool cal_exx = exx_info.info_global.cal_exx;
- write_Vxc(nspin,
- nbasis,
- drank,
- pv,
- psi,
- ucell,
- sf,
- solvent,
- rho_basis,
- rhod_basis,
- vloc,
- chg,
- kv,
- orb_cutoff,
- wg,
- gd,
- PARAM.inp.dft_plus_u,
- PARAM.globalv.gamma_only_local,
- PARAM.globalv.global_out_dir,
- PARAM.inp.out_ndigits,
- PARAM.inp.ks_solver,
- cal_exx,
- exx_info
-#ifdef __EXX
- ,
- Hexxd,
- Hexxc
-#endif
- );
+ write_term(e_orb_hartree, "eband_hart.txt", "V_H");
}
+ // NOTE: the XC term (eband_vxc.txt etc.) is written by write_Vxc from
+ // ctrl_runner_lcao.cpp for both out_mat_xc and out_eband,
+ // so it is intentionally not handled here.
}
// 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 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&
-#ifdef __EXX
- , std::vector>>>*,
- std::vector>>>>*
-#endif
+ const std::vector&, const TwoCenterBundle&
);
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 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&
-#ifdef __EXX
- , std::vector>>>*,
- std::vector>>>>*
-#endif
+ const std::vector&, const TwoCenterBundle&
);
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 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&
-#ifdef __EXX
- , std::vector>>>*,
- std::vector>>>>*
-#endif
+ const std::vector&, const TwoCenterBundle&
);
} // namespace ModuleIO
diff --git a/source/source_io/module_energy/write_eband_terms.h b/source/source_io/module_energy/write_eband_terms.h
index 54d980a8701..067b2966b1f 100644
--- a/source/source_io/module_energy/write_eband_terms.h
+++ b/source/source_io/module_energy/write_eband_terms.h
@@ -4,10 +4,6 @@
#include "source_base/matrix.h"
#include "source_psi/psi.h"
-#include
-#include
-#include