Skip to content

Commit 395872f

Browse files
jwaldrop107github-actions[bot]ryanmrichard
authored
DIIS (#29)
* SCF loop rewritten with DIIS option * DIIS acceleration working, but not compatible with uncertain values * remove floating point type restrictions from tests * Committing clang-format changes * DIIS default on Co-authored-by: Ryan Richard <ryanmrichard1@gmail.com> * update test settings for DIIS default --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Ryan Richard <ryanmrichard1@gmail.com>
1 parent 0412a76 commit 395872f

5 files changed

Lines changed: 227 additions & 138 deletions

File tree

src/scf/driver/driver.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ inline void load_modules(pluginplay::ModuleManager& mm) {
3030
inline void set_defaults(pluginplay::ModuleManager& mm) {
3131
mm.change_submod("Loop", "Electronic energy", "Electronic energy");
3232
mm.change_submod("Loop", "Density matrix", "Density matrix builder");
33-
mm.change_submod("Loop", "Guess update", "Diagonalization Fock update");
33+
mm.change_submod("Loop", "Diagonalizer",
34+
"Generalized eigensolve via Eigen");
35+
mm.change_submod("Loop", "Fock matrix builder", "Fock matrix builder");
3436
mm.change_submod("Loop", "One-electron Fock operator",
3537
"Restricted One-Electron Fock op");
3638
mm.change_submod("Loop", "Fock operator", "Restricted Fock Op");

src/scf/driver/scf_loop.cpp

Lines changed: 128 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -20,36 +20,52 @@
2020
namespace scf::driver {
2121
namespace {
2222

23+
// Comparison between convergence metrics based on floating point type
2324
struct Kernel {
24-
explicit Kernel(parallelzone::runtime::RuntimeView rv) : m_rv(rv) {}
25+
using rv_t = parallelzone::runtime::RuntimeView;
26+
27+
explicit Kernel(rv_t rv) : m_rv(rv) {}
28+
2529
template<typename FloatType>
2630
auto run(const tensorwrapper::buffer::BufferBase& a, double tol) {
2731
tensorwrapper::allocator::Eigen<FloatType> allocator(m_rv);
2832
const auto& eigen_a = allocator.rebind(a);
2933
return tensorwrapper::types::fabs(eigen_a.get_data(0)) < FloatType(tol);
3034
}
3135

32-
parallelzone::runtime::RuntimeView m_rv;
36+
rv_t m_rv;
37+
};
38+
39+
// Pull out nuclear-nuclear interaction term, if there is one.
40+
struct GrabNuclear : chemist::qm_operator::OperatorVisitor {
41+
using V_nn_type = simde::type::V_nn_type;
42+
43+
GrabNuclear() : chemist::qm_operator::OperatorVisitor(false) {}
44+
45+
void run(const V_nn_type& V_nn) { m_pv = &V_nn; }
46+
47+
const V_nn_type* m_pv = nullptr;
3348
};
3449

3550
const auto desc = R"(
3651
)";
3752

3853
} // namespace
3954

55+
using tensor_t = simde::type::tensor;
56+
using cmos_t = simde::type::cmos;
57+
using diis_t = tensorwrapper::diis::DIIS;
58+
using density_t = simde::type::decomposable_e_density;
59+
using fock_pt = simde::FockOperator<density_t>;
60+
using density_pt = simde::aos_rho_e_aos<cmos_t>;
61+
using v_nn_pt = simde::charge_charge_interaction;
62+
using fock_matrix_pt = simde::aos_f_e_aos;
63+
using diagonalizer_pt = simde::GeneralizedEigenSolve;
64+
using s_pt = simde::aos_s_e_aos;
4065
using simde::type::electronic_hamiltonian;
41-
using simde::type::hamiltonian;
42-
using simde::type::op_base_type;
43-
44-
using density_t = simde::type::decomposable_e_density;
45-
using fock_pt = simde::FockOperator<density_t>;
46-
using density_pt = simde::aos_rho_e_aos<simde::type::cmos>;
47-
using v_nn_pt = simde::charge_charge_interaction;
48-
using fock_matrix_pt = simde::aos_f_e_aos;
49-
using s_pt = simde::aos_s_e_aos;
5066

5167
template<typename WfType>
52-
using egy_pt = simde::eval_braket<WfType, hamiltonian, WfType>;
68+
using egy_pt = simde::eval_braket<WfType, simde::type::hamiltonian, WfType>;
5369

5470
template<typename WfType>
5571
using elec_egy_pt = simde::eval_braket<WfType, electronic_hamiltonian, WfType>;
@@ -60,15 +76,7 @@ using pt = simde::Optimize<egy_pt<WfType>, WfType>;
6076
template<typename WfType>
6177
using update_pt = simde::UpdateGuess<WfType>;
6278

63-
struct GrabNuclear : chemist::qm_operator::OperatorVisitor {
64-
using V_nn_type = simde::type::V_nn_type;
65-
66-
GrabNuclear() : chemist::qm_operator::OperatorVisitor(false) {}
67-
68-
void run(const V_nn_type& V_nn) { m_pv = &V_nn; }
69-
70-
const V_nn_type* m_pv = nullptr;
71-
};
79+
using tensorwrapper::utilities::floating_point_dispatch;
7280

7381
MODULE_CTOR(SCFLoop) {
7482
using wf_type = simde::type::rscf_wf;
@@ -81,47 +89,59 @@ MODULE_CTOR(SCFLoop) {
8189
add_input<double>("density tolerance").set_default(1.0E-6);
8290
add_input<double>("gradient tolerance").set_default(1.0E-6);
8391

92+
const std::size_t diis_sample_default = 5;
93+
add_input<bool>("DIIS").set_default(true);
94+
add_input<std::size_t>("DIIS max samples").set_default(diis_sample_default);
95+
8496
add_submodule<elec_egy_pt<wf_type>>("Electronic energy");
8597
add_submodule<density_pt>("Density matrix");
86-
add_submodule<update_pt<wf_type>>("Guess update");
98+
add_submodule<s_pt>("Overlap matrix builder");
99+
add_submodule<fock_matrix_pt>("Fock matrix builder");
100+
add_submodule<diagonalizer_pt>("Diagonalizer");
87101
add_submodule<fock_pt>("One-electron Fock operator");
88102
add_submodule<fock_pt>("Fock operator");
89-
add_submodule<fock_matrix_pt>("Fock matrix builder");
90103
add_submodule<v_nn_pt>("Charge-charge");
91-
add_submodule<s_pt>("Overlap matrix builder");
92104
}
93105

94106
MODULE_RUN(SCFLoop) {
95-
using wf_type = simde::type::rscf_wf;
96-
using density_op_type = simde::type::rho_e<simde::type::cmos>;
107+
using wf_type = simde::type::rscf_wf;
108+
using density_op_type = simde::type::rho_e<cmos_t>;
109+
97110
const auto&& [braket, psi0] = pt<wf_type>::unwrap_inputs(inputs);
98-
// TODO: Assert bra == ket == psi0
99-
const auto& H = braket.op();
100-
const auto& H_elec = H.electronic_hamiltonian();
101-
const auto& H_core = H_elec.core_hamiltonian();
102-
const auto& aos = psi0.orbitals().from_space();
103111
const auto max_iter = inputs.at("max iterations").value<unsigned int>();
104112
const auto e_tol = inputs.at("energy tolerance").value<double>();
105113
const auto dp_tol = inputs.at("density tolerance").value<double>();
106114
const auto g_tol = inputs.at("gradient tolerance").value<double>();
107115

108-
auto& egy_mod = submods.at("Electronic energy");
109-
auto& density_mod = submods.at("Density matrix");
110-
auto& update_mod = submods.at("Guess update");
111-
auto& fock_mod = submods.at("One-electron Fock operator");
112-
auto& Fock_mod = submods.at("Fock operator");
113-
auto& V_nn_mod = submods.at("Charge-charge");
114-
// TODO: should be split off into orbital gradient module
115-
auto& F_mod = submods.at("Fock matrix builder");
116-
auto& S_mod = submods.at("Overlap matrix builder");
117-
118-
// Step 1: Nuclear-nuclear repulsion
116+
auto& egy_mod = submods.at("Electronic energy");
117+
auto& density_mod = submods.at("Density matrix");
118+
auto& diagonalizer_mod = submods.at("Diagonalizer");
119+
auto& fock_mod = submods.at("One-electron Fock operator");
120+
auto& Fock_mod = submods.at("Fock operator");
121+
auto& V_nn_mod = submods.at("Charge-charge");
122+
auto& F_mod = submods.at("Fock matrix builder");
123+
auto& S_mod = submods.at("Overlap matrix builder");
124+
125+
// Unwrap Braket and trial wavefunction
126+
// TODO: Assert bra == ket == psi0
127+
const auto& H = braket.op();
128+
const auto& H_elec = H.electronic_hamiltonian();
129+
const auto& H_core = H_elec.core_hamiltonian();
130+
const auto& aos = psi0.orbitals().from_space();
131+
132+
// DIIS settings
133+
const auto diis_on = inputs.at("DIIS").value<bool>();
134+
const auto diis_max_samples =
135+
inputs.at("DIIS max samples").value<std::size_t>();
136+
diis_t diis(diis_max_samples);
137+
138+
// Nuclear-nuclear repulsion
119139
GrabNuclear visitor;
120140
H.visit(visitor);
121141
bool has_nn = (visitor.m_pv != nullptr);
122142

123143
// TODO: Clean up charges class to make this easier...
124-
simde::type::tensor e_nuclear(0.0);
144+
tensor_t e_nuclear(0.0);
125145
if(has_nn) {
126146
const auto& V_nn = *visitor.m_pv;
127147
const auto n_lhs = V_nn.lhs_particle().as_nuclei();
@@ -143,109 +163,121 @@ MODULE_RUN(SCFLoop) {
143163
chemist::braket::BraKet s_mn(aos, simde::type::s_e_type{}, aos);
144164
const auto& S = S_mod.run_as<s_pt>(s_mn);
145165

146-
wf_type psi_old = psi0;
147-
simde::type::tensor e_old;
148-
149-
density_op_type rho_hat(psi_old.orbitals(), psi_old.occupations());
150-
chemist::braket::BraKet P_mn(aos, rho_hat, aos);
151-
const auto& P = density_mod.run_as<density_pt>(P_mn);
152-
density_t rho_old(P, psi_old.orbitals());
153-
154-
auto& logger = get_runtime().logger();
166+
// For convergence checking
167+
wf_type psi_old;
168+
density_t rho_old;
169+
tensor_t e_old;
170+
tensor_t F_old;
155171

172+
// Initialize loop
156173
unsigned int iter = 0;
174+
auto& logger = get_runtime().logger();
157175
while(iter < max_iter) {
158-
// Step 2: Old density is used to create the corresponding Fock operator
159-
// TODO: Make easier to go from many-electron to one-electron
160-
// TODO: template fock_pt on Hamiltonian type and only pass H_elec
161-
const auto& f_old = fock_mod.run_as<fock_pt>(H, rho_old);
162-
163-
// Step 3: Fock operator used to compute new wavefunction/density
164-
const auto& psi_new =
165-
update_mod.run_as<update_pt<wf_type>>(f_old, psi_old);
176+
// Step 1: Generate trial wavefunction
177+
wf_type psi;
178+
if(iter > 0) {
179+
// Diagonalize the Fock matrix
180+
const auto&& [evalues, evectors] =
181+
diagonalizer_mod.run_as<diagonalizer_pt>(F_old, S);
182+
183+
// Construct new trial wavefunction
184+
cmos_t cmos(evalues, aos, evectors);
185+
psi = wf_type(psi_old.orbital_indices(), cmos);
186+
} else {
187+
// Use trial wavefunction provided from initial guess
188+
psi = psi0;
189+
}
166190

167-
density_op_type rho_hat_new(psi_new.orbitals(), psi_new.occupations());
168-
chemist::braket::BraKet P_mn_new(aos, rho_hat_new, aos);
169-
const auto& P_new = density_mod.run_as<density_pt>(P_mn_new);
191+
// Step 2: Construct electronic density
192+
density_op_type rho_hat(psi.orbitals(), psi.occupations());
193+
chemist::braket::BraKet P_mn(aos, rho_hat, aos);
194+
const auto& P = density_mod.run_as<density_pt>(P_mn);
195+
density_t rho(P, psi.orbitals());
170196

171-
density_t rho_new(P_new, psi_new.orbitals());
197+
// Step 3: Construct new Fock matrix
198+
const auto& f_hat = fock_mod.run_as<fock_pt>(H, rho);
199+
chemist::braket::BraKet f_mn(aos, f_hat, aos);
200+
auto F = F_mod.run_as<fock_matrix_pt>(f_mn);
172201

173202
// Step 4: New electronic energy
174203
// Step 4a: New Fock operator to new electronic Hamiltonian
175-
// TODO: Should just be H_core + F;
176-
const auto& F_new = Fock_mod.run_as<fock_pt>(H, rho_new);
204+
// TODO: Should just be H_core + F_hat;
205+
const auto& F_hat = Fock_mod.run_as<fock_pt>(H, rho);
177206
electronic_hamiltonian H_new;
178207
for(std::size_t i = 0; i < H_core.size(); ++i)
179208
H_new.emplace_back(H_core.coefficient(i),
180209
H_core.get_operator(i).clone());
181-
for(std::size_t i = 0; i < F_new.size(); ++i)
182-
H_new.emplace_back(F_new.coefficient(i),
183-
F_new.get_operator(i).clone());
210+
for(std::size_t i = 0; i < F_hat.size(); ++i)
211+
H_new.emplace_back(F_hat.coefficient(i),
212+
F_hat.get_operator(i).clone());
184213

185214
// Step 4b: New electronic hamiltonian to new electronic energy
186-
chemist::braket::BraKet H_00(psi_new, H_new, psi_new);
187-
auto e_new = egy_mod.run_as<elec_egy_pt<wf_type>>(H_00);
215+
chemist::braket::BraKet H_00(psi, H_new, psi);
216+
auto e = egy_mod.run_as<elec_egy_pt<wf_type>>(H_00);
188217

189218
auto e_msg = "SCF iteration = " + std::to_string(iter) + ":";
190-
e_msg += " Electronic Energy = " + e_new.to_string();
219+
e_msg += " Electronic Energy = " + e.to_string();
191220
logger.log(e_msg);
192221

193-
bool converged = false;
194222
// Step 5: Converged?
223+
bool converged = false;
195224
if(iter > 0) {
196225
// Change in the energy
197-
simde::type::tensor de;
198-
de("") = e_new("") - e_old("");
226+
tensor_t de;
227+
de("") = e("") - e_old("");
199228

200229
// Change in the density
201-
simde::type::tensor dp;
230+
tensor_t dp;
202231
const auto& P_old = rho_old.value();
203-
dp("m,n") = rho_new.value()("m,n") - P_old("m,n");
232+
dp("m,n") = rho.value()("m,n") - P_old("m,n");
204233
auto dp_norm = tensorwrapper::operations::infinity_norm(dp);
205234

206235
// Orbital gradient: FPS-SPF
207-
// TODO: module satisfying BraKet(aos, Commutator(F,P), aos)
208-
const auto& f_new = fock_mod.run_as<fock_pt>(H, rho_new);
209-
chemist::braket::BraKet F_mn(aos, f_new, aos);
210-
const auto& F_matrix = F_mod.run_as<fock_matrix_pt>(F_mn);
211-
212-
auto grad = commutator(F_matrix, P_new, S);
213-
214-
simde::type::tensor grad_norm;
236+
auto grad = commutator(F, P, S);
237+
tensor_t grad_norm;
215238
grad_norm("") = grad("m,n") * grad("n,m");
216239

217-
Kernel k(get_runtime());
240+
// Log convergence metrics
241+
logger.log(" dE = " + de.to_string());
242+
logger.log(" dP = " + dp_norm.to_string());
243+
logger.log(" dG = " + grad_norm.to_string());
218244

219-
using tensorwrapper::utilities::floating_point_dispatch;
245+
// Check for convergence
246+
Kernel k(get_runtime());
220247
auto e_conv = floating_point_dispatch(k, de.buffer(), e_tol);
221248
auto g_conv = floating_point_dispatch(k, grad_norm.buffer(), g_tol);
222249
auto dp_conv = floating_point_dispatch(k, dp_norm.buffer(), dp_tol);
250+
if(e_conv && g_conv && dp_conv) converged = true;
223251

224-
logger.log(" dE = " + de.to_string());
225-
logger.log(" dP = " + dp_norm.to_string());
226-
logger.log(" dG = " + grad_norm.to_string());
252+
// If using DIIS and not converged, extrapolate new Fock matrix
253+
if(diis_on && !converged) { F = diis.extrapolate(F, grad); }
254+
} else if(diis_on) {
255+
// For DIIS, still need to the orbital gradient
256+
auto grad = commutator(F, P, S);
227257

228-
if(e_conv && g_conv && dp_conv) converged = true;
258+
// If using DIIS, extrapolate new Fock matrix
259+
F = diis.extrapolate(F, grad);
229260
}
230261

231262
// Step 6: Not converged so reset
232-
e_old = e_new;
233-
psi_old = psi_new;
234-
rho_old = rho_new;
263+
e_old = e;
264+
psi_old = psi;
265+
rho_old = rho;
266+
F_old = F;
235267
if(converged) break;
236268
++iter;
237269
}
238270
if(iter == max_iter) throw std::runtime_error("SCF failed to converge");
239271

240-
simde::type::tensor e_total;
272+
tensor_t e_total;
241273

242274
// e_nuclear is a double. This hack converts it to udouble (if needed)
243275
tensorwrapper::allocator::Eigen<double> dalloc(get_runtime());
244276
using tensorwrapper::types::udouble;
245277
tensorwrapper::allocator::Eigen<udouble> ualloc(get_runtime());
246278

247279
if(ualloc.can_rebind(e_old.buffer())) {
248-
simde::type::tensor temp(e_old);
280+
tensor_t temp(e_old);
249281
auto val = dalloc.rebind(e_nuclear.buffer()).get_data(0);
250282
ualloc.rebind(temp.buffer()).set_data(0, val);
251283
e_nuclear = temp;

0 commit comments

Comments
 (0)