Skip to content

Commit e1946af

Browse files
authored
feat(gint): add single-precision (fp32) support for grid integration module (Useful Information for implementing mixed (single/double) precision calculations for grid integral operation (#7149)
* module_gint: lay Phase A groundwork for fp32 CPU execution Add the execution-precision scaffolding without changing the default fp64 behavior: introduce GintRealPrecision/GintExecConfig, extend cal_gint_vl and cal_gint_rho with optional config entry points, instantiate BaseMatrix<float>/AtomPair<float>/HContainer<float>, and expose GintInfo::get_hr<float>() for future internal fp32 buffers. Also add serial HContainer cast helpers in gint_common so later phases can move between float and double containers at the module_gint boundary, and record the stage handoff in progress.md. Validation: ../build.sh build_phase_a; cmake --build build_phase_a -j14; OMP_NUM_THREADS=7 mpirun -n 2 --bind-to socket build_phase_a/abacus_2g in tests/performance/P101_si32_lcao completed successfully. Extracted runtime properties differ from the checked-in result.ref and are documented in progress.md for follow-up. * module_gint: add staged fp32 internal path for gint_vl Stage B focuses on the CPU gint_vl path while keeping the public hR interface in double precision. Gint_vl is refactored into a precision-aware dispatcher plus templated Real kernels so the module can execute either fp64 or fp32 internally based on GintExecConfig without changing existing callers. The fp32 path now casts the local vr_eff buffer once, evaluates phi/phi_vldr3 in float, accumulates into HContainer<float>, then casts the serial result back to HContainer<double> before compose_hr_gint/transfer_hr_gint_to_hR. compose_hr_gint is generalized to work with both float and double containers, and gint_interface now forwards the execution config into the CPU gint_vl entry point. During full linking, the new fp32 phi path exposed a missing GintAtom<float> explicit instantiation; this commit adds the required set_phi<float> and set_phi_dphi<float> instantiations so the single-precision gint_vl chain links cleanly end to end. Validation for this stage: ../build.sh build_phase_b; cmake --build build_phase_b -j14; OMP_NUM_THREADS=7 mpirun -n 2 --bind-to socket build_phase_b/abacus_2g in tests/performance/P101_si32_lcao. The build and runtime both succeeded. The extracted P101 outputs remain identical to stage A under the current default fp64 integration path, which is expected because upper-layer callers still pass the default execution config. progress.md is updated with the implementation notes, the transient link issue, and the verification record for stage B. * module_gint: implement phase C mixed-precision gint_rho pipeline Phase C of the module_gint fp32 support plan is now in place for the CPU gint_rho path. This keeps the public API and external rho accumulation in double precision while enabling an internal float execution path that mirrors the phase-B gint_vl structure. Key code changes: - refactor Gint_rho into a dispatcher plus cal_gint_impl<Real>() so cpu_internal_real can select fp64 or fp32 internally - move dm_gint allocation into template-local std::vector<HContainer<Real>> buffers instead of storing a fixed double container in the class - generalize transfer_dm_2d_to_gint to transfer_dm_2d_to_gint<TGint, TDM>() and add the float<-double instantiation by gathering serial double DMR first and then casting locally - extend PhiOperator::phi_dot_phi to support independent input/output precisions so float phi and phi_dm can accumulate directly into external double rho - wire GintExecConfig through cal_gint_rho CPU dispatch instead of ignoring the config argument Verification: - ../build.sh build_phase_c - cmake --build build_phase_c -j14 - OMP_NUM_THREADS=7 mpirun -n 2 --bind-to socket /home/dzc/abacus/abacus-mix/build_phase_c/abacus_2g in tests/performance/P101_si32_lcao (exit code 0) - catch_properties output remained stable relative to earlier stages: etotref -3403.2017700426458759, totalforceref 2.961504, totalstressref 469.743372; totaltimeref is still not extracted in this environment * module_gint: wire phase D SCF precision control for LCAO Phase D connects the mixed-precision gint execution paths from phases B/C to the LCAO SCF control flow. The SCF layer now owns a stateful precision controller, updates it after each iteration, and pushes the current GintExecConfig into the main LCAO gint_vl and gint_rho call sites. Key code changes: - add source_estate/module_charge/GintPrecisionController with the phase-D policy: start in fp32, switch to fp64 after two consecutive non-restart iterations with drho <= max(100 * scf_thr, 1e-5), and never switch back within the same SCF - store the controller inside Charge_Mixing and reset/update it from chgmixing_ks_lcao() and chgmixing_ks() - propagate current GintExecConfig from ESolver_KS_LCAO::iter_init() into Charge and Potential so upper layers can read precision state without coupling module_gint back to SCF logic - wire LCAO SCF main-path gint callers to the propagated config: veff_lcao uses Potential::get_gint_exec_config() for non-meta-GGA gint_vl, rho_tau_lcao uses Charge::get_gint_exec_config() for gint_rho, and the ElecStateLCAO PEXSI dm2rho path now follows the same config - keep non-SCF and post-processing paths on the default fp64 behavior by not pushing a non-default config into those paths Verification: - ../build.sh build_phase_d - cmake --build build_phase_d -j14 - OMP_NUM_THREADS=7 mpirun -n 2 --bind-to socket /home/dzc/abacus/abacus-mix/build_phase_d/abacus_2g in tests/performance/P101_si32_lcao (exit code 0) - catch_properties output remained stable: etotref -3403.2017700426458759, totalforceref 2.961504, totalstressref 469.743372; totaltimeref is still not extracted in this environment * module_gint: redo phase D with scoped SCF precision context Replace the first phase-D wiring with a less invasive integration path for LCAO SCF mixed precision. Key changes: - keep GintPrecisionController as the SCF policy holder, but move ownership into ESolver_KS_LCAO instead of Charge_Mixing - add ModuleGint::current_exec_config() and ModuleGint::ScopedExecConfig so module_gint can read the active precision from a scoped context - scope the active GintExecConfig inside ESolver_KS_LCAO::hamilt2rho_single(), which covers the main updateHk -> veff_lcao -> dm2rho path without threading config through Charge or Potential - add no-config overloads for cal_gint_vl() and cal_gint_rho() that fall back to the scoped module_gint context - revert the previous intrusive state propagation from Charge, Potential, Charge_Mixing, veff_lcao, rho_tau_lcao, and the PEXSI dm2rho call site - keep non-SCF callers on the default fp64 path unless they explicitly create a scoped config Validation: - ../build.sh build_phase_d - cmake --build build_phase_d -j14 - cd tests/performance/P101_si32_lcao && OMP_NUM_THREADS=7 mpirun -n 2 --bind-to socket /home/dzc/abacus/abacus-mix/build_phase_d/abacus_2g Observed results: - etotref -3403.2017700426454212 - totalforceref 2.961504 - totalstressref 469.743372 - Gint cal_gint_vl: 1.70 s / 9 calls - Gint cal_gint_rho: 1.73 s / 9 calls - LCAO_domain dm2rho: 1.78 s / 9 calls * module_gint: complete phase E validation and test scaffolding Phase E adds focused regression coverage around the new CPU single-precision execution path and the stage-D precision scheduler. The module_gint test tree now exercises get_hr<float>(), HContainer casting helpers, transfer_dm_2d_to_gint<float,double>(), and ScopedExecConfig restore semantics. To make those paths testable, GintInfo exposes lightweight test-only factory helpers and get_hr<T>() is moved inline into the header. The stage also adds runtime override support to GintPrecisionController through ABACUS_GINT_FORCE_CPU_REAL={fp32,fp64,auto}, plus a dedicated estate-side unit test covering reset/update behavior under forced and automatic modes. This keeps the phase-D scoped-context design intact while giving us a low-intrusion switch for performance and numerical comparison runs. While building the new tests, phase E uncovered and fixed a real serial-path bug in make_cast_hcontainer(): the old code used the parallel insert_ijrs overload even when paraV was absent. The serial branch now reconstructs the destination HContainer topology from the source atom-pair/R-index layout before allocating and casting values. Validation performed for this commit: * ../build.sh build_phase_e * cmake --build build_phase_e -j14 * cmake --build build_phase_e -j14 --target abacus_2g * ../build.sh build_phase_e_ut * cmake -S . -B build_phase_e_ut -DBUILD_TESTING=ON * cmake --build build_phase_e_ut -j14 --target MODULE_LCAO_gint_common_test MODULE_LCAO_gint_precision_test MODULE_ESTATE_gint_precision_controller * ctest --output-on-failure -R 'MODULE_LCAO_gint_common_test|MODULE_LCAO_gint_precision_test|MODULE_ESTATE_gint_precision_controller' * OMP_NUM_THREADS=7 mpirun -n 2 --bind-to socket build_phase_e/abacus_2g in tests/performance/P101_si32_lcao * ABACUS_GINT_FORCE_CPU_REAL=fp32 OMP_NUM_THREADS=7 mpirun -n 2 --bind-to socket build_phase_e/abacus_2g * ABACUS_GINT_FORCE_CPU_REAL=fp64 OMP_NUM_THREADS=7 mpirun -n 2 --bind-to socket build_phase_e/abacus_2g Observed on P101_si32_lcao: mixed/fp32/fp64 total energies stay within about 1e-12 eV, extracted force/stress totals remain identical at script precision, and no clear speedup appears yet, suggesting other work such as set_phi or conversion overhead still dominates this case. * module_gint: simplify precision switch logic in GintPrecisionController - Remove qualified-iteration counting mechanism, switch to fp64 immediately when drho falls below threshold - Simplify update_after_iteration interface (remove iter, conv_esolver, is_restart_step parameters) - Adjust switch threshold from 100*scf_thr to 1000*scf_thr - Update unit tests to match simplified interface - Add device cpu to performance test inputs - Update .gitignore for build/profiling artifacts * refactor(gint): make gint_rho use fp32-only rho cache Refactor Gint_rho to follow the same if-constexpr style used by gint_vl. For double precision runs, write rho contributions directly into the external double buffer and skip any temporary cache allocation or copy-back work. For single precision runs, convert the external rho buffer into a float cache before phi_dot_phi, use that cache during grid integration, and cast it back to the external double buffer after cal_gint finishes. * perf(gint): optimize set_phi by grouping orbitals into radial blocks - Introduce RadialBlock to GintAtom to store contiguous (L, N, m) orbital information. - Pre-calculate these blocks in the GintAtom constructor. - Refactor set_phi to iterate over radial blocks, significantly reducing the overhead of indexing and branching in the hot loop. - Walk the Ylm buffer linearly within each block, avoiding repeated lookups of atom_->iw2_ylm[iw]. * add "gint_precision" parameter * simplify precision control * unify some variables' name * fix: address PR review issues for gint-mix-precision - Remove progress.md (internal development log) - Clean .gitignore (remove personal profile/debug entries) - Remove unused is_restart_step variable in iter_finish - Enable precision update for all SCF calculations (relax, MD, etc.) - Restore P103_si128_lcao scf_nmax to 100 (was debug artifact) - Replace magic numbers in switch threshold with named constants - Add input validation guard for nspin=4 + gint_precision!=double * fix: replace C++17 features with C++11-compatible patterns for CI compatibility The project default CMAKE_CXX_STANDARD is C++11. The 'Build without MPI' and 'abacuslite' CI checks failed because gint_vl.cpp, gint_rho.cpp, and gint_common.cpp used C++17 features (if constexpr, std::is_same_v) that are unavailable at C++11/14. Replace all if constexpr with tag-dispatch overloading using std::true_type/std::false_type to select same-type vs cross-type code paths at compile time. Replace std::is_same_v<A,B> with std::is_same<A,B>::type for compile-time tag generation. * refactor: simplify C++11-compatible type dispatch in gint module Replace tag-dispatch overloading (dummy pointer tags, std::true_type/ std::false_type) with simpler C++11 patterns: - gint_vl.cpp / gint_rho.cpp: remove dummy tag parameters; rely on natural overload resolution (non-template double overload vs template). - gint_common.cpp: replace 6 tag-dispatch helpers (gather_dm_serial + gather_dm_nspin4) with 2 enable_if SFINAE overloads (gather_dm), unifying serial and nspin4 call paths. Net reduction of ~40 lines with identical semantics. * feat: add SCF output notice for gint precision mode (mix/single) - Print notification at SCF start when gint_precision is 'mix' or 'single' - Print notification when precision switches from fp32 to fp64 in mix mode - Change update_after_iteration to return bool indicating if switch occurred - Update unit tests to verify return values
1 parent 6289c6b commit e1946af

39 files changed

Lines changed: 974 additions & 123 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ abacus.json
2626
toolchain/install/
2727
toolchain/abacus_env.sh
2828
.trae
29+
compile_commands.json

docs/advanced/input_files/input-main.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
- [min\_dist\_coef](#min_dist_coef)
2929
- [device](#device)
3030
- [precision](#precision)
31+
- [gint\_precision](#gint_precision)
3132
- [timer\_enable\_nvtx](#timer_enable_nvtx)
3233
- [cell\_factor](#cell_factor)
3334
- [dm\_to\_rho](#dm_to_rho)
@@ -759,6 +760,16 @@
759760
- double: double precision
760761
- **Default**: double
761762

763+
### gint_precision
764+
765+
- **Type**: String
766+
- **Availability**: *Used only for LCAO basis set on CPU.*
767+
- **Description**: Specifies the precision when performing grid integral in LCAO calculations.
768+
- single: single precision
769+
- double: double precision
770+
- mix: mixed precision, starting from single precision and switching to double precision when the SCF residual becomes small enough
771+
- **Default**: double
772+
762773
### timer_enable_nvtx
763774

764775
- **Type**: Boolean

docs/parameters.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,17 @@ parameters:
256256
default_value: double
257257
unit: ""
258258
availability: Used only for plane wave basis set.
259+
- name: gint_precision
260+
category: System variables
261+
type: String
262+
description: |
263+
Specifies the precision when performing grid integral in LCAO calculations.
264+
* single: single precision
265+
* double: double precision
266+
* mix: mixed precision, starting from single precision and switching to double precision when the SCF residual becomes small enough
267+
default_value: double
268+
unit: ""
269+
availability: Used only for LCAO basis set on CPU.
259270
- name: timer_enable_nvtx
260271
category: System variables
261272
type: Boolean

source/source_esolver/esolver_ks_lcao.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,24 @@ void ESolver_KS_LCAO<TK, TR>::iter_init(UnitCell& ucell, const int istep, const
308308
module_charge::chgmixing_ks_lcao(iter, this->p_chgmix, this->dftu,
309309
this->dmat.dm->get_DMR_pointer(1)->get_nnr(), PARAM.inp);
310310

311+
if (iter == 1)
312+
{
313+
this->gint_precision_controller_.set_mode(PARAM.inp.gint_precision);
314+
this->gint_precision_controller_.reset_for_new_scf();
315+
this->gint_info_->set_exec_precision(this->gint_precision_controller_.current_precision());
316+
if (PARAM.inp.gint_precision == "mix")
317+
{
318+
GlobalV::ofs_running << "\n >> Gint mixed-precision mode: starting SCF with fp32"
319+
<< " (will switch to fp64 when drho is small enough)" << std::endl;
320+
std::cout << " >> NOTICE: Gint grid-integration starts with fp32 (mixed-precision mode)" << std::endl;
321+
}
322+
else if (PARAM.inp.gint_precision == "single")
323+
{
324+
GlobalV::ofs_running << "\n >> Gint single-precision mode: using fp32 throughout SCF" << std::endl;
325+
std::cout << " >> NOTICE: Gint grid-integration uses fp32 throughout SCF (single-precision mode)" << std::endl;
326+
}
327+
}
328+
311329
// mohan update 2012-06-05
312330
this->pelec->f_en.deband_harris = this->pelec->cal_delta_eband(ucell);
313331

@@ -439,6 +457,14 @@ void ESolver_KS_LCAO<TK, TR>::iter_finish(UnitCell& ucell, const int istep, int&
439457
// charge mixing is performed, potential is updated,
440458
// HF and kS energies are computed, meta-GGA, Jason and restart
441459
ESolver_KS::iter_finish(ucell, istep, iter, conv_esolver);
460+
const bool precision_switched = this->gint_precision_controller_.update_after_iteration(this->drho, this->scf_thr);
461+
this->gint_info_->set_exec_precision(this->gint_precision_controller_.current_precision());
462+
if (precision_switched)
463+
{
464+
GlobalV::ofs_running << "\n >> Gint precision switched: fp32 -> fp64 (drho = "
465+
<< this->drho << ")" << std::endl;
466+
std::cout << " >> NOTICE: Gint grid-integration precision switched from fp32 to fp64" << std::endl;
467+
}
442468

443469
// mix density matrix if mixing_restart + mixing_dmr + not first
444470
// mixing_restart at every iter except the last iter

source/source_esolver/esolver_ks_lcao.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "source_basis/module_nao/two_center_bundle.h" // nao basis
77
#include "source_lcao/module_gint/gint.h" // gint
88
#include "source_lcao/module_gint/gint_info.h"
9+
#include "source_estate/module_charge/gint_precision_controller.h"
910
#include "source_lcao/setup_deepks.h" // for deepks, mohan add 20251008
1011
#include "source_lcao/setup_exx.h" // for exx, mohan add 20251008
1112
#include "source_lcao/module_rdmft/rdmft.h" // rdmft
@@ -99,6 +100,8 @@ class ESolver_KS_LCAO : public ESolver_KS
99100
// because it's hard to seperate force and stress calculation in LCAO.
100101
ModuleBase::matrix scs;
101102
bool have_force = false;
103+
104+
GintPrecisionController gint_precision_controller_;
102105

103106

104107
public:

source/source_estate/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ list(APPEND objects
2121
module_pot/pot_xc_fdm.cpp
2222
module_pot/pot_cosikr.cpp
2323
module_charge/chgmixing.cpp
24+
module_charge/gint_precision_controller.cpp
2425
module_charge/charge.cpp
2526
module_charge/charge_init.cpp
2627
module_charge/charge_mpi.cpp
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#include "gint_precision_controller.h"
2+
3+
#include <algorithm>
4+
5+
void GintPrecisionController::set_mode(const std::string& precision_mode)
6+
{
7+
this->mode_ = parse_mode_(precision_mode);
8+
}
9+
10+
GintPrecisionController::PrecisionMode GintPrecisionController::parse_mode_(const std::string& precision_mode)
11+
{
12+
if (precision_mode == "single")
13+
{
14+
return PrecisionMode::single;
15+
}
16+
if (precision_mode == "mix")
17+
{
18+
return PrecisionMode::mix;
19+
}
20+
return PrecisionMode::double_mode;
21+
}
22+
23+
void GintPrecisionController::reset_for_new_scf()
24+
{
25+
switch (this->mode_)
26+
{
27+
case PrecisionMode::single:
28+
case PrecisionMode::mix:
29+
this->current_precision_ = ModuleGint::GintPrecision::fp32;
30+
this->locked_double_precision_ = false;
31+
break;
32+
case PrecisionMode::double_mode:
33+
default:
34+
this->current_precision_ = ModuleGint::GintPrecision::fp64;
35+
this->locked_double_precision_ = true;
36+
break;
37+
}
38+
}
39+
40+
bool GintPrecisionController::update_after_iteration(double drho, double scf_thr)
41+
{
42+
if (this->locked_double_precision_ || this->mode_ != PrecisionMode::mix)
43+
{
44+
return false;
45+
}
46+
47+
// Switch from fp32 to fp64 when drho is close enough to the target.
48+
// fp32 has ~7 significant digits (~1e-7 relative error), so we switch
49+
// well before that limit to let fp64 handle the final convergence.
50+
// The floor (kMinSwitchThreshold) prevents switching too early when
51+
// scf_thr is extremely tight.
52+
constexpr double kSwitchFactor = 1000.0;
53+
constexpr double kMinSwitchThreshold = 1.0e-5;
54+
const double switch_thr = std::max(kSwitchFactor * scf_thr, kMinSwitchThreshold);
55+
if (drho <= switch_thr)
56+
{
57+
this->current_precision_ = ModuleGint::GintPrecision::fp64;
58+
this->locked_double_precision_ = true;
59+
return true;
60+
}
61+
return false;
62+
}
63+
64+
ModuleGint::GintPrecision GintPrecisionController::current_precision() const
65+
{
66+
return this->current_precision_;
67+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#ifndef GINT_PRECISION_CONTROLLER_H
2+
#define GINT_PRECISION_CONTROLLER_H
3+
4+
#include "source_lcao/module_gint/gint_helper.h"
5+
6+
#include <string>
7+
8+
class GintPrecisionController
9+
{
10+
public:
11+
GintPrecisionController() = default;
12+
13+
void set_mode(const std::string& precision_mode);
14+
15+
void reset_for_new_scf();
16+
17+
/// Returns true if precision switched from fp32 to fp64 in this call.
18+
bool update_after_iteration(double drho, double scf_thr);
19+
20+
ModuleGint::GintPrecision current_precision() const;
21+
22+
private:
23+
enum class PrecisionMode
24+
{
25+
single,
26+
double_mode,
27+
mix
28+
};
29+
30+
static PrecisionMode parse_mode_(const std::string& precision_mode);
31+
32+
ModuleGint::GintPrecision current_precision_ = ModuleGint::GintPrecision::fp64;
33+
PrecisionMode mode_ = PrecisionMode::double_mode;
34+
bool locked_double_precision_ = true;
35+
};
36+
37+
#endif

source/source_estate/test/CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,11 @@ AddTest(
101101
../../source_io/module_output/output.cpp ../../source_base/module_fft/fft_bundle.cpp ../../source_base/module_fft/fft_cpu.cpp
102102
)
103103

104-
endif()
104+
AddTest(
105+
TARGET MODULE_ESTATE_gint_precision_controller
106+
LIBS parameter ${math_libs} base device
107+
SOURCES gint_precision_controller_test.cpp
108+
../module_charge/gint_precision_controller.cpp
109+
)
110+
111+
endif()
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include "gtest/gtest.h"
2+
3+
#include "../module_charge/gint_precision_controller.h"
4+
5+
TEST(GintPrecisionControllerTest, AutoModeSwitchesToFp64ImmediatelyWhenDrhoIsSmallEnough)
6+
{
7+
GintPrecisionController controller;
8+
9+
controller.set_mode("mix");
10+
controller.reset_for_new_scf();
11+
EXPECT_EQ(controller.current_precision(), ModuleGint::GintPrecision::fp32);
12+
13+
EXPECT_TRUE(controller.update_after_iteration(9.0e-5, 1.0e-7));
14+
EXPECT_EQ(controller.current_precision(), ModuleGint::GintPrecision::fp64);
15+
}
16+
17+
TEST(GintPrecisionControllerTest, DefaultModeStartsAndStaysFp64)
18+
{
19+
GintPrecisionController controller;
20+
21+
controller.reset_for_new_scf();
22+
EXPECT_EQ(controller.current_precision(), ModuleGint::GintPrecision::fp64);
23+
24+
EXPECT_FALSE(controller.update_after_iteration(9.0e-5, 1.0e-7));
25+
EXPECT_EQ(controller.current_precision(), ModuleGint::GintPrecision::fp64);
26+
}
27+
28+
TEST(GintPrecisionControllerTest, SingleModeStartsAndStaysFp32)
29+
{
30+
GintPrecisionController controller;
31+
32+
controller.set_mode("single");
33+
controller.reset_for_new_scf();
34+
EXPECT_EQ(controller.current_precision(), ModuleGint::GintPrecision::fp32);
35+
36+
EXPECT_FALSE(controller.update_after_iteration(9.0e-5, 1.0e-7));
37+
EXPECT_EQ(controller.current_precision(), ModuleGint::GintPrecision::fp32);
38+
}
39+
40+
TEST(GintPrecisionControllerTest, MixModeLocksFp64AfterSwitch)
41+
{
42+
GintPrecisionController controller;
43+
44+
controller.set_mode("mix");
45+
controller.reset_for_new_scf();
46+
EXPECT_TRUE(controller.update_after_iteration(9.0e-5, 1.0e-6));
47+
EXPECT_EQ(controller.current_precision(), ModuleGint::GintPrecision::fp64);
48+
49+
// After locking, should return false
50+
EXPECT_FALSE(controller.update_after_iteration(1.0, 1.0e-6));
51+
EXPECT_EQ(controller.current_precision(), ModuleGint::GintPrecision::fp64);
52+
}
53+
54+
TEST(GintPrecisionControllerTest, MixModeReturnsFalseWhenDrhoTooLarge)
55+
{
56+
GintPrecisionController controller;
57+
58+
controller.set_mode("mix");
59+
controller.reset_for_new_scf();
60+
// drho is large, should not switch yet
61+
EXPECT_FALSE(controller.update_after_iteration(1.0, 1.0e-7));
62+
EXPECT_EQ(controller.current_precision(), ModuleGint::GintPrecision::fp32);
63+
}

0 commit comments

Comments
 (0)