votca 2026-dev
Loading...
Searching...
No Matches
ewaldblockjacobipreconditioner.h
Go to the documentation of this file.
1#ifndef VOTCA_XTP_EWALDBLOCKJACOBIPRECONDITIONER_H
2#define VOTCA_XTP_EWALDBLOCKJACOBIPRECONDITIONER_H
3
4// Standard includes
5#include <optional>
6#include <stdexcept>
7
8// Third party includes
9#include <Eigen/Cholesky>
10#include <Eigen/IterativeLinearSolvers>
11
12// Local VOTCA includes
14#include "ewaldregistry.h"
15
16namespace votca {
17namespace xtp {
18
111 using Scalar = double;
112 using Vector = Eigen::VectorXd;
113
114 public:
116 enum {
117 ColsAtCompileTime = Eigen::Dynamic,
118 MaxColsAtCompileTime = Eigen::Dynamic
119 };
120
122
123 // registry/ids/alpha_ewald/thole_a mirror EwaldPeriodicDipoleOperator's
124 // own constructor exactly -- same segments, same layout, same
125 // intramolecular interactor parameters -- so a caller already holding
126 // those values for the operator can pass the identical ones here.
128 std::vector<Index> ids, double alpha_ewald,
129 double thole_a)
130 : registry_(&registry), ids_(std::move(ids)) {
131 intra_interactor_.emplace(alpha_ewald, thole_a);
132 BuildOffsets();
134 is_initialized_ = true;
135 }
136
137 Index rows() const { return size_; }
138 Index cols() const { return size_; }
139
140 // Matches DiagonalPreconditioner's own analyzePattern/factorize/compute
141 // split: this class's own local blocks depend only on registry_/ids_/
142 // intra_interactor_ (fixed at construction, never on the operator
143 // matrix passed in here), so all of these are no-ops that just
144 // return *this -- the real work already happened in the constructor.
145 // Present only because Eigen::ConjugateGradient's own compute() path
146 // requires them to exist on whatever preconditioner type it's given.
147 template <typename MatType>
149 return *this;
150 }
151 template <typename MatType>
153 return *this;
154 }
155 template <typename MatType>
157 return *this;
158 }
159
160 template <typename Rhs, typename Dest>
161 void _solve_impl(const Rhs& b, Dest& x) const {
162 // Segments own disjoint slices of x, so they are independent.
163#pragma omp parallel for schedule(dynamic, 4)
164 for (Index nn = 0; nn < Index(ids_.size()); ++nn) {
165 const std::size_t n = std::size_t(nn);
166 const Index base = offsets_[n];
167 const Index width = offsets_[n + 1] - base;
168 // LDLT::solve works identically regardless of block size -- no
169 // special case needed for single-site (width==3) segments; their
170 // own factorizations_[n] already holds a plain 3x3 LDLT of
171 // getPInv() alone (see FactorizeBlocks), the same result
172 // DiagonalPreconditioner itself would give them.
173 x.segment(base, width) = factorizations_[n].solve(b.segment(base, width));
174 }
175 }
176
177 template <typename Rhs>
178 inline const Eigen::Solve<EwaldBlockJacobiPreconditioner, Rhs> solve(
179 const Eigen::MatrixBase<Rhs>& b) const {
180 eigen_assert(is_initialized_ &&
181 "EwaldBlockJacobiPreconditioner is not initialized.");
182 eigen_assert(size_ == b.rows() &&
183 "EwaldBlockJacobiPreconditioner::solve(): invalid number "
184 "of rows of the right hand side matrix b");
185 return Eigen::Solve<EwaldBlockJacobiPreconditioner, Rhs>(*this,
186 b.derived());
187 }
188
189 Eigen::ComputationInfo info() const { return Eigen::Success; }
190
191 private:
193 offsets_.reserve(ids_.size() + 1);
194 offsets_.push_back(0);
195 for (Index id : ids_) {
196 if (!registry_->Has(id, EwaldChargeState::Neutral)) {
197 throw std::runtime_error(
198 "EwaldBlockJacobiPreconditioner: segment id not registered at "
199 "EwaldChargeState::Neutral");
200 }
201 Index n_sites = registry_->Get(id, EwaldChargeState::Neutral).size();
202 offsets_.push_back(offsets_.back() + 3 * n_sites);
203 }
204 size_ = offsets_.back();
205 }
206
207 // Builds and factorizes every segment's own local block, matching
208 // EwaldPeriodicDipoleOperator's own intramolecular term: the same
209 // B-function/ComputeThole calls, and the same sign it enters the
210 // OPERATOR with -- i.e. subtracted, since RawMultiply forms
211 // A = P^-1 - C. Note this is the opposite sign to
212 // AddIntraSegmentCoupling's own internal accumulation, which builds
213 // the coupling field itself (a positive quantity) that RawMultiply
214 // then subtracts. An earlier version of this comment claimed to
215 // mirror that method's sign convention "exactly", and the code did --
216 // which is precisely why this was wrong, and why it survived the
217 // operator's own sign fix without being noticed. Compare against the
218 // operator's assembled block, not against AddIntraSegmentCoupling in
219 // isolation.
221 // One independent block per segment: built and factorized in
222 // parallel, each into its own slot.
223 factorizations_.resize(ids_.size());
224#pragma omp parallel for schedule(dynamic, 4)
225 for (Index nn = 0; nn < Index(ids_.size()); ++nn) {
226 const std::size_t n = std::size_t(nn);
227 const PolarSegment& segment =
229 const Index n_sites = segment.size();
230 const Index width = 3 * n_sites;
231 Eigen::MatrixXd block = Eigen::MatrixXd::Zero(width, width);
232
233 for (Index s = 0; s < n_sites; ++s) {
234 block.block<3, 3>(3 * s, 3 * s) = segment[s].getPInv();
235 }
236
237 if (n_sites >= 2) {
238 for (Index i = 0; i < n_sites; ++i) {
239 const PolarSite& site_i = segment[i];
240 for (Index j = i + 1; j < n_sites; ++j) {
241 const PolarSite& site_j = segment[j];
242 const Eigen::Vector3d r_vec = site_i.getPos() - site_j.getPos();
243 const double r = r_vec.norm();
245 intra_interactor_->ComputeB(r);
247 intra_interactor_->ComputeThole(r, site_j, site_i);
248 const Eigen::Matrix3d coupling =
249 t.l5 * b.B2 * (r_vec * r_vec.transpose()) -
250 t.l3 * b.B1 * Eigen::Matrix3d::Identity();
251 // BUG FIX (this session): MINUS, not plus. The operator
252 // being preconditioned is A = P^-1 - C: RawMultiply builds
253 // the intramolecular term with exactly the `coupling`
254 // expression above and then subtracts it (result -= intra).
255 // This class assembled it with a plus, so it was
256 // factorizing P^-1 + C_intra -- the operator as it stood
257 // BEFORE the coupling-sign fix, which this file was written
258 // against and which never propagated here.
259 //
260 // The consequence was not a wrong answer (a preconditioner
261 // cannot change the fixed point, only the path to it) but a
262 // markedly worse one: on a real 5000-site solve this took
263 // PCG from 16 iterations to 29, with a visibly
264 // non-monotonic p.A.p curvature trace, versus a smoothly
265 // decreasing one unpreconditioned.
266 block.block<3, 3>(3 * i, 3 * j) -= coupling;
267 block.block<3, 3>(3 * j, 3 * i) -= coupling.transpose();
268 }
269 }
270 }
271
272 factorizations_[n].compute(block);
273 }
274 }
275
277 std::vector<Index> ids_;
278 std::optional<EwaldRealSpaceInteractor> intra_interactor_;
279 std::vector<Index> offsets_;
281 std::vector<Eigen::LDLT<Eigen::MatrixXd>> factorizations_;
283};
284
285} // namespace xtp
286} // namespace votca
287
288#endif // VOTCA_XTP_EWALDBLOCKJACOBIPRECONDITIONER_H
const Eigen::Solve< EwaldBlockJacobiPreconditioner, Rhs > solve(const Eigen::MatrixBase< Rhs > &b) const
EwaldBlockJacobiPreconditioner & compute(const MatType &)
std::vector< Eigen::LDLT< Eigen::MatrixXd > > factorizations_
EwaldBlockJacobiPreconditioner(EwaldRegistry &registry, std::vector< Index > ids, double alpha_ewald, double thole_a)
std::optional< EwaldRealSpaceInteractor > intra_interactor_
EwaldBlockJacobiPreconditioner & factorize(const MatType &)
EwaldBlockJacobiPreconditioner & analyzePattern(const MatType &)
Class to represent Atom/Site in electrostatic+polarization.
Definition polarsite.h:36
const Eigen::Vector3d & getPos() const
Definition staticsite.h:80
STL namespace.
Charge transport classes.
Definition ERIs.h:28
ClassicalSegment< PolarSite > PolarSegment
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26