diff --git a/AGENTS.md b/AGENTS.md
index 56606c2ff40..648ae386896 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/density_matrix.md b/docs/advanced/elec_properties/density_matrix.md
index 2155af8a239..df755c3e723 100644
--- a/docs/advanced/elec_properties/density_matrix.md
+++ b/docs/advanced/elec_properties/density_matrix.md
@@ -6,11 +6,11 @@ out_dmk 1
```
After finishing the calculation, the density matrix is written into `OUT.${suffix}/`.
-For current develop versions:
-- gamma-only (`gamma_only = 1`): `dmg1_nao.txt` (`nspin=1/4`) or `dms1g1_nao.txt` and `dms2g1_nao.txt` (`nspin=2`)
-- multi-k (`gamma_only = 0`): `dmk1g1_nao.txt`, `dmk2g1_nao.txt`, ... (`nspin=1/4`) or `dmk1s1g1_nao.txt`, `dmk1s2g1_nao.txt`, ... (`nspin=2`)
+For current develop versions, the default filenames (with `out_app_flag = 0` or `1`) are:
+- gamma-only (`gamma_only = 1`): `dm_nao.txt` (`nspin=1/4`) or `dms1_nao.txt` and `dms2_nao.txt` (`nspin=2`)
+- multi-k (`gamma_only = 0`): `dmk1_nao.txt`, `dmk2_nao.txt`, ... (`nspin=1/4`) or `dmk1s1_nao.txt`, `dmk1s2_nao.txt`, ... (`nspin=2`)
-Here `g{istep}` denotes the geometry/step index in the output filename.
+With `out_app_flag = 2`, a one-based geometry-step suffix `g{istep}` is inserted before `_nao` (for example `dmk1g1_nao.txt`). With `out_app_flag = 0` the fixed file is truncated and overwritten at every write, and with `out_app_flag = 1` records are appended to the fixed file.
For 3.10-LTS, the corresponding keyword is `out_dm`, and file names follow the legacy style such as `SPIN1_DM` and `SPIN2_DM`.
diff --git a/docs/advanced/elec_properties/dos.md b/docs/advanced/elec_properties/dos.md
index 47076b54cd5..969998a46ff 100644
--- a/docs/advanced/elec_properties/dos.md
+++ b/docs/advanced/elec_properties/dos.md
@@ -68,7 +68,7 @@ Gamma
8 8 8 0 0 0
```
-Run the program, and you will see a file named doss1g1.txt in the output directory. The columns are: energy(eV), dos (number of states in each energy bin), dos_int (integrated DOS), dos_smear(1/eV) (Gaussian-smeared DOS), dos_smear_int (integrated smeared DOS). Plot the file with graphing software, and you'll get the DOS.
+Run the program, and you will see a file named doss1.txt in the output directory (with `out_app_flag = 2` the name carries a one-based `g{step}` index, e.g., `doss1g1.txt`). The columns are: energy(eV), dos (number of states in each energy bin), dos_int (integrated DOS), dos_smear(1/eV) (Gaussian-smeared DOS), dos_smear_int (integrated smeared DOS). Plot the file with graphing software, and you'll get the DOS.
The `dos` column is a raw per-bin histogram, while `dos_smear` is the physically meaningful density of states in 1/eV. The `abacus-plot` tool plots `dos_smear` by default; set `"smearing": false` in its config.json to plot the raw histogram instead, or apply your own smearing to the raw column with `abacus_plot.utils.gaussian_smearing`. For nspin=2, two files (doss1*/doss2*) are written, one per spin channel; the tool pairs them automatically whether you pass either file, a directory, or a list of both files. For nspin=4, a single spinor-summed file is written.
@@ -82,7 +82,7 @@ The `dos` column is a raw per-bin histogram, while `dos_smear` is the physically
## PDOS
-Along with the DOS files, we also produce the projected density of states (PDOS) in files named pdoss{spin}g{geom}.txt (e.g., pdoss1g1.txt).
+Along with the DOS files, we also produce the projected density of states (PDOS) in files named pdoss{spin}.txt; with `out_app_flag = 2` a one-based `g{geom}` index is inserted, e.g., pdoss1g1.txt.
The PDOS file uses a plain-text format. Each row corresponds to one energy point and one atom. Columns: energy(eV), atom (1-based), species, then pdos values ordered as s(1), p(3), d(5), f(7), etc. Zeta components are summed, and values below 1e-6 are zeroed out.
diff --git a/docs/advanced/elec_properties/hs_matrix.md b/docs/advanced/elec_properties/hs_matrix.md
index 5adf14e3971..4cd3b715786 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`.
+Only when `out_app_flag` is 2 is `g${step}` 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`. With `out_app_flag = 0` the fixed file is truncated and overwritten at every write, and with `out_app_flag = 1` it accumulates one record per written step.
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 `out_app_flag` is 0, the fixed file is truncated and overwritten at every write. Only when `out_app_flag` is 2 does each ionic step have 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 2, 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:
+Each ionic step is a complete record with no padding; its header is self-identifying through a magic value and a format version:
-1. Native `int`: zero-based ionic step, matrix dimension, number of R blocks.
+1. Versioned header: five native `int` values, namely the magic `0x48535242` (`HSRB`), the format version (`2`), the one-based ionic step, the matrix dimension, and the number of R blocks. The magic and version are absent from the unversioned layout described in the migration note below.
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,14 @@ 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. When `out_app_flag` is 0, the fixed file is truncated and overwritten at every write. Only when `out_app_flag` is 2 is every ionic step written to its own file.
+
+> **Format version and migration note.** The native binary record is now versioned. Version `2` starts with the magic `0x48535242` and the version itself, and stores a one-based ionic step. Earlier files are unversioned and start directly with the ionic step, so the first `int` of an unversioned file is a small step index (`0` or `1`) rather than the magic, which makes the two layouts distinguishable.
+> - **v3.11.0-beta9 and v3.11.0-beta10** wrote unversioned records with a **zero-based** ionic step (the first ionic step is stored as `0`).
+> - **Development builds between that change and this versioning scheme** wrote unversioned records with a **one-based** ionic step, so an unversioned file cannot be interpreted from its content alone; the writer version is required.
+> - **Version `2` and later** write the magic, the format version, and a **one-based** ionic step.
+>
+> The matrix data itself is unchanged; only the header and the step convention differ. Readers should reject a record whose first `int` is not the expected magic instead of guessing the layout, and must consult the format version before interpreting the remaining fields. Reading an unversioned beta file as version `2`, or a version `2` file as unversioned, shifts the reported ionic-step index and misaligns every following field.
### NPZ Format
diff --git a/docs/advanced/elec_properties/position_matrix.md b/docs/advanced/elec_properties/position_matrix.md
index c504a866b64..c617c62f8fd 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). With out_app_flag = 2, each written ionic step has its own file named `rrg${step}_nao.txt`, where `${step}` is the one-based ionic-step index; with out_app_flag = 0 or 1 the fixed name `rr_nao.txt` is reused (mode 0 truncates and overwrites it at every write, mode 1 appends one record per written step). The single-shot output of `calculation = get_s` is always 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..b9812b80327 100644
--- a/docs/advanced/elec_properties/wfc.md
+++ b/docs/advanced/elec_properties/wfc.md
@@ -12,13 +12,13 @@ For `basis_type=pw` and `esolver_type=ksdft`, [`out_wfc_pw`](https://abacus-rtd.
The files are stored in `OUT.${suffix}/`. Their pattern is `wfk{k}[s{spin}][g{geometry step}][e{electronic iteration}]_pw.txt` for `out_wfc_pw=1` and `wfk{k}[s{spin}][g{geometry step}][e{electronic iteration}]_pw.dat` for `out_wfc_pw=2`. The `s*` label is omitted for `nspin=1`, is `s1` or `s2` for `nspin=2`, and is `s4` for `nspin=4`. All PW files include the `k*` label, including Gamma-only calculations.
-With `out_freq_ion=0`, files are written only when the electronic calculation converges or reaches `scf_nmax`, and the names contain neither `g*` nor `e*`. During structural relaxation or molecular dynamics, each later ionic step overwrites the same files. With `out_freq_ion>0`, output is restricted to the ionic steps selected by `out_freq_ion` and occurs at multiples of `out_freq_elec`, at convergence, or at `scf_nmax`; both `g*` and `e*` are included in the file names. A static `calculation=scf` or `calculation=nscf` run also receives `g1e*` indices when `out_freq_ion>0`.
+With `out_freq_ion=0`, files are written only when the electronic calculation converges or reaches `scf_nmax`; no `g*` or `e*` index is added. During structural relaxation or molecular dynamics with `out_app_flag=1`, the first written step truncates the unindexed files and later ionic steps append one frame per step; with `out_app_flag=0` (the default) the unindexed files are overwritten at every write. With `out_freq_ion>0`, output is restricted to the ionic steps selected by `out_freq_ion`. For `out_app_flag=0` or `2`, output occurs at multiples of `out_freq_elec`, at convergence, or at `scf_nmax`; for `out_app_flag=1` only the last electronic iteration of each selected ionic step is written (`out_freq_elec` is ignored), so the appended file holds exactly one frame per step. Both `g*` and `e*` indices are then added only when `out_app_flag=2`; with `out_app_flag=0` the fixed files are truncated and overwritten at every write, and with `out_app_flag=1` they accumulate one frame per written step.
-The [`init_wfc file binary`](../scf/initialization.md#wavefunction) path reads only unindexed binary `wf*_pw.dat` files from `read_file_dir`. Generate directly reusable files with `out_wfc_pw=2` and normally `out_freq_ion=0`. Text `wf*_pw.txt` files and files containing `g*` or `e*` indices are not matched automatically.
+The [`init_wfc file binary`](../scf/initialization.md#wavefunction) path reads only unindexed binary `wf*_pw.dat` files from `read_file_dir`, starting from the first frame. Such directly reusable files are normally generated with `out_wfc_pw=2` and `out_freq_ion=0`; multi-frame files accumulated under `out_app_flag=1` are trajectories and only their first frame is visible to the reader. Text `wf*_pw.txt` files and files containing `g*` or `e*` indices are not matched automatically.
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..8aedf37a0a6 100644
--- a/docs/advanced/output_files/output-specification.md
+++ b/docs/advanced/output_files/output-specification.md
@@ -55,12 +55,21 @@ 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 = 0` keeps the fixed cube filenames (chg/tau/pot/elf) and
+ truncates and overwrites them at every write. `out_app_flag = 1` also keeps
+ the fixed filenames: the first written ionic step truncates them and every
+ later written step appends one complete cube frame whose first comment line
+ is `Ionic_Step {#}` (one-based). Only `out_app_flag = 2` adds the `g#` suffix
+ and 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
+ - `nspin=1`: `chg_ini.cube`; `nspin=2/4`: `chgs{#}_ini.cube`
+ - `out_freq_ion = 0` writes at every step; `out_freq_ion > 0` writes every `out_freq_ion` steps
+ - Only `out_app_flag = 2` inserts `g{#}` (e.g., `chgg1_ini.cube`, `chgs1g1_ini.cube`); modes 0 and 1 keep the fixed name
- For initial potential output (`out_pot = 3`):
- - `out_freq_ion = 0`: `pot_ini.cube` (nspin=1) or `pots{#}_ini.cube` (nspin=2/4) - output at every step (overwrite same file)
- - `out_freq_ion > 0`: `potg{#}_ini.cube` (nspin=1) or `pots{#}g{#}_ini.cube` (nspin=2/4) - output every out_freq_ion steps
+ - `nspin=1`: `pot_ini.cube`; `nspin=2/4`: `pots{#}_ini.cube`
+ - `out_freq_ion = 0` writes at every step; `out_freq_ion > 0` writes every `out_freq_ion` steps
+ - Only `out_app_flag = 2` inserts `g{#}` (e.g., `potg1_ini.cube`, `pots1g1_ini.cube`); modes 0 and 1 keep the fixed name
### 2.2 Examples
@@ -71,22 +80,22 @@ All output file naming conventions can be found in the online documentation (wit
| `chgs3.cube` | Charge density, spin 3 (non-collinear with SOC) |
| `chg_ini.cube` | Initial charge density (out_freq_ion=0, nspin=1) |
| `chgs1_ini.cube` | Initial charge density (out_freq_ion=0, spin 1, nspin=2/4) |
-| `chgg1_ini.cube` | Initial charge density, geometry step 1 (nspin=1) |
-| `chgs1g1_ini.cube` | Initial charge density, spin 1, geometry step 1 (nspin=2/4) |
+| `chgg1_ini.cube` | Initial charge density, geometry step 1, out_app_flag=2 (nspin=1) |
+| `chgs1g1_ini.cube` | Initial charge density, spin 1, geometry step 1, out_app_flag=2 (nspin=2/4) |
| `pot_ini.cube` | Initial potential (out_freq_ion=0, nspin=1) |
| `pots1_ini.cube` | Initial potential (out_freq_ion=0, spin 1, nspin=2/4) |
-| `potg1_ini.cube` | Initial potential, geometry step 1 (nspin=1) |
-| `pots1g1_ini.cube` | Initial potential, spin 1, geometry step 1 (nspin=2/4) |
+| `potg1_ini.cube` | Initial potential, geometry step 1, out_app_flag=2 (nspin=1) |
+| `pots1g1_ini.cube` | Initial potential, spin 1, geometry step 1, out_app_flag=2 (nspin=2/4) |
| `pots1.cube` | Local potential, spin 1 |
| `elftot.cube` | ELF, total (nspin=1/4) |
| `elfs1.cube` | ELF, spin 1 (nspin=2) |
| `elfs2.cube` | ELF, spin 2 (nspin=2) |
| `elftot.cube` | ELF, total (nspin=2) |
-| `elftotg1.cube` | ELF, total, geometry step 1 (nspin=1/4) |
-| `elfs1g1.cube` | ELF, spin 1, geometry step 1 (nspin=2) |
-| `elftotg1.cube` | ELF, total, geometry step 1 (nspin=2) |
+| `elftotg1.cube` | ELF, total, geometry step 1, out_app_flag=2 (nspin=1/4) |
+| `elfs1g1.cube` | ELF, spin 1, geometry step 1, out_app_flag=2 (nspin=2) |
+| `elftotg1.cube` | ELF, total, geometry step 1, out_app_flag=2 (nspin=2) |
| `eig_occ.txt` | Eigenvalues and occupations |
-| `doss1g1.txt` | DOS, spin 1, geometry step 1 |
+| `doss1g1.txt` | DOS, spin 1, geometry step 1, out_app_flag=2 |
| `wf_pw.dat` | Wavefunction, plane wave basis |
| `sr.csr` | Overlap matrix in real space (no spin index) |
@@ -95,31 +104,46 @@ All output file naming conventions can be found in the online documentation (wit
| File Name | Description |
|-----------|-------------|
| `running_scf.log` | SCF iteration log |
-| `doss1g1.txt` | Density of states |
+| `doss1g1.txt` | Density of states, spin 1, geometry step 1, out_app_flag=2 |
| `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) | |
| `chg_ini.cube` | Initial charge density (out_freq_ion=0, nspin=1) |
| `chgs{#}_ini.cube` | Initial charge density (out_freq_ion=0, spin {#}, nspin=2/4) |
-| `chgg{#}_ini.cube` | Initial charge density (out_freq_ion>0, geometry step {#}, nspin=1) |
-| `chgs{#}g{#}_ini.cube` | Initial charge density (out_freq_ion>0, spin {#}, geometry step {#}, nspin=2/4) |
+| `chgg{#}_ini.cube` | Initial charge density (geometry step {#}, nspin=1, out_app_flag=2) |
+| `chgs{#}g{#}_ini.cube` | Initial charge density (spin {#}, geometry step {#}, nspin=2/4, out_app_flag=2) |
| Initial potential (out_pot=3) | |
| `pot_ini.cube` | Initial potential (out_freq_ion=0, nspin=1) |
| `pots{#}_ini.cube` | Initial potential (out_freq_ion=0, spin {#}, nspin=2/4) |
-| `potg{#}_ini.cube` | Initial potential (out_freq_ion>0, geometry step {#}, nspin=1) |
-| `pots{#}g{#}_ini.cube` | Initial potential (out_freq_ion>0, spin {#}, geometry step {#}, nspin=2/4) |
+| `potg{#}_ini.cube` | Initial potential (geometry step {#}, nspin=1, out_app_flag=2) |
+| `pots{#}g{#}_ini.cube` | Initial potential (spin {#}, geometry step {#}, nspin=2/4, out_app_flag=2) |
| `taus1.cube`, `taus2.cube` | Kinetic energy density (tau) |
| `pots1.cube`, `pots2.cube` | Local potential |
| ELF (out_elf) | |
| `elftot.cube` | ELF, total (nspin=1/4) |
| `elfs1.cube`, `elfs2.cube` | ELF, spin 1/2 (nspin=2) |
| `elftot.cube` | ELF, total (nspin=2) |
-| `elftotg{#}.cube` | ELF, total, geometry step {#} (nspin=1/4) |
-| `elfs{#}g{#}.cube` | ELF, spin {#}, geometry step {#} (nspin=2) |
-| `elftotg{#}.cube` | ELF, total, geometry step {#} (nspin=2) |
+| `elftotg{#}.cube` | ELF, total, geometry step {#}, out_app_flag=2 (nspin=1/4) |
+| `elfs{#}g{#}.cube` | ELF, spin {#}, geometry step {#}, out_app_flag=2 (nspin=2) |
+| `elftotg{#}.cube` | ELF, total, geometry step {#}, out_app_flag=2 (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
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 243b1ee57f4..97c72f0fad7 100644
--- a/source/Makefile.Objects
+++ b/source/Makefile.Objects
@@ -238,6 +238,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/fs_compat.h b/source/source_base/fs_compat.h
index 3e3a58e0dca..4ad1c0054c2 100644
--- a/source/source_base/fs_compat.h
+++ b/source/source_base/fs_compat.h
@@ -43,6 +43,30 @@ inline int make_directory(const std::string& path)
#endif
}
+/**
+ * @brief Ensure a directory exists, creating it lazily if needed.
+ *
+ * Intended for write-time directory creation: output writers call this just
+ * before writing a file into a directory that may not exist yet, instead of
+ * relying on a central startup routine to predict which directories will be
+ * needed. An already-existing directory (EEXIST) is not an error, so callers
+ * on different MPI ranks may invoke it concurrently for the same path. The
+ * parent directory must already exist (only one level is created).
+ *
+ * @param path directory path to ensure; an empty path is accepted as a no-op
+ * @return true if the directory now exists (created or already present),
+ * false on a real creation failure (errno is set)
+ */
+inline bool ensure_directory(const std::string& path)
+{
+ if (path.empty())
+ {
+ return true;
+ }
+ const int ret = make_directory(path);
+ return ret == 0 || errno == EEXIST;
+}
+
} // namespace ModuleBase
#endif // MODULEBASE_FS_COMPAT_H
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..3f1b464ca03 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 == 2))
? "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 mode 2 writes each step to a separate g{step} file
std::string iter_block
- = (iter >= 0 && (!out_app_flag))
+ = (iter >= 0 && (out_app_flag == 2))
? "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 mode 2 writes each electronic iteration to a separate e{iter} 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..d95b24c2453 100644
--- a/source/source_base/module_out/filename.h
+++ b/source/source_base/module_out/filename.h
@@ -16,10 +16,13 @@ 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=fixed filename, overwritten at each write; 1=fixed filename, appended;
+ * 2=subdirectory layout with a g{step} suffix.
* @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.
+ * @param istep: index of the ion step starting from 0. The step number is only embedded in the
+ * filename in mode 2 (out_app_flag == 2), and only when istep >= 0.
+ * @param iter: index of the electronic step starting from 0. The step number is only embedded in
+ * the filename in mode 2 (out_app_flag == 2), and only when iter >= 0.
* @return The generated filename.
*/
std::string filename_output(
@@ -31,7 +34,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..254040cb0b3
--- /dev/null
+++ b/source/source_base/test/test_filename.cpp
@@ -0,0 +1,126 @@
+#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 = overwrite a fixed filename -> no g{N} suffix even when istep >= 0.
+// ---------------------------------------------------------------------------
+TEST_F(FilenameOutputTest, Mode0FixedNameIgnoresIstep)
+{
+ // 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/wfk1s1_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 = per-step file -> the only mode that embeds the g{N} suffix.
+// The directory routing is handled by the caller (set_global_dir).
+// ---------------------------------------------------------------------------
+TEST_F(FilenameOutputTest, Mode2EmbedsStep)
+{
+ 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} only in mode 2; modes 0 and 1 keep the fixed filename.
+// ---------------------------------------------------------------------------
+TEST_F(FilenameOutputTest, IterIndexOnlyInMode2)
+{
+ // mode 0: neither g nor e.
+ 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/wfk1s1_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");
+
+ // mode 2: both g and e present.
+ const std::string fn_mode2 = filename_output(
+ dir, "wf", "pw", ik_local, ik2iktot, nspin, nkstot,
+ /*out_type=*/1, /*out_app_flag=*/2, /*gamma_only=*/false,
+ /*istep=*/1, /*iter=*/3);
+ EXPECT_EQ(fn_mode2, "OUT.test/wfk1s1g2e3_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 3d83e0a783d..8e759788772 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/CMakeLists.txt b/source/source_cell/module_neighlist/test/CMakeLists.txt
index f43b2aa505e..e11e704da35 100644
--- a/source/source_cell/module_neighlist/test/CMakeLists.txt
+++ b/source/source_cell/module_neighlist/test/CMakeLists.txt
@@ -121,6 +121,7 @@ if(ENABLE_MPI)
../../mdcell_reader.cpp
../../mdcell.cpp
../../print_cell.cpp
+ ../../stru_frame_io.cpp
../../domain_decomposition.cpp
../neighbor_search.cpp
../bin_manager.cpp
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..043e256c06f 100644
--- a/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp
+++ b/source/source_cell/module_neighlist/test/test_mdcell_reader.cpp
@@ -1,9 +1,11 @@
#include "source_cell/domain_decomposition.h"
+#include
#include
#include "source_cell/mdcell_reader.h"
#include "source_cell/mdcell.h"
#include "source_cell/print_cell.h"
+#include "source_cell/stru_frame_io.h"
#include "source_base/constants.h"
#include "source_base/parallel_cell.h"
#include "source_base/global_variable.h"
@@ -152,10 +154,9 @@ TEST(MDCellReaderTest, ReadOwnedAtomsFromSTRUWithoutUnitCell)
TEST(MDCellReaderTest, RestartStruPreservesAtomRecordsAcrossRanks)
{
- int rank = 0;
- int size = 1;
- MPI_Comm_rank(MPI_COMM_WORLD, &rank);
- MPI_Comm_size(MPI_COMM_WORLD, &size);
+ const ModuleBase::CommunicationDomain world_domain = ModuleBase::world_comm_domain();
+ const int rank = world_domain.rank();
+ const int size = world_domain.size();
ASSERT_GE(size, 4);
std::vector owned_atoms;
@@ -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,168 @@ TEST(MDCellReaderTest, RestartStruPreservesAtomRecordsAcrossRanks)
if (rank == 0) std::remove(output_file.c_str());
}
+// ---------------------------------------------------------------------------
+// mdcell::print_stru_file append + frame_comment.
+// ---------------------------------------------------------------------------
+TEST(MDCellReaderTest, STRUAppendWithFrameComment)
+{
+ const ModuleBase::CommunicationDomain world_domain = ModuleBase::world_comm_domain();
+ const int rank = world_domain.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());
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Fresh-run truncation: rewriting the fixed-name file (append=false) must
+// drop the frames left by a previous run so extract_last_frame only sees the
+// new run's frames.
+// ---------------------------------------------------------------------------
+TEST(MDCellReaderTest, STRUFreshRunTruncatesPreviousFrames)
+{
+ const ModuleBase::CommunicationDomain world_domain = ModuleBase::world_comm_domain();
+ const int rank = world_domain.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();
+ const ModuleBase::Matrix3 inverse = lattice.Inverse();
+ const std::vector species_labels = {"A"};
+ const std::vector species_masses = {1.0};
+ const std::vector species_counts = {1};
+ MDCell mdcell;
+ mdcell.initialize_from_owned_atoms(lattice,
+ inverse,
+ 1.0,
+ 1.0,
+ 1,
+ owned_atoms,
+ species_labels,
+ species_masses,
+ species_counts,
+ 0.0,
+ world_domain);
+ StruMeta metadata;
+ metadata.species.resize(1);
+ const std::string output_file = testing::TempDir() + "mdcell_fresh_run_test.STRU";
+ const std::string frame_file = testing::TempDir() + "mdcell_fresh_run_test.frame";
+
+ // Previous run: frames 1 and 2 accumulate.
+ mdcell::print_stru_file(mdcell, metadata, output_file,
+ "# MD step 1\n\n", /*append=*/false);
+ mdcell::print_stru_file(mdcell, metadata, output_file,
+ "# MD step 2\n\n", /*append=*/true);
+
+ // The extraction runs on rank zero only; broadcast its result so every
+ // rank takes the same (collective-safe) return decision. Later
+ // print_stru_file calls broadcast/reduce across the whole communicator,
+ // so a rank-local ASSERT here would hang the remaining ranks.
+ const bool extracted = (rank == 0)
+ ? stru_frame_io::extract_last_frame(output_file, frame_file)
+ : true;
+ const int extracted_local = extracted ? 1 : 0;
+ const int extracted_all = world_domain.max(extracted_local);
+ ASSERT_EQ(extracted_all, 1);
+
+ if (rank == 0)
+ {
+ std::ifstream prev(frame_file.c_str());
+ std::string prev_str((std::istreambuf_iterator(prev)),
+ std::istreambuf_iterator());
+ prev.close();
+ EXPECT_THAT(prev_str, testing::HasSubstr("ATOMIC_SPECIES"));
+ }
+
+ // Fresh run: the first frame truncates the fixed-name file (append=false)
+ // and starts numbering from step 1 again.
+ mdcell::print_stru_file(mdcell, metadata, output_file,
+ "# MD step 1\n\n", /*append=*/false);
+
+ if (rank == 0)
+ {
+ std::ifstream ifs(output_file.c_str());
+ std::string str((std::istreambuf_iterator(ifs)),
+ std::istreambuf_iterator());
+ ifs.close();
+ // Only the fresh run's single frame remains: one ATOMIC_SPECIES block,
+ // one frame marker, no trace of the previous step-2 frame.
+ int occurrences = 0;
+ size_t pos = 0;
+ while ((pos = str.find("ATOMIC_SPECIES", pos)) != std::string::npos)
+ {
+ ++occurrences;
+ pos += 14;
+ }
+ EXPECT_EQ(occurrences, 1);
+ EXPECT_THAT(str, testing::HasSubstr("# MD step 1"));
+ EXPECT_THAT(str, testing::Not(testing::HasSubstr("# MD step 2")));
+ std::remove(output_file.c_str());
+ std::remove(frame_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..64822241cb4 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,22 +383,29 @@ 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;
}
@@ -413,7 +427,13 @@ 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());
+ // Every rank learns the write position from the file itself instead of
+ // broadcasting the MPI_Offset held by rank 0: after the barrier the header
+ // is in place, so the end of the file is the current base offset plus the
+ // header that was just written (zero for a truncated fresh file).
+ const off_t file_end = lseek(file, 0, SEEK_END);
+ MPI_Offset offset = file_end >= 0 ? static_cast(file_end)
+ : 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 +473,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..db6d7b474ab
--- /dev/null
+++ b/source/source_cell/stru_frame_io.cpp
@@ -0,0 +1,105 @@
+/**
+ * @file stru_frame_io.cpp
+ * @brief Implementation of the cumulative STRU frame extraction helpers.
+ */
+#include "stru_frame_io.h"
+
+#include
+#include
+#include
+
+namespace
+{
+const char* const frame_marker = "# MD step ";
+
+/// Parse the step number following a "# MD step " marker; 0 when malformed.
+int parse_marker_step(const std::string& line)
+{
+ const std::string number = line.substr(std::string(frame_marker).size());
+ if (number.empty())
+ {
+ return 0;
+ }
+ char* end = nullptr;
+ const long value = std::strtol(number.c_str(), &end, 10);
+ if (end == number.c_str() || value <= 0)
+ {
+ return 0;
+ }
+ return static_cast(value);
+}
+} // namespace
+
+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;
+ }
+ 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, std::string(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();
+}
+
+int last_frame_step(const std::string& cumulative_path)
+{
+ std::ifstream ifs(cumulative_path.c_str());
+ if (!ifs)
+ {
+ return 0;
+ }
+ int last_step = 0;
+ std::string line;
+ while (std::getline(ifs, line))
+ {
+ const bool is_marker = line.compare(0, std::string(frame_marker).size(), frame_marker) == 0;
+ if (is_marker)
+ {
+ const int step = parse_marker_step(line);
+ if (step > 0)
+ {
+ last_step = step;
+ }
+ }
+ }
+ return last_step;
+}
+} // 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..fff0067c98f
--- /dev/null
+++ b/source/source_cell/stru_frame_io.h
@@ -0,0 +1,37 @@
+#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);
+
+/// @brief Read the one-based step number of the last frame of a cumulative STRU.
+/// @param cumulative_path path of the mode-1 "STRU" file
+/// @return the step number carried by the last "# MD step N" marker, or 0 when
+/// the file is missing or carries no frame marker
+int last_frame_step(const std::string& cumulative_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..2f280c1196d
--- /dev/null
+++ b/source/source_cell/test/test_stru_frame_io.cpp
@@ -0,0 +1,112 @@
+/**
+ * @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));
+}
+
+TEST_F(StruFrameIoTest, LastFrameStepReportsNewestMarker)
+{
+ 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 3\n\nLATTICE_CONSTANT\n10.2\n";
+ ofs.close();
+
+ EXPECT_EQ(stru_frame_io::last_frame_step(cumulative), 3);
+}
+
+TEST_F(StruFrameIoTest, LastFrameStepIsZeroOnMissingOrUnmarkedFile)
+{
+ EXPECT_EQ(stru_frame_io::last_frame_step(case_dir + "NO_SUCH_STRU"), 0);
+
+ const std::string unmarked = case_dir + "STRU_no_marker";
+ std::ofstream ofs(unmarked.c_str());
+ ofs << "ATOMIC_SPECIES\nSi 28.0855\n";
+ ofs.close();
+
+ EXPECT_EQ(stru_frame_io::last_frame_step(unmarked), 0);
+}
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_gets.cpp b/source/source_esolver/esolver_gets.cpp
index d0a2d727587..2a4d410ef63 100644
--- a/source/source_esolver/esolver_gets.cpp
+++ b/source/source_esolver/esolver_gets.cpp
@@ -1,5 +1,6 @@
#include "esolver_gets.h"
+#include "source_base/fs_compat.h"
#include "source_base/timer.h"
#include "source_cell/module_neighbor/sltk_atom_arrange.h"
#include "source_cell/read_pp_ucell.h"
@@ -196,6 +197,7 @@ void ESolver_GetS::runner(BaseCell& basecell, const int istep)
}
const std::string fn = PARAM.globalv.global_out_dir + "sr_nao.csr";
+ const std::string global_matrix_dir = PARAM.globalv.global_matrix_dir;
auto* hamilt_ptr = static_cast>*>(this->p_hamilt);
const bool binary = false;
@@ -209,7 +211,7 @@ void ESolver_GetS::runner(BaseCell& basecell, const int istep)
sparse_threshold,
precision,
PARAM.globalv.global_out_dir,
- PARAM.globalv.global_matrix_dir,
+ global_matrix_dir,
PARAM.inp.calculation,
PARAM.inp.out_app_flag,
PARAM.inp.nspin,
@@ -217,6 +219,8 @@ void ESolver_GetS::runner(BaseCell& basecell, const int istep)
if (this->inp_->out_mat_r[0])
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_matrix_dir);
Position_op r_matrix;
const bool cal_force = PARAM.inp.cal_force;
const int nlocal = PARAM.globalv.nlocal;
@@ -226,7 +230,7 @@ void ESolver_GetS::runner(BaseCell& basecell, const int istep)
-1, // get_s has no ionic step; use -1 for no step suffix
this->inp_->out_mat_r[1],
PARAM.globalv.global_out_dir,
- PARAM.globalv.global_matrix_dir,
+ global_matrix_dir,
PARAM.inp.calculation,
PARAM.inp.out_app_flag,
nlocal,
@@ -236,13 +240,15 @@ void ESolver_GetS::runner(BaseCell& basecell, const int istep)
if (this->inp_->out_mat_ds[0])
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_matrix_dir);
LCAO_HS_Arrays HS_Arrays; // store sparse arrays
ModuleIO::MatROutputOptions mat_R_options;
mat_R_options.binary = binary;
mat_R_options.sparse_threshold = sparse_threshold;
mat_R_options.precision = this->inp_->out_mat_ds[1];
mat_R_options.global_out_dir = PARAM.globalv.global_out_dir;
- mat_R_options.global_matrix_dir = PARAM.globalv.global_matrix_dir;
+ mat_R_options.global_matrix_dir = global_matrix_dir;
mat_R_options.calculation = PARAM.inp.calculation;
mat_R_options.out_app_flag = PARAM.inp.out_app_flag;
mat_R_options.nspin = PARAM.inp.nspin;
diff --git a/source/source_esolver/esolver_ks.cpp b/source/source_esolver/esolver_ks.cpp
index dcb2a3c61f6..8c80c068116 100644
--- a/source/source_esolver/esolver_ks.cpp
+++ b/source/source_esolver/esolver_ks.cpp
@@ -496,7 +496,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 f476cf78d90..ae1c99417b8 100644
--- a/source/source_esolver/esolver_ks_lcao.cpp
+++ b/source/source_esolver/esolver_ks_lcao.cpp
@@ -174,6 +174,9 @@ void ESolver_KS_LCAO::before_scf(UnitCell& ucell, const int istep, const
ModuleBase::TITLE("ESolver_KS_LCAO", "before_scf");
ModuleBase::timer::start("ESolver_KS_LCAO", "before_scf");
+ // Record the ionic step (not output_step) for provenance headers written in after_all_runners.
+ this->istep_ = istep;
+
//! 1) call before_scf() of ESolver_KS.
// The base preparation uses this index only for initial charge output.
ESolver_KS::before_scf(ucell, output_step);
@@ -443,6 +446,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_lcaopw.cpp b/source/source_esolver/esolver_ks_lcaopw.cpp
index 5c6ba6e9549..90fe4ab537d 100644
--- a/source/source_esolver/esolver_ks_lcaopw.cpp
+++ b/source/source_esolver/esolver_ks_lcaopw.cpp
@@ -13,6 +13,7 @@
#include "source_pw/module_pwdft/stress_pw.h"
//---------------------------------------------------
#include "source_base/global_variable.h"
+#include "source_base/fs_compat.h"
#include "source_base/parallel_comm.h"
#include "source_estate/elecstate_pw.h"
#include "source_pw/module_pwdft/hamilt_lcaopw.h"
@@ -277,6 +278,9 @@ namespace ModuleESolver
#ifdef __LCAO
if (this->inp_->out_mat_xc)
{
+ const std::string global_mat_dir = PARAM.globalv.global_mat_dir;
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
#ifdef __EXX
bool cal_exx = this->general_exx_info_.cal_exx;
double hybrid_alpha = this->general_exx_info_.hybrid_alpha;
@@ -284,7 +288,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 +308,7 @@ namespace ModuleESolver
this->kv,
this->pelec->wg,
PARAM.globalv.gamma_only_local,
- PARAM.globalv.global_out_dir,
+ 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..a1057b1bfc0 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
+// (modes 0/1 keep the fixed names without the g{#} index; only mode 2 numbers them)
// - 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);
+ // Modes 0 and 1 reuse the same fixed filename (mode 0 overwrites,
+ // mode 1 accumulates one complete cube frame per written step);
+ // only mode 2 embeds the geometry-step index.
+ const bool include_geom_step = (output_frequency > 0) && (inp.out_app_flag == 2);
+ 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);
+ // Modes 0 and 1 reuse the same fixed filename (mode 0 overwrites,
+ // mode 1 accumulates one complete cube frame per written step);
+ // only mode 2 embeds the geometry-step index.
+ const bool include_geom_step = (output_frequency > 0) && (inp.out_app_flag == 2);
+ 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/CMakeLists.txt b/source/source_io/CMakeLists.txt
index e16e5b31e1c..2719b147f23 100644
--- a/source/source_io/CMakeLists.txt
+++ b/source/source_io/CMakeLists.txt
@@ -157,6 +157,7 @@ add_executable(
EXCLUDE_FROM_ALL
input_yaml_main.cpp
input_help.cpp
+ ../source_cell/stru_frame_io.cpp
../source_base/global_file.cpp
../source_base/global_function.cpp
../source_base/global_variable.cpp
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 57bed1d3a12..24da68b8eb6 100644
--- a/source/source_io/module_ctrl/ctrl_output_fp.cpp
+++ b/source/source_io/module_ctrl/ctrl_output_fp.cpp
@@ -1,5 +1,6 @@
#include "ctrl_output_fp.h" // use ctrl_output_fp()
+#include "source_base/fs_compat.h" // use ModuleBase::ensure_directory
#include "source_basis/module_pw/pw_basis_big.h" // use PW_Basis_Big
#include "source_estate/elecstate.h" // use elecstate::ElecState
#include "source_estate/module_charge/charge.h" // use Charge
@@ -33,10 +34,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.
@@ -53,16 +58,32 @@ void ctrl_output_fp(UnitCell& ucell,
bool should_output = (output_frequency == 0);
if (istep_in >= 0)
{
- geom_block = "g" + std::to_string(istep + 1);
+ // Modes 0 and 1 reuse a fixed filename: mode 0 truncates and overwrites
+ // it at every write, while mode 1 appends one complete frame per written
+ // step. Only mode 2 keeps a per-step g{index} suffix, which makes every
+ // file hold exactly one frame.
+ if (out_app_flag == 2)
+ {
+ 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)
{
+ // Mode-2 CHG/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_chg_dir);
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)
@@ -90,12 +111,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";
@@ -113,6 +135,7 @@ void ctrl_output_fp(UnitCell& ucell,
1, // default out_fermi
PARAM.globalv.two_fermi,
false,
+ append_cube,
ofs_running,
desc_tau);
}
@@ -122,9 +145,11 @@ void ctrl_output_fp(UnitCell& ucell,
// 5) write potential
if ((inp.out_pot[0] == 1 || inp.out_pot[0] == 3) && should_output)
{
+ // Mode-2 POT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_pot_dir);
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)
@@ -152,13 +177,16 @@ 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";
+ // Mode-2 POT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_pot_dir);
+ std::string fn = global_pot_dir + "potes";
fn += geom_block + ".cube";
const std::vector* pcc_potential = pelec->pot->pcc_electron_potential();
@@ -178,6 +206,7 @@ void ctrl_output_fp(UnitCell& ucell,
pcc_potential,
solvent_potential,
inp.out_pot[1],
+ append_cube,
ofs_running);
}
@@ -201,6 +230,7 @@ void ctrl_output_fp(UnitCell& ucell,
0, // out_fermi
false, // two_fermi, unused without out_fermi
false, // reduce_all_pool
+ append_cube,
ofs_running,
data_desc);
}
@@ -217,7 +247,9 @@ 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;
+ // Mode-2 ELF/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(out_dir);
ModuleIO::write_elf(out_dir,
istep,
nspin,
@@ -229,6 +261,7 @@ void ctrl_output_fp(UnitCell& ucell,
inp.out_elf[1],
geom_block,
PARAM.globalv.two_fermi,
+ append_cube,
ofs_running);
}
@@ -251,12 +284,14 @@ void ctrl_output_fp(UnitCell& ucell,
const bool output_dipole = inp.esolver_type == "tddft" || should_output;
if (inp.out_dipole == 1 && output_dipole)
{
+ // Mode-2 DIP/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_dip_dir);
const int dipole_channels = nspin == 4 ? 1 : nspin;
// In a spinor density only channel zero is charge; the other three channels are magnetization.
for (int is = 0; is < dipole_channels; ++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 0a3b13120dd..435a6412bd4 100644
--- a/source/source_io/module_ctrl/ctrl_output_pw.cpp
+++ b/source/source_io/module_ctrl/ctrl_output_pw.cpp
@@ -8,6 +8,7 @@
#include "../module_wannier/to_w90_pw.h" // wannier90 interface
#include "../module_wf/write_wfc_pw.h" // use write_wfc_pw
#include "source_base/formatter.h"
+#include "source_base/fs_compat.h" // use ModuleBase::ensure_directory
#include "source_io/module_chgpot/get_pchg_pw.h"
#include "source_io/module_wf/get_wf_pw.h"
#include "source_lcao/module_deltaspin/deltaspin_pw_mi.h"
@@ -47,7 +48,17 @@ void ModuleIO::ctrl_iter_pw(const int istep,
{
if (istep % output_frequency == 0)
{
- if (iter % inp.out_freq_elec == 0 || iter == inp.scf_nmax || conv_esolver || final_output)
+ // In mode 1 the fixed-name file accumulates one frame per ionic
+ // step, so only the last electronic iteration of each selected
+ // step is written (out_freq_elec is ignored). In modes 0 and 2
+ // out_freq_elec still selects intermediate iterations.
+ const bool last_elec_iter = (iter == inp.scf_nmax || conv_esolver || final_output);
+ bool write_iter = last_elec_iter;
+ if (inp.out_app_flag != 1)
+ {
+ write_iter = write_iter || (iter % inp.out_freq_elec == 0);
+ }
+ if (write_iter)
{
istep_in = istep;
iter_in = iter;
@@ -62,9 +73,17 @@ void ModuleIO::ctrl_iter_pw(const int istep,
if (out_wfc_flag && (inp.out_wfc_pw == 1 || inp.out_wfc_pw == 2))
{
+ const std::string global_psi_dir = PARAM.globalv.global_psi_dir;
+ // Mode-2 PSI/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_psi_dir);
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,
@@ -74,8 +93,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,
+ global_psi_dir,
stp.psi_cpu[0],
kv,
pw_wfc,
@@ -156,6 +176,9 @@ void ModuleIO::ctrl_scf_pw(const int istep,
if (out_dos_tmp)
{
+ const std::string global_dos_dir = PARAM.globalv.global_dos_dir;
+ // Mode-2 DOS/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_dos_dir);
ModuleIO::write_dos_pw(ucell,
pelec->ekb,
pelec->wg,
@@ -175,7 +198,7 @@ void ModuleIO::ctrl_scf_pw(const int istep,
PARAM.globalv.two_fermi,
inp.out_app_flag,
inp.bndpar,
- PARAM.globalv.global_out_dir,
+ global_dos_dir,
ofs_running);
}
}
@@ -186,17 +209,23 @@ void ModuleIO::ctrl_scf_pw(const int istep,
//------------------------------------------------------------------
if (inp.out_pchg.size() > 0 && output_td_state)
{
+ const std::string global_chg_dir = PARAM.globalv.global_chg_dir;
+ // Mode-2 CHG/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_chg_dir);
// 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, 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)
{
+ const std::string global_psi_dir = PARAM.globalv.global_psi_dir;
+ // Mode-2 PSI/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_psi_dir);
// 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, global_psi_dir, ofs_running);
}
//------------------------------------------------------------------
@@ -289,6 +318,9 @@ void ModuleIO::ctrl_runner_pw(UnitCell& ucell,
//----------------------------------------------------------
if (inp.out_ldos[0])
{
+ const std::string global_dos_dir = PARAM.globalv.global_dos_dir;
+ // Mode-2 DOS/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_dos_dir);
stp.update_psi_d();
ModuleIO::cal_ldos_pw(reinterpret_cast>*>(pelec),
*stp.template get_psi_d(),
@@ -298,7 +330,7 @@ void ModuleIO::ctrl_runner_pw(UnitCell& ucell,
inp.out_ldos,
inp.stm_bias,
inp.nspin,
- PARAM.globalv.global_out_dir,
+ 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..6d054d1a0a5 100644
--- a/source/source_io/module_ctrl/ctrl_runner_lcao.cpp
+++ b/source/source_io/module_ctrl/ctrl_runner_lcao.cpp
@@ -1,5 +1,6 @@
#include "ctrl_runner_lcao.h" // use ctrl_runner_lcao()
+#include "source_base/fs_compat.h" // use ModuleBase::ensure_directory
#include "source_estate/elecstate_lcao.h" // use elecstate::ElecState
#include "source_lcao/hamilt_lcao.h" // use hamilt::HamiltLCAO
#include "source_hamilt/module_xc/exx_info.h"
@@ -20,6 +21,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
@@ -52,21 +54,37 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell
// 2) out ldos
if (inp.out_ldos[0])
{
+ const std::string global_dos_dir = PARAM.globalv.global_dos_dir;
+ // Mode-2 DOS/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_dos_dir);
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, 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)
{
+ const std::string global_mat_dir = PARAM.globalv.global_mat_dir;
+ // Only out_mat_xc writes matrices into MAT/ (out_eband files land in
+ // the output root); the mode-2 subdirectory is created lazily here.
+ if (inp.out_mat_xc)
+ {
+ ModuleBase::ensure_directory(global_mat_dir);
+ }
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 +102,8 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell
gd,
inp.dft_plus_u,
PARAM.globalv.gamma_only_local,
+ inp.out_mat_xc,
+ global_mat_dir,
PARAM.globalv.global_out_dir,
inp.out_ndigits,
inp.ks_solver,
@@ -99,6 +119,9 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell
if (inp.out_mat_xc2[0])
{
+ const std::string global_mat_dir = PARAM.globalv.global_mat_dir;
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
bool cal_exx = exx_info.info_global.cal_exx;
double hybrid_alpha = exx_info.info_global.hybrid_alpha;
bool real_number = exx_info.info_ri.real_number;
@@ -119,7 +142,7 @@ void ctrl_runner_lcao(UnitCell& ucell, // unitcell
kv,
orb.cutoffs(),
gd,
- PARAM.globalv.global_out_dir,
+ global_mat_dir,
cal_exx,
hybrid_alpha,
real_number
@@ -136,11 +159,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 +180,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 +193,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 +217,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 +241,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..16ec490487d 100644
--- a/source/source_io/module_ctrl/ctrl_scf_lcao.cpp
+++ b/source/source_io/module_ctrl/ctrl_scf_lcao.cpp
@@ -2,6 +2,7 @@
#include "source_hamilt/module_xc/exx_info.h"
#include "source_base/formatter.h"
+#include "source_base/fs_compat.h" // use ModuleBase::ensure_directory
#include "source_base/module_out/filename.h"
#include "source_base/tool_quit.h" // use ModuleBase::WARNING_QUIT
#include "source_estate/elecstate_lcao.h" // use elecstate::ElecState
@@ -53,8 +54,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,16 +137,24 @@ 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;
+ const std::string global_matrix_dir = PARAM.globalv.global_matrix_dir;
+ const int npol = PARAM.globalv.npol;
+ const int nlocal = PARAM.globalv.nlocal;
+ const bool cal_force = inp.cal_force;
//------------------------------------------------------------------
//! 1) print out density of states (DOS)
//------------------------------------------------------------------
if (inp.out_dos)
{
+ const std::string global_dos_dir = PARAM.globalv.global_dos_dir;
+ // Mode-2 DOS/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_dos_dir);
ModuleIO::write_dos_lcao(psi,
p_hamilt,
pv,
@@ -168,7 +177,7 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell,
inp.dos_emin_ev,
PARAM.globalv.two_fermi,
inp.bndpar,
- PARAM.globalv.global_out_dir,
+ global_dos_dir,
ofs_running);
}
@@ -177,10 +186,15 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell,
//------------------------------------------------------------------
if (inp.out_dmr[0])
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
const int precision = inp.out_dmr[1];
-
- ModuleIO::write_dmr(dm->get_dmr_vec(), &ucell, precision, pv, out_app_flag,
- ucell.get_iat2iwt(), ucell.nat, istep, ofs_running);
+ // File-name selection and append/truncate are independent decisions:
+ // mode 0 keeps the fixed filename and overwrites it at every write,
+ // mode 1 keeps the fixed filename and appends later steps, and mode 2
+ // embeds the ionic step in the g{step} filename.
+ ModuleIO::write_dmr(dm->get_dmr_vec(), &ucell, precision, pv, out_app_flag,
+ ucell.get_iat2iwt(), ucell.nat, global_mat_dir, istep, ofs_running);
}
//------------------------------------------------------------------
@@ -188,6 +202,8 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell,
//------------------------------------------------------------------
if (inp.out_dmk[0])
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
std::vector efermis(nspin == 2 ? 2 : 1);
for (int ispin = 0; ispin < efermis.size(); ispin++)
{
@@ -195,7 +211,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);
}
//------------------------------------------------------------------
@@ -204,8 +220,10 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell,
const int hsk_out_type = inp.out_hsk[0];
if (hsk_out_type == 1 || hsk_out_type == 2)
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
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(),
@@ -274,13 +292,15 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell,
//------------------------------------------------------------------
if (inp.out_hsr[0] == 1 || inp.out_hsr[0] == 2)
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
const int precision = inp.out_hsr[1];
std::vector*> hr_vec = p_hamilt->getHR_vector();
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,
+ global_mat_dir, pelec->eferm,
ofs_running);
}
@@ -290,28 +310,34 @@ 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";
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
+ std::string zipname = global_mat_dir + "sr_nao.npz";
ModuleIO::output_mat_npz(ucell, zipname, *(p_hamilt->getSR()));
}
if (inp.out_hr_npz || output_hsr_npz)
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
std::vector*> hr_vec = p_hamilt->getHR_vector();
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]));
}
}
if (inp.out_dm_npz)
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
const std::vector*>& dmr_vec = dm->get_dmr_vec();
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]));
}
}
@@ -343,6 +369,15 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell,
kv,
p_ham_tk,
&dftu,
+ global_out_dir,
+ global_matrix_dir,
+ inp.calculation,
+ out_app_flag,
+ nspin,
+ gamma_only,
+ npol,
+ nlocal,
+ cal_force,
ofs_running);
//------------------------------------------------------------------
@@ -399,7 +434,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 +477,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,35 +488,51 @@ 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_params.global_matrix_dir = PARAM.globalv.global_matrix_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 = 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])
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
+ h_params.precision = inp.out_mat_vnlk[1];
ModuleIO::write_h_vnl(h_params);
}
- if (inp.out_mat_h_vl[0])
+ if (inp.out_mat_vlk[0])
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
+ h_params.precision = inp.out_mat_vlk[1];
ModuleIO::write_h_vl(h_params);
}
- if (inp.out_mat_h_vh[0])
+ if (inp.out_mat_vhk[0])
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
+ h_params.precision = inp.out_mat_vhk[1];
ModuleIO::write_h_vh(h_params);
}
- if (inp.out_mat_h_vxc[0])
+ if (inp.out_mat_vxck[0])
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
+ h_params.precision = inp.out_mat_vxck[1];
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)
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
// V^EXX(R) output is wired for the gamma (TK==double) exx interfaces.
+ h_params.precision = inp.out_mat_exxk[1];
setup_exx_h_params(h_params, exx_nao, exx_info);
}
#endif
@@ -491,6 +542,8 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell,
//------------------------------------------------------------------
if (inp.out_mat_tk[0])
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
hamilt::HS_Matrix_K hsk(&pv, true);
hamilt::HContainer hR(&pv);
hamilt::Operator* ekinetic
@@ -509,7 +562,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 +580,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,
@@ -547,6 +600,8 @@ void ModuleIO::ctrl_scf_lcao(UnitCell& ucell,
//------------------------------------------------------------------
if (inp.out_mat_l[0])
{
+ // Mode-2 MAT/ subdirectory is created lazily at write time.
+ ModuleBase::ensure_directory(global_mat_dir);
ModuleIO::Angmom_op mylcalculator(inp.orbital_dir,
ucell,
orb.get_rcutmax_Phi(),
@@ -558,7 +613,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..3acd1220972 100644
--- a/source/source_io/module_dhs/write_dh.cpp
+++ b/source/source_io/module_dhs/write_dh.cpp
@@ -1,5 +1,6 @@
#include "write_dh.h"
+#include "source_base/fs_compat.h"
#include "source_base/global_function.h"
#include "source_base/timer.h"
#include "source_io/module_hs/hs_dense_io.h"
@@ -43,10 +44,20 @@ 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;
+ // The mode-2 MAT/ subdirectory is created lazily here at write time; in
+ // modes 0/1 this is a no-op because mat_dir is the existing output dir.
+ ModuleBase::ensure_directory(mat_dir);
+ const int out_app_flag = PARAM.inp.out_app_flag;
+ // Mode 1 appends one frame per ionic step; the first step (istep == 0)
+ // truncates the fixed file so a rerun does not keep stale records. Only
+ // mode 2 embeds the step in the name.
+ const bool append = (out_app_flag == 1) && (params.istep > 0);
+ const bool numbered = (out_app_flag == 2);
#ifdef __MPI
Parallel_Orbitals serialV;
@@ -79,15 +90,15 @@ 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, numbered, params.istep);
// dH/dR is not a Hamiltonian: no Fermi energy annotation in the header
const double no_efermi = 0.0;
#ifdef __MPI
ModuleIO::write_hcontainer_csr(
- fr, &ucell, 8, &hR_s, params.istep, ispin, nspin, label, "", no_efermi, false);
+ fr, &ucell, 8, &hR_s, params.istep, append, ispin, nspin, label, "", no_efermi, false);
#else
ModuleIO::write_hcontainer_csr(
- fr, &ucell, 8, hR, params.istep, ispin, nspin, label, "", no_efermi, false);
+ fr, &ucell, 8, hR, params.istep, append, ispin, nspin, label, "", no_efermi, false);
#endif
if (params.ofs_running != nullptr)
{
@@ -117,19 +128,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) && (params.istep > 0);
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 19fb6bdcea6..f1bc24b05b5 100644
--- a/source/source_io/module_dipole/write_dipole.cpp
+++ b/source/source_io/module_dipole/write_dipole.cpp
@@ -70,6 +70,9 @@ void write_dipole(const UnitCell& ucell,
ofs_running << " Write dipole data to file: " << fn << std::endl;
// Empty local FFT partitions contribute zero but must still join the subsequent reduction.
+ // Do NOT reject nplane == 0 alone: 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 || (rhopw->nplane == 0 && rhopw->nrxx != 0))
{
ModuleBase::WARNING_QUIT("ModuleIO::write_dipole", "Invalid global grid dimensions or inconsistent local plane count");
@@ -119,13 +122,16 @@ 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::right << std::setw(6) << istep + 1 << std::scientific << std::setprecision(precision - 1);
- for (int direction = 0; direction < 3; ++direction)
+ // Write to file on master process only; ofs is opened only there.
+ if (GlobalV::MY_RANK == 0)
{
- ofs << " " << std::setw(19) << dipole_elec[direction];
+ ofs << std::right << std::setw(6) << istep + 1 << std::scientific << std::setprecision(precision - 1);
+ for (int direction = 0; direction < 3; ++direction)
+ {
+ ofs << " " << std::setw(19) << dipole_elec[direction];
+ }
+ ofs << std::endl;
}
- ofs << std::endl;
// Calculate ionic dipole moment
// Accumulate Z_v * taud, then transform to Cartesian coordinates.
diff --git a/source/source_io/module_dm/test/test_write_dmr.cpp b/source/source_io/module_dm/test/test_write_dmr.cpp
index e5876e7c07a..f8946e280ec 100644
--- a/source/source_io/module_dm/test/test_write_dmr.cpp
+++ b/source/source_io/module_dm/test/test_write_dmr.cpp
@@ -67,12 +67,26 @@ void fill_matrix(hamilt::HContainer& matrix, Parallel_Orbitals& pv, doub
hamilt::AtomPair pair(0, 0, 0, 0, 0, &pv, values);
matrix.insert_pair(pair);
}
+
+int count_frames(const std::string& content)
+{
+ const std::string tag = " --- Ionic Step ";
+ int count = 0;
+ std::string::size_type pos = 0;
+ while ((pos = content.find(tag, pos)) != std::string::npos)
+ {
+ ++count;
+ pos += tag.size();
+ }
+ return count;
+}
} // namespace
TEST(WriteDmr, GenFnameKeepsCurrentContract)
{
EXPECT_EQ(ModuleIO::dmr_gen_fname(1, 0, true, -1), "dmrs1_nao.csr");
- EXPECT_EQ(ModuleIO::dmr_gen_fname(1, 1, false, 2), "dmrs2g3_nao.csr");
+ EXPECT_EQ(ModuleIO::dmr_gen_fname(1, 1, false, 2), "dmrs2_nao.csr");
+ EXPECT_EQ(ModuleIO::dmr_gen_fname(1, 1, true, 2), "dmrs2g3_nao.csr");
EXPECT_EQ(ModuleIO::dmr_gen_fname(2, 0, true, 5), "dmrs1_nao.npz");
}
@@ -89,7 +103,7 @@ TEST(WriteDmr, CsrHeaderKeepsCurrentFormat)
double values[4] = {0.25, 0.0, 0.0, 0.75};
fill_matrix(matrix, pv, values);
- ModuleIO::write_dmr_csr(filename, &ucell, 4, &matrix, 0, 1, 2);
+ ModuleIO::write_dmr_csr(filename, &ucell, 4, &matrix, 0, 1, 2, false);
const std::string output = read_file(filename);
EXPECT_THAT(output, testing::HasSubstr(" --- Ionic Step 1 ---\n"));
@@ -101,3 +115,40 @@ TEST(WriteDmr, CsrHeaderKeepsCurrentFormat)
std::remove(filename.c_str());
}
+
+TEST(WriteDmr, ModeOneFixedNameTruncatesOnRerun)
+{
+ // Mirror the controller policy for out_app_flag == 1: the DM(R)
+ // filename stays fixed for every step; write_dmr_csr truncates at
+ // istep <= 0 and appends otherwise. A 0 -> 1 -> 0 sequence (two steps
+ // of one run, then the first step of a rerun in the same directory)
+ // must therefore use one filename and drop the previous run's frames.
+ const bool numbered = false; // out_app_flag == 1
+ EXPECT_EQ(ModuleIO::dmr_gen_fname(1, 0, numbered, 0), "dmrs1_nao.csr");
+ EXPECT_EQ(ModuleIO::dmr_gen_fname(1, 0, numbered, 1), "dmrs1_nao.csr");
+
+ UnitCell ucell;
+ init_unitcell(ucell);
+ Parallel_Orbitals pv;
+ init_serial_orbitals(pv);
+ hamilt::HContainer matrix(&pv);
+ double values[4] = {0.25, 0.0, 0.0, 0.75};
+ fill_matrix(matrix, pv, values);
+
+ std::string filename = testing::TempDir() + "/dmrs1_nao.csr";
+ std::remove(filename.c_str());
+
+ ModuleIO::write_dmr_csr(filename, &ucell, 4, &matrix, 0, 0, 1, true);
+ ModuleIO::write_dmr_csr(filename, &ucell, 4, &matrix, 1, 0, 1, true);
+ const std::string after_first_run = read_file(filename);
+ EXPECT_EQ(count_frames(after_first_run), 2);
+ EXPECT_THAT(after_first_run, testing::HasSubstr(" --- Ionic Step 2 ---\n"));
+
+ // First step of a rerun must truncate the accumulated frames.
+ ModuleIO::write_dmr_csr(filename, &ucell, 4, &matrix, 0, 0, 1, true);
+ const std::string after_rerun = read_file(filename);
+ EXPECT_EQ(count_frames(after_rerun), 1);
+ EXPECT_THAT(after_rerun, testing::HasSubstr(" --- Ionic Step 1 ---\n"));
+
+ std::remove(filename.c_str());
+}
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 8a2998c58c0..da27cbe92c8 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,181 @@ TEST(DMKTest,WriteDMK) {
};
+// ---------------------------------------------------------------------------
+// out_app_flag = 0: one fixed filename, truncated and overwritten each call.
+// ---------------------------------------------------------------------------
+TEST(DMKTest, Mode0FixedNameOverwrite) {
+ 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 -> the same fixed filename, overwritten each call.
+ 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();
+ // The second write replaced the first record: only step 2 remains.
+ EXPECT_THAT(str, testing::HasSubstr(" --- Ionic Step 2 ---"));
+ EXPECT_THAT(str, testing::Not(testing::HasSubstr(" --- Ionic Step 1 ---")));
+ // g-suffixed files must NOT exist.
+ ifs.open("dms1g1_nao.txt");
+ EXPECT_FALSE(ifs.good());
+ ifs.open("dms1g2_nao.txt");
+ EXPECT_FALSE(ifs.good());
+ remove("dms1_nao.txt");
+ remove("dms2_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: per-step g{step} filename; directory routing is handled by
+// set_global_dir and is covered by io_sys_var_test.
+// ---------------------------------------------------------------------------
+TEST(DMKTest, Mode2PerStepFile) {
+ 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..10e02669bb5 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");
+ // Modes 0 and 1 reuse a fixed filename (mode 0 overwrites the previous
+ // snapshot, mode 1 appends a new record); only mode 2 embeds the ionic
+ // step in the filename. The file body always records the real ionic step.
+ const int istep_file = (out_app_flag == 2) ? istep : -1;
+ // 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..12af7520c80 100644
--- a/source/source_io/module_dm/write_dmr.cpp
+++ b/source/source_io/module_dm/write_dmr.cpp
@@ -1,19 +1,19 @@
#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"
namespace ModuleIO
{
-std::string dmr_gen_fname(const int out_type, const int ispin, const bool append, const int istep)
+std::string dmr_gen_fname(const int out_type, const int ispin, const bool numbered, const int istep)
{
std::string fname = "dmr.csr";
if (out_type == 1)
{
- if (!append && istep >= 0)
+ if (numbered && istep >= 0)
{
// spa stands for sparse
fname = "dmrs" + std::to_string(ispin+1) + "g" + std::to_string(istep + 1) + "_nao.csr";
@@ -40,20 +40,24 @@ void write_dmr_csr(std::string& fname,
hamilt::HContainer* dm_serial,
const int istep,
const int ispin,
- const int nspin)
+ const int nspin,
+ const bool append)
{
// write the head: ION step number, basis number and R loop number
std::ofstream ofs;
- // mohan update 2025-05-26
- if(istep<=0)
+ // The first written step of a run (or a single-point SCF) truncates the
+ // fixed file so a rerun does not keep stale records; later steps append
+ // only when the caller asks for it (mode 1).
+ const bool append_now = append && (istep > 0);
+ if (append_now)
{
- ofs.open(fname);
+ ofs.open(fname, std::ios::app);
}
- else if(istep>0)
+ else
{
- ofs.open(fname, std::ios::app);
+ ofs.open(fname);
}
@@ -82,14 +86,20 @@ void write_dmr(const std::vector*> dmr,
const UnitCell* ucell,
const int precision,
const Parallel_2D& paraV,
- const bool append,
+ const int out_app_flag,
const int* iat2iwt,
const int nat,
+ const std::string& dmr_dir,
const int istep,
std::ofstream& ofs_running)
{
const int nspin = dmr.size();
assert(nspin > 0);
+ // Mode 1 appends one frame per ionic step; the first step (istep == 0)
+ // truncates the fixed file so a rerun does not keep stale records. Only
+ // mode 2 embeds the step in the name.
+ const bool append = (out_app_flag == 1) && (istep > 0);
+ const bool numbered = (out_app_flag == 2);
for (int ispin = 0; ispin < nspin; ispin++)
{
const int nbasis = dmr[ispin]->get_nbasis();
@@ -110,8 +120,8 @@ 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);
- write_dmr_csr(fname, ucell, precision, &dm_serial, istep, ispin, nspin);
+ std::string fname = dmr_dir + dmr_gen_fname(out_type, ispin, numbered, istep);
+ write_dmr_csr(fname, ucell, precision, &dm_serial, istep, ispin, nspin, append);
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..640fb54d896 100644
--- a/source/source_io/module_dm/write_dmr.h
+++ b/source/source_io/module_dm/write_dmr.h
@@ -16,13 +16,14 @@ namespace ModuleIO
*
* @param out_type The output type. 1: csr, 2: npz.
* @param ispin The spin value, starting from 0.
- * @param append A boolean indicating whether append the data to one file or create a new file.
+ * @param numbered Whether the ionic step is embedded in the filename as a g{step} suffix
+ * (true for out_app_flag == 2).
* @param istep The ION step (default: -1), starting from 0.
* @return The generated filename as a string.
*/
std::string dmr_gen_fname(const int out_type,
const int ispin,
- const bool append = true,
+ const bool numbered = false,
const int istep = -1);
/**
@@ -33,6 +34,8 @@ std::string dmr_gen_fname(const int out_type,
* @param precision Control the ouptut precision
* @param dm_serial A pointer to the Hamiltonian container.
* @param istep The current step number.
+ * @param append Whether to append to the existing file. The first written step
+ * (istep <= 0) always truncates.
*/
void write_dmr_csr(std::string& fname,
const UnitCell *ucell,
@@ -40,7 +43,8 @@ void write_dmr_csr(std::string& fname,
hamilt::HContainer* dm_serial,
const int istep,
const int ispin,
- const int nspin);
+ const int nspin,
+ const bool append);
/**
* Writes DMR to a file.
@@ -49,20 +53,21 @@ void write_dmr_csr(std::string& fname,
* @param ucell stands for unitcell
* @param precision Control the output precision
* @param paraV The parallel 2D object.
- * @param out_type The output file type. 1: csr, 2: npz.
- * @param sparse Whether output the sparse DM.
- * @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 out_app_flag Output mode: 0 = fixed filename, overwritten at every write; 1 = fixed filename,
+ * appended; 2 = per-step g{step} filename.
+ * @param iat2iwt The mapping from atom index to the first local orbital index.
+ * @param nat The number of atoms.
+ * @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,
const UnitCell *ucell,
const int precision,
const Parallel_2D& paraV,
- const bool append,
+ const int out_app_flag,
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..4305faa4b98 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,8 @@ bool ModuleIO::cal_dos(const int& is, // index for spin
if (GlobalV::MY_RANK == 0)
{
- if(out_app_flag==true)
+ const bool append_step = (out_app_flag == 1) && (istep > 0);
+ if (append_step)
{
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..5e719bfe740 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 = "./";
@@ -101,14 +101,14 @@ TEST_F(DosPWTest,Dos1)
{
#endif
std::ifstream ifs;
- ifs.open("doss1g1.txt");
+ ifs.open("doss1.txt");
std::string str((std::istreambuf_iterator(ifs)),std::istreambuf_iterator());
EXPECT_THAT(str, testing::HasSubstr("4801 # number of points"));
EXPECT_THAT(str, testing::HasSubstr(" -5.000000 0.500000 0.500000"));
EXPECT_THAT(str, testing::HasSubstr(" 18.000000 0.000000 8.000000"));
EXPECT_THAT(str, testing::HasSubstr("2.849588"));
ifs.close();
- remove("doss1g1.txt");
+ remove("doss1.txt");
#ifdef __MPI
}
#endif
@@ -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;
@@ -180,7 +180,7 @@ TEST_F(DosPWTest,Dos2)
{
#endif
std::ifstream ifs;
- ifs.open("doss1g1.txt");
+ ifs.open("doss1.txt");
std::string str1((std::istreambuf_iterator(ifs)),std::istreambuf_iterator());
EXPECT_THAT(str1, testing::HasSubstr("2526 # number of points"));
EXPECT_THAT(str1, testing::HasSubstr(" -5.002500 0.500000 0.500000"));
@@ -222,7 +222,7 @@ TEST_F(DosPWTest,Dos2)
}
EXPECT_EQ(row_count, declared_points);
ifs.close();
- remove("doss1g1.txt");
+ remove("doss1.txt");
#ifdef __MPI
}
#endif
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..add0fa08007 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");
@@ -113,8 +113,9 @@ TEST_F(WritePdosTextTest, BasicOutput)
remove("test_write_pdos_text.log");
}
-// istep >= 0 appends g{istep+1} to the file name.
-TEST_F(WritePdosTextTest, IstepFileName)
+// mode 0 keeps a fixed name even when istep >= 0; the step only appears in
+// the record header.
+TEST_F(WritePdosTextTest, Mode0FixedNameWithStep)
{
GlobalV::ofs_running.open("test_write_pdos_text.log");
@@ -127,12 +128,12 @@ 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");
+ const std::string str = read_file("./pdoss1.txt");
EXPECT_THAT(str, testing::HasSubstr("# istep: 3"));
- remove("./pdoss1g3.txt");
+ remove("./pdoss1.txt");
GlobalV::ofs_running.close();
remove("test_write_pdos_text.log");
}
@@ -163,7 +164,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 +178,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: per-step g{step} filename (the only mode that adds it).
+// ---------------------------------------------------------------------------
+TEST_F(WritePdosTextTest, Mode2PerStepFile)
+{
+ 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..e068430f1e1 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)
+ // Modes 0 and 1 reuse the fixed name (mode 0 overwrites, mode 1 appends
+ // a new DOS block per written step); only mode 2 keeps the g{N} suffix.
+ if(istep>=0 && out_app_flag==2)
{
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..434a39c6f80 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)
+ // Modes 0 and 1 reuse the fixed name (mode 0 overwrites, mode 1 appends
+ // a new DOS block per written step); only mode 2 keeps the g{N} suffix.
+ if(istep_in>=0 && out_app_flag==2)
{
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..c3ded7e75c6 100644
--- a/source/source_io/module_dos/write_pdos_text.cpp
+++ b/source/source_io/module_dos/write_pdos_text.cpp
@@ -17,24 +17,38 @@ 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;
+ // Modes 0 and 1 reuse the fixed filename (mode 0 overwrites, mode 1 appends
+ // each ionic step); only mode 2 embeds the step number in the filename.
+ // 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 == 2) ? istep : -1;
+ 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..6d6880aa33f 100644
--- a/source/source_io/module_energy/write_bands.cpp
+++ b/source/source_io/module_energy/write_bands.cpp
@@ -7,6 +7,7 @@
#include "source_base/module_parallel/para_bridge.h"
#include "source_base/timer.h"
#include "source_base/tool_title.h"
+#include "source_io/module_output/provenance_header.h"
#include "source_io/module_output/spin_tag.h"
#include "source_io/module_parameter/parameter.h" // mohan add 20250911
@@ -14,8 +15,28 @@
#include
#endif
+namespace
+{
+
+/**
+ * @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 int istep_one_based = istep + 1;
+ const std::string step_line = FmtCore::format("# IONIC STEP %d%s\n", istep_one_based, spin_tag.c_str());
+ return ModuleIO::provenance_header(ModuleIO::local_timestamp()) + step_line;
+}
+
+} // 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 +62,11 @@ 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_spin_tag = ModuleIO::make_compact_spin_tag(is, nspin0);
+ const std::string header = build_band_header(istep, header_spin_tag);
const double eshift = 0.0;
nscf_bands(is,
eig_file,
@@ -51,7 +74,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 +86,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 +114,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 +150,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 +178,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 +200,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 b67094449cc..5f30588ceca 100644
--- a/source/source_io/module_energy/write_eband_terms.cpp
+++ b/source/source_io/module_energy/write_eband_terms.cpp
@@ -1,16 +1,18 @@
#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_output/provenance_header.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 +20,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 +43,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_timestamp();
double etxc = 0.0;
double vtxc = 0.0;
@@ -68,6 +66,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 +119,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 +151,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 +171,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 +204,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