-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhartree_fock.h
More file actions
344 lines (284 loc) · 11.7 KB
/
Copy pathhartree_fock.h
File metadata and controls
344 lines (284 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
/*
* Original file: hartree-fock++.cc
* Modified by Xiaoyi Zhai, code for MP2 energy calculation was added.
* Copyright (C) 2004-2026 Edward F. Valeev
*
* This file is part of Libint library.
*
* Libint library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Libint library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Libint library. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef HARTREE_FOCK_H
#define HARTREE_FOCK_H
// standard C++ headers
#include <atomic>
#include <chrono>
#include <cmath>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <memory>
#include <mutex>
#include <sstream>
#include <thread>
#include <unordered_map>
#include <vector>
// Eigen matrix algebra library
#include <Eigen/Cholesky>
#include <Eigen/Dense>
#include <Eigen/Eigenvalues>
// Libint Gaussian integrals library
#include <libint2/dfbs_generator.h>
#include <libint2/diis.h>
#include <libint2/lcao/molden.h>
#include <libint2/util/intpart_iter.h>
#include <libint2.hpp>
#if !LIBINT2_CONSTEXPR_STATICS
#include <libint2/statics_definition.h>
#endif
#if defined(_OPENMP)
#include <omp.h>
#endif
#include <cstring>
/// to use precomputed shell pair data must decide on max precision a priori
const auto max_engine_precision = std::numeric_limits<double>::epsilon() / 1e10;
// use conservative screening method
constexpr auto screening_method = libint2::ScreeningMethod::SchwarzInf;
// uncomment if want to report integral timings
// N.B. integral engine timings are controled in engine.h
#define REPORT_INTEGRAL_TIMINGS
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>
Matrix; // import dense, dynamically sized Matrix type from Eigen;
// this is a matrix with row-major storage
// (http://en.wikipedia.org/wiki/Row-major_order)
// to meet the layout of the integrals returned by the Libint integral library
typedef Eigen::DiagonalMatrix<double, Eigen::Dynamic, Eigen::Dynamic>
DiagonalMatrix;
#ifdef LIBINT2_HAVE_BTAS
typedef btas::Tensor<double> tensor;
#endif
using libint2::Atom;
using libint2::BasisSet;
using libint2::BraKet;
using libint2::Operator;
using libint2::ScreeningMethod;
using libint2::Shell;
// Type definitions
using shellpair_list_t = std::unordered_map<size_t, std::vector<size_t>>;
using shellpair_data_t = std::vector<std::vector<
std::shared_ptr<libint2::ShellPair>>>; // in same order as shellpair_list_t
// Global variables (declared as extern)
extern shellpair_list_t obs_shellpair_list; // shellpair list for OBS
extern shellpair_data_t obs_shellpair_data; // shellpair data for OBS
// Utility functions
std::vector<Atom> read_geometry(const std::string& filename);
Matrix compute_soad_guess(const std::vector<Atom>& atoms);
Matrix compute_shellblock_norm(const BasisSet& obs, const Matrix& A);
// One-body integral functions
template <Operator obtype,
typename OperatorParams =
typename libint2::operator_traits<obtype>::oper_params_type>
std::array<Matrix, libint2::operator_traits<obtype>::nopers> compute_1body_ints(
const BasisSet& obs, OperatorParams oparams = OperatorParams());
// Two-body integral functions
template <libint2::Operator Kernel = libint2::Operator::coulomb>
Matrix compute_schwarz_ints(
const BasisSet& bs1, const BasisSet& bs2 = BasisSet(),
bool use_2norm = false, // use infty norm by default
typename libint2::operator_traits<Kernel>::oper_params_type params =
libint2::operator_traits<Kernel>::default_params());
Matrix compute_do_ints(const BasisSet& bs1, const BasisSet& bs2 = BasisSet(),
bool use_2norm = false // use infty norm by default
);
// Shell pair functions
std::tuple<shellpair_list_t, shellpair_data_t> compute_shellpairs(
const BasisSet& bs1, const BasisSet& bs2 = BasisSet(),
double threshold = 1e-12);
// Fock matrix functions
Matrix compute_2body_fock(
const BasisSet& obs, const Matrix& D,
double precision = std::numeric_limits<
double>::epsilon(), // discard contributions smaller than this
const Matrix& Schwarz = Matrix() // K_ij = sqrt(||(ij|ij)||_\infty); if
// empty, do not Schwarz screen
);
Matrix compute_2body_fock_general(
const BasisSet& obs, const Matrix& D, const BasisSet& D_bs,
bool D_is_sheldiagonal = false, // set D_is_shelldiagonal if doing SOAD
double precision = std::numeric_limits<
double>::epsilon() // discard contributions smaller than this
);
// Linear algebra functions
std::tuple<Matrix, Matrix, double> conditioning_orthogonalizer(
const Matrix& S, double S_condition_number_threshold);
std::tuple<Matrix, Matrix, size_t, double, double> gensqrtinv(
const Matrix& S, bool symmetric = false,
double max_condition_number = 1e8);
// Two-index integral function
Matrix compute_2body_2index_ints(const BasisSet& bs);
std::vector<double> ao2mo_incore_all(const BasisSet& obs,
const Matrix& C_occ,
const Matrix& C_virt);
std::vector<double> ao2mo_incore_batch(const BasisSet& obs,
const Matrix& C_occ,
const Matrix& C_virt,
const Matrix& C_virt_batch);
// Calculate MP2 energy
double cal_mp2_in_memory(const BasisSet& obs,
const Matrix& C_occ,
const Matrix& C_virt,
const Eigen::VectorXd& eps);
double cal_mp2_batch(const BasisSet& obs,
const Matrix& C_occ,
const Matrix& C_virt,
const Eigen::VectorXd& eps,
const int max_memory);
// Parallelization utilities
namespace libint2 {
extern int nthreads;
/// fires off \c nthreads instances of lambda in parallel
template <typename Lambda>
void parallel_do(Lambda& lambda) {
#ifdef _OPENMP
#pragma omp parallel
{
auto thread_id = omp_get_thread_num();
lambda(thread_id);
}
#else // use C++11 threads
std::vector<std::thread> threads;
for (int thread_id = 0; thread_id != libint2::nthreads; ++thread_id) {
if (thread_id != nthreads - 1)
threads.push_back(std::thread(lambda, thread_id));
else
lambda(thread_id);
} // threads_id
for (int thread_id = 0; thread_id < nthreads - 1; ++thread_id)
threads[thread_id].join();
#endif
}
} // namespace libint2
// Template function implementations
template <Operator obtype, typename OperatorParams>
std::array<Matrix, libint2::operator_traits<obtype>::nopers> compute_1body_ints(
const BasisSet& obs, OperatorParams oparams) {
const auto n = obs.nbf();
const auto nshells = obs.size();
using libint2::nthreads;
typedef std::array<Matrix, libint2::operator_traits<obtype>::nopers>
result_type;
const unsigned int nopers = libint2::operator_traits<obtype>::nopers;
result_type result;
for (auto& r : result) r = Matrix::Zero(n, n);
// construct the 1-body integrals engine
std::vector<libint2::Engine> engines(nthreads);
engines[0] = libint2::Engine(obtype, obs.max_nprim(), obs.max_l(), 0);
// pass operator params to the engine, e.g.
// nuclear attraction ints engine needs to know where the charges sit ...
// the nuclei are charges in this case; in QM/MM there will also be classical
// charges
engines[0].set_params(oparams);
for (size_t i = 1; i != nthreads; ++i) {
engines[i] = engines[0];
}
auto shell2bf = obs.shell2bf();
auto compute = [&](int thread_id) {
const auto& buf = engines[thread_id].results();
// loop over unique shell pairs, {s1,s2} such that s1 >= s2
// this is due to the permutational symmetry of the real integrals over
// Hermitian operators: (1|2) = (2|1)
for (auto s1 = 0l, s12 = 0l; s1 != nshells; ++s1) {
auto bf1 = shell2bf[s1]; // first basis function in this shell
auto n1 = obs[s1].size();
auto s1_offset = s1 * (s1 + 1) / 2;
for (auto s2 : obs_shellpair_list[s1]) {
auto s12 = s1_offset + s2;
if (s12 % nthreads != thread_id) continue;
auto bf2 = shell2bf[s2];
auto n2 = obs[s2].size();
auto n12 = n1 * n2;
// compute shell pair; return is the pointer to the buffer
engines[thread_id].compute(obs[s1], obs[s2]);
for (unsigned int op = 0; op != nopers; ++op) {
// "map" buffer to a const Eigen Matrix, and copy it to the
// corresponding blocks of the result
Eigen::Map<const Matrix> buf_mat(buf[op], n1, n2);
result[op].block(bf1, bf2, n1, n2) = buf_mat;
if (s1 != s2) // if s1 >= s2, copy {s1,s2} to the corresponding
// {s2,s1} block, note the transpose!
result[op].block(bf2, bf1, n2, n1) = buf_mat.transpose();
}
}
}
}; // compute lambda
libint2::parallel_do(compute);
return result;
}
template <libint2::Operator Kernel>
Matrix compute_schwarz_ints(
const BasisSet& bs1, const BasisSet& _bs2, bool use_2norm,
typename libint2::operator_traits<Kernel>::oper_params_type params) {
const BasisSet& bs2 = (_bs2.empty() ? bs1 : _bs2);
const auto nsh1 = bs1.size();
const auto nsh2 = bs2.size();
const auto bs1_equiv_bs2 = (&bs1 == &bs2);
Matrix K = Matrix::Zero(nsh1, nsh2);
// construct the 2-electron repulsion integrals engine
using libint2::Engine;
using libint2::nthreads;
std::vector<Engine> engines(nthreads);
// !!! very important: cannot screen primitives in Schwarz computation !!!
auto epsilon = 0.;
engines[0] = Engine(Kernel, std::max(bs1.max_nprim(), bs2.max_nprim()),
std::max(bs1.max_l(), bs2.max_l()), 0, epsilon, params);
for (size_t i = 1; i != nthreads; ++i) {
engines[i] = engines[0];
}
std::cout << "computing Schwarz bound prerequisites (kernel=" << (int)Kernel
<< ") ... ";
libint2::Timers<1> timer;
timer.set_now_overhead(25);
timer.start(0);
auto compute = [&](int thread_id) {
const auto& buf = engines[thread_id].results();
// loop over permutationally-unique set of shells
for (auto s1 = 0l, s12 = 0l; s1 != nsh1; ++s1) {
auto n1 = bs1[s1].size(); // number of basis functions in this shell
auto s2_max = bs1_equiv_bs2 ? s1 : nsh2 - 1;
for (auto s2 = 0; s2 <= s2_max; ++s2, ++s12) {
if (s12 % nthreads != thread_id) continue;
auto n2 = bs2[s2].size();
auto n12 = n1 * n2;
engines[thread_id].compute2<Kernel, BraKet::xx_xx, 0>(bs1[s1], bs2[s2],
bs1[s1], bs2[s2]);
assert(buf[0] != nullptr &&
"to compute Schwarz ints turn off primitive screening");
// to apply Schwarz inequality to individual integrals must use the
// "diagonal" elements to apply it to sets of functions (e.g. shells)
// use the whole shell-set of ints here
Eigen::Map<const Matrix> buf_mat(buf[0], n12, n12);
auto norm2 =
use_2norm ? buf_mat.norm() : buf_mat.lpNorm<Eigen::Infinity>();
K(s1, s2) = std::sqrt(norm2);
if (bs1_equiv_bs2) K(s2, s1) = K(s1, s2);
}
}
}; // thread lambda
libint2::parallel_do(compute);
timer.stop(0);
std::cout << "done (" << timer.read(0) << " s)" << std::endl;
return K;
}
#endif // HARTREE_FOCK_H