2020namespace scf ::driver {
2121namespace {
2222
23+ // Comparison between convergence metrics based on floating point type
2324struct 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
3550const 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;
4065using 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
5167template <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
5470template <typename WfType>
5571using elec_egy_pt = simde::eval_braket<WfType, electronic_hamiltonian, WfType>;
@@ -60,15 +76,7 @@ using pt = simde::Optimize<egy_pt<WfType>, WfType>;
6076template <typename WfType>
6177using 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
7381MODULE_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
94106MODULE_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