votca 2026-dev
Loading...
Searching...
No Matches
screening_kernels.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2026 The VOTCA Development Team
3 * (http://www.votca.org)
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License")
6 *
7 * You may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20// Standard includes
21#include <algorithm>
22#include <cmath>
23#include <utility>
24#include <vector>
25
26// Local VOTCA includes
29
30namespace votca {
31namespace xtp {
32
34
35namespace {
36
37// Lower-triangle tiles (I >= J) of an n x n matrix in blocks of size bs.
38std::vector<std::pair<Index, Index>> LowerTiles(Index n, Index bs) {
39 const Index nt = (n + bs - 1) / bs;
40 std::vector<std::pair<Index, Index>> tiles;
41 tiles.reserve(std::size_t(nt * (nt + 1) / 2));
42 for (Index J = 0; J < nt; ++J) {
43 for (Index I = J; I < nt; ++I) {
44 tiles.emplace_back(I, J);
45 }
46 }
47 return tiles;
48}
49
50// S_lower += Xp^T Xp - Xn^T Xn on the given tiles, one tile per task.
51void AccumulateTiles(Eigen::MatrixXd& S, const Eigen::MatrixXd& Xp,
52 const Eigen::MatrixXd& Xn, Index bs,
53 const std::vector<std::pair<Index, Index>>& tiles) {
54 const Index n = S.cols();
55#pragma omp parallel for schedule(dynamic)
56 for (std::size_t t = 0; t < tiles.size(); ++t) {
57 const Index i0 = tiles[t].first * bs;
58 const Index j0 = tiles[t].second * bs;
59 const Index ni = std::min(bs, n - i0);
60 const Index nj = std::min(bs, n - j0);
61 auto block = S.block(i0, j0, ni, nj);
62 if (Xp.rows() > 0) {
63 block.noalias() +=
64 Xp.middleCols(i0, ni).transpose() * Xp.middleCols(j0, nj);
65 }
66 if (Xn.rows() > 0) {
67 block.noalias() -=
68 Xn.middleCols(i0, ni).transpose() * Xn.middleCols(j0, nj);
69 }
70 }
71}
72
73} // namespace
74
75Eigen::MatrixXd WeightedGram::Compute(Index nblocks, Index ncols,
76 const WeightsFn& weights,
77 const RowsFn& rows) {
78 Eigen::MatrixXd S = Eigen::MatrixXd::Zero(ncols, ncols);
79 if (nblocks == 0 || ncols == 0) {
80 return S;
81 }
82 // Tiles small enough to give every thread several tasks, large enough
83 // for efficient GEMMs.
84 const Index nthreads = OPENMP::getMaxThreads();
85 Index bs = 256;
86 while (bs > 64) {
87 const Index nt = (ncols + bs - 1) / bs;
88 if (nt * (nt + 1) / 2 >= 4 * nthreads) {
89 break;
90 }
91 bs /= 2;
92 }
93 const auto tiles = LowerTiles(ncols, bs);
94
95 // Called from inside a parallel region (e.g. per CDA residue), every
96 // thread builds its own matrix: keep the stacked rows small then.
97 const double budget = OPENMP::InsideActiveParallelRegion()
98 ? std::min(batch_bytes_, 6.4e7)
100 const Index max_rows =
101 std::max<Index>(1, static_cast<Index>(budget / (8.0 * double(ncols))));
102
103 std::vector<Eigen::VectorXd> w(static_cast<std::size_t>(nblocks));
104 Index first = 0;
105 while (first < nblocks) {
106 // collect blocks until the stacked rows reach the budget
107 Index last = first;
108 Index nrows = 0;
109 do {
110 weights(last, w[std::size_t(last)]);
111 nrows += w[std::size_t(last)].size();
112 ++last;
113 } while (last < nblocks && nrows < max_rows);
114
115 // rows with positive and with negative weight, per block
116 std::vector<Index> pos_offset(std::size_t(last - first) + 1, 0);
117 std::vector<Index> neg_offset(std::size_t(last - first) + 1, 0);
118 for (Index m = first; m < last; ++m) {
119 const Eigen::VectorXd& wm = w[std::size_t(m)];
120 const Index npos = (wm.array() > 0.0).count();
121 const Index nneg = (wm.array() < 0.0).count();
122 const std::size_t k = std::size_t(m - first);
123 pos_offset[k + 1] = pos_offset[k] + npos;
124 neg_offset[k + 1] = neg_offset[k] + nneg;
125 }
126 Eigen::MatrixXd Xp(pos_offset.back(), ncols);
127 Eigen::MatrixXd Xn(neg_offset.back(), ncols);
128
129#pragma omp parallel for schedule(dynamic)
130 for (Index m = first; m < last; ++m) {
131 const std::size_t k = std::size_t(m - first);
132 const Eigen::VectorXd& wm = w[std::size_t(m)];
133 const Index npos = pos_offset[k + 1] - pos_offset[k];
134 const Index nneg = neg_offset[k + 1] - neg_offset[k];
135 auto Xp_m = Xp.middleRows(pos_offset[k], npos);
136 auto Xn_m = Xn.middleRows(neg_offset[k], nneg);
137 if (npos == wm.size()) {
138 // all weights positive (epsilon at zero or imaginary frequency)
139 const Eigen::VectorXd scale = wm.cwiseSqrt();
140 rows(m, [&](const Eigen::Ref<const Eigen::MatrixXd>& R) {
141 Xp_m.noalias() = scale.asDiagonal() * R;
142 });
143 continue;
144 }
145 std::vector<Index> pos_rows;
146 std::vector<Index> neg_rows;
147 pos_rows.reserve(std::size_t(npos));
148 neg_rows.reserve(std::size_t(nneg));
149 for (Index r = 0; r < wm.size(); ++r) {
150 if (wm(r) > 0.0) {
151 pos_rows.push_back(r);
152 } else if (wm(r) < 0.0) {
153 neg_rows.push_back(r);
154 }
155 }
156 Eigen::VectorXd pos_scale(npos);
157 Eigen::VectorXd neg_scale(nneg);
158 for (Index i = 0; i < npos; ++i) {
159 pos_scale(i) = std::sqrt(wm(pos_rows[std::size_t(i)]));
160 }
161 for (Index i = 0; i < nneg; ++i) {
162 neg_scale(i) = std::sqrt(-wm(neg_rows[std::size_t(i)]));
163 }
164 rows(m, [&](const Eigen::Ref<const Eigen::MatrixXd>& R) {
165 for (Index c = 0; c < ncols; ++c) {
166 for (Index i = 0; i < npos; ++i) {
167 Xp_m(i, c) = pos_scale(i) * R(pos_rows[std::size_t(i)], c);
168 }
169 for (Index i = 0; i < nneg; ++i) {
170 Xn_m(i, c) = neg_scale(i) * R(neg_rows[std::size_t(i)], c);
171 }
172 }
173 });
174 }
175#if defined(EIGEN_USE_MKL_ALL)
177 // threaded MKL dsyrk on the lower triangle
178 if (Xp.rows() > 0) {
179 S.selfadjointView<Eigen::Lower>().rankUpdate(Xp.transpose(), 1.0);
180 }
181 if (Xn.rows() > 0) {
182 S.selfadjointView<Eigen::Lower>().rankUpdate(Xn.transpose(), -1.0);
183 }
184 first = last;
185 continue;
186 }
187#endif
188 AccumulateTiles(S, Xp, Xn, bs, tiles);
189 first = last;
190 }
191 S.triangularView<Eigen::StrictlyUpper>() = S.transpose();
192 return S;
193}
194
195SymmetricEigenSystem SymmetricEigen(const Eigen::MatrixXd& A, bool vectors) {
197 const char jobz = vectors ? 'V' : 'N';
198 // the routines overwrite their copy of A with the eigenvectors
199 auto finish = [&](Eigen::MatrixXd& work) {
200 if (vectors) {
201 result.vectors = std::move(work);
202 }
203 };
204 if (A.rows() > 0 && accelerate::LapackAvailable()) {
205 Eigen::MatrixXd work = A;
206 result.values.resize(A.rows());
207 if (accelerate::dsyevd(jobz, 'L', long(A.rows()), work.data(),
208 long(work.outerStride()),
209 result.values.data()) == 0) {
210 finish(work);
211 return result;
212 }
213 }
214#if defined(EIGEN_USE_MKL_ALL) || defined(EIGEN_USE_LAPACKE)
215 const lapack_int n = static_cast<lapack_int>(A.rows());
216 if (n > 0) {
217 Eigen::MatrixXd work = A;
218 result.values.resize(A.rows());
219 const lapack_int info = LAPACKE_dsyevd(
220 LAPACK_COL_MAJOR, jobz, 'L', n, work.data(),
221 static_cast<lapack_int>(work.outerStride()), result.values.data());
222 if (info == 0) {
223 finish(work);
224 return result;
225 }
226 }
227#endif
228 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(
229 A, vectors ? Eigen::ComputeEigenvectors : Eigen::EigenvaluesOnly);
230 result.values = es.eigenvalues();
231 if (vectors) {
232 result.vectors = es.eigenvectors();
233 }
234 return result;
235}
236
237CholeskyFactor::CholeskyFactor(const Eigen::MatrixXd& A) { compute(A); }
238
239void CholeskyFactor::compute(const Eigen::MatrixXd& A) {
240 ok_ = false;
241 L_ = A;
242 const Index n = A.rows();
243 if (n == 0) {
244 ok_ = true;
245 return;
246 }
248 ok_ = accelerate::dpotrf('L', long(n), L_.data(), long(L_.outerStride())) ==
249 0;
250 } else {
251#if defined(EIGEN_USE_MKL_ALL) || defined(EIGEN_USE_LAPACKE)
252 ok_ = LAPACKE_dpotrf(LAPACK_COL_MAJOR, 'L', static_cast<lapack_int>(n),
253 L_.data(),
254 static_cast<lapack_int>(L_.outerStride())) == 0;
255#else
256 Eigen::LLT<Eigen::MatrixXd> llt(A);
257 ok_ = llt.info() == Eigen::Success;
258 if (ok_) {
259 L_ = llt.matrixL();
260 }
261#endif
262 }
263 if (ok_) {
264 // LAPACK leaves the strict upper triangle as it was
265 L_.triangularView<Eigen::StrictlyUpper>().setZero();
266 }
267}
268
269Eigen::MatrixXd CholeskyFactor::SolveL(const Eigen::MatrixXd& B) const {
270 return L_.triangularView<Eigen::Lower>().solve(B);
271}
272
273Eigen::MatrixXd CholeskyFactor::Solve(const Eigen::MatrixXd& B) const {
274 return L_.transpose().triangularView<Eigen::Upper>().solve(SolveL(B));
275}
276
277Eigen::MatrixXd InverseSPD(const Eigen::MatrixXd& A) {
278 if (A.rows() > 0 && accelerate::LapackAvailable()) {
279 Eigen::MatrixXd inv = A;
280 const long n = long(A.rows());
281 const long lda = long(inv.outerStride());
282 if (accelerate::dpotrf('L', n, inv.data(), lda) == 0 &&
283 accelerate::dpotri('L', n, inv.data(), lda) == 0) {
284 inv.triangularView<Eigen::StrictlyUpper>() = inv.transpose();
285 return inv;
286 }
287 return A.partialPivLu().inverse();
288 }
289#if defined(EIGEN_USE_MKL_ALL) || defined(EIGEN_USE_LAPACKE)
290 const lapack_int n = static_cast<lapack_int>(A.rows());
291 Eigen::MatrixXd inv = A;
292 if (n == 0) {
293 return inv;
294 }
295 const lapack_int lda = static_cast<lapack_int>(inv.outerStride());
296 if (LAPACKE_dpotrf(LAPACK_COL_MAJOR, 'L', n, inv.data(), lda) == 0 &&
297 LAPACKE_dpotri(LAPACK_COL_MAJOR, 'L', n, inv.data(), lda) == 0) {
298 inv.triangularView<Eigen::StrictlyUpper>() = inv.transpose();
299 return inv;
300 }
301#else
302 Eigen::LLT<Eigen::MatrixXd> llt(A);
303 if (llt.info() == Eigen::Success) {
304 // the solve is symmetric only to rounding; make it exactly symmetric
305 // like the LAPACK paths (lower triangle mirrored)
306 Eigen::MatrixXd inv =
307 llt.solve(Eigen::MatrixXd::Identity(A.rows(), A.cols()));
308 inv.triangularView<Eigen::StrictlyUpper>() = inv.transpose();
309 return inv;
310 }
311#endif
312 return A.partialPivLu().inverse();
313}
314
315} // namespace xtp
316} // namespace votca
void compute(const Eigen::MatrixXd &A)
Eigen::MatrixXd Solve(const Eigen::MatrixXd &B) const
A^-1 B.
Eigen::MatrixXd SolveL(const Eigen::MatrixXd &B) const
L^-1 B.
std::function< void(Index m, const RowsVisitor &use)> RowsFn
std::function< void(Index m, Eigen::VectorXd &w)> WeightsFn
Writes the weights of block m; their number is the number of rows.
static Eigen::MatrixXd Compute(Index nblocks, Index ncols, const WeightsFn &weights, const RowsFn &rows)
Index getMaxThreads()
Definition eigen.h:145
bool InsideActiveParallelRegion()
Definition eigen.h:153
bool LapackAvailable()
Whether the routines below can run here (Accelerate build, macOS 13.3+).
int dpotrf(char uplo, long n, double *a, long lda)
Cholesky factorisation of a symmetric positive definite matrix.
int dpotri(char uplo, long n, double *a, long lda)
Inverse from the Cholesky factor of dpotrf (one triangle).
int dsyevd(char jobz, char uplo, long n, double *a, long lda, double *w)
Charge transport classes.
Definition ERIs.h:28
SymmetricEigenSystem SymmetricEigen(const Eigen::MatrixXd &A, bool vectors=true)
With vectors = false only the eigenvalues are computed.
Eigen::MatrixXd InverseSPD(const Eigen::MatrixXd &A)
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26