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 for (std::size_t n = 0; n < ids_.size(); ++n) {
163 const Index base = offsets_[n];
164 const Index width = offsets_[n + 1] - base;
165 // LDLT::solve works identically regardless of block size -- no
166 // special case needed for single-site (width==3) segments; their
167 // own factorizations_[n] already holds a plain 3x3 LDLT of
168 // getPInv() alone (see FactorizeBlocks), the same result
169 // DiagonalPreconditioner itself would give them.
170 x.segment(base, width) = factorizations_[n].solve(b.segment(base, width));
171 }
172 }
173
174 template <typename Rhs>
175 inline const Eigen::Solve<EwaldBlockJacobiPreconditioner, Rhs> solve(
176 const Eigen::MatrixBase<Rhs>& b) const {
177 eigen_assert(is_initialized_ &&
178 "EwaldBlockJacobiPreconditioner is not initialized.");
179 eigen_assert(size_ == b.rows() &&
180 "EwaldBlockJacobiPreconditioner::solve(): invalid number "
181 "of rows of the right hand side matrix b");
182 return Eigen::Solve<EwaldBlockJacobiPreconditioner, Rhs>(*this,
183 b.derived());
184 }
185
186 Eigen::ComputationInfo info() const { return Eigen::Success; }
187
188 private:
190 offsets_.reserve(ids_.size() + 1);
191 offsets_.push_back(0);
192 for (Index id : ids_) {
193 if (!registry_->Has(id, EwaldChargeState::Neutral)) {
194 throw std::runtime_error(
195 "EwaldBlockJacobiPreconditioner: segment id not registered at "
196 "EwaldChargeState::Neutral");
197 }
198 Index n_sites = registry_->Get(id, EwaldChargeState::Neutral).size();
199 offsets_.push_back(offsets_.back() + 3 * n_sites);
200 }
201 size_ = offsets_.back();
202 }
203
204 // Builds and factorizes every segment's own local block, matching
205 // EwaldPeriodicDipoleOperator's own intramolecular term: the same
206 // B-function/ComputeThole calls, and the same sign it enters the
207 // OPERATOR with -- i.e. subtracted, since RawMultiply forms
208 // A = P^-1 - C. Note this is the opposite sign to
209 // AddIntraSegmentCoupling's own internal accumulation, which builds
210 // the coupling field itself (a positive quantity) that RawMultiply
211 // then subtracts. An earlier version of this comment claimed to
212 // mirror that method's sign convention "exactly", and the code did --
213 // which is precisely why this was wrong, and why it survived the
214 // operator's own sign fix without being noticed. Compare against the
215 // operator's assembled block, not against AddIntraSegmentCoupling in
216 // isolation.
218 factorizations_.reserve(ids_.size());
219 for (std::size_t n = 0; n < ids_.size(); ++n) {
220 const PolarSegment& segment =
222 const Index n_sites = segment.size();
223 const Index width = 3 * n_sites;
224 Eigen::MatrixXd block = Eigen::MatrixXd::Zero(width, width);
225
226 for (Index s = 0; s < n_sites; ++s) {
227 block.block<3, 3>(3 * s, 3 * s) = segment[s].getPInv();
228 }
229
230 if (n_sites >= 2) {
231 for (Index i = 0; i < n_sites; ++i) {
232 const PolarSite& site_i = segment[i];
233 for (Index j = i + 1; j < n_sites; ++j) {
234 const PolarSite& site_j = segment[j];
235 const Eigen::Vector3d r_vec = site_i.getPos() - site_j.getPos();
236 const double r = r_vec.norm();
238 intra_interactor_->ComputeB(r);
240 intra_interactor_->ComputeThole(r, site_j, site_i);
241 const Eigen::Matrix3d coupling =
242 t.l5 * b.B2 * (r_vec * r_vec.transpose()) -
243 t.l3 * b.B1 * Eigen::Matrix3d::Identity();
244 // BUG FIX (this session): MINUS, not plus. The operator
245 // being preconditioned is A = P^-1 - C: RawMultiply builds
246 // the intramolecular term with exactly the `coupling`
247 // expression above and then subtracts it (result -= intra).
248 // This class assembled it with a plus, so it was
249 // factorizing P^-1 + C_intra -- the operator as it stood
250 // BEFORE the coupling-sign fix, which this file was written
251 // against and which never propagated here.
252 //
253 // The consequence was not a wrong answer (a preconditioner
254 // cannot change the fixed point, only the path to it) but a
255 // markedly worse one: on a real 5000-site solve this took
256 // PCG from 16 iterations to 29, with a visibly
257 // non-monotonic p.A.p curvature trace, versus a smoothly
258 // decreasing one unpreconditioned.
259 block.block<3, 3>(3 * i, 3 * j) -= coupling;
260 block.block<3, 3>(3 * j, 3 * i) -= coupling.transpose();
261 }
262 }
263 }
264
265 Eigen::LDLT<Eigen::MatrixXd> ldlt(block);
266 factorizations_.push_back(std::move(ldlt));
267 }
268 }
269
271 std::vector<Index> ids_;
272 std::optional<EwaldRealSpaceInteractor> intra_interactor_;
273 std::vector<Index> offsets_;
275 std::vector<Eigen::LDLT<Eigen::MatrixXd>> factorizations_;
277};
278
279} // namespace xtp
280} // namespace votca
281
282#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