votca 2026-dev
Loading...
Searching...
No Matches
ewaldbackground.h
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#ifndef VOTCA_XTP_EWALDBACKGROUND_H
21#define VOTCA_XTP_EWALDBACKGROUND_H
22
23// Standard includes
24#include <algorithm>
25#include <chrono>
26#include <cmath>
27#include <fstream>
28#include <limits>
29#include <stdexcept>
30
31// Third party includes
32#include <Eigen/Eigenvalues>
33#include <Eigen/IterativeLinearSolvers>
34#include <boost/format.hpp>
35
36// Local VOTCA includes
49
71
72namespace votca {
73namespace xtp {
74
75class EwaldBackground final : public QMCalculator {
76 public:
77 std::string Identify() const { return "ewaldbackground"; }
78 bool WriteToStateFile() const { return false; }
79
80 protected:
81 void ParseOptions(const tools::Property& user_options);
82 bool Evaluate(Topology& top);
83
84 private:
85 std::string mapping_file_;
86 std::string checkpoint_file_;
87
88 double alpha_ = 0.5; // bohr^-1. XML value (coulombmethod.alpha) is
89 // in nm^-1. Unless set explicitly, derived from
90 // the box in Evaluate().
91 bool alpha_explicit_ = false;
92 double k_max_ = 3.0; // bohr^-1, XML value in nm^-1. Unless set
93 // explicitly, derived from alpha_ in Evaluate().
94 bool k_max_explicit_ = false;
95 double thole_a_ = 0.39;
96 double r_min_ = 18.897259886; // bohr (= 1.0 nm, the default applied
97 // in ParseOptions). XML value
98 // (realspace.r_min) is in nm.
99 double field_tol_ = 1e-8;
100 // Dimensionless: the real-space distance cutoff is
101 // screening_factor_/alpha (see the EwaldRealSpaceSum constructor).
102 double screening_factor_ = 6.0;
104
106 double pcg_tolerance_ = 1e-8;
107 bool induce_ = true; // legacy polarmethod.induce; if false, induced
108 // dipoles stay zero and no solve is run.
109 // Debug/experimental, no legacy counterpart: use
110 // EwaldBlockJacobiPreconditioner instead of the diagonal one.
112 // Debug/experimental. Use weighted-Jacobi iteration (SolveWithJOR,
113 // equivalent to legacy PolarBackground's SOR) instead of PCG. JOR does
114 // not require a positive-definite operator, whereas CG fails if
115 // SolveWithIndefinitenessCheck finds p.A.p <= 0.
116 bool use_jor_ = false;
117 // JOR relaxation factor. Legacy PolarBackground default
118 // (_polar_wSOR_N = 0.35), which overrides the 0.25 default of
119 // APolarSite::Induce.
120 double jor_omega_ = 0.35;
121 // Debug/experimental. Unrelaxed first JOR step, replicating legacy's
122 // iteration history (see SolveWithJOR).
124};
125
127 mapping_file_ = opt.get("multipoles").as<std::string>();
129 "output.checkpoint", "ewaldbackground.hdf5");
130
131 alpha_explicit_ = opt.exists("coulombmethod.alpha");
132 if (alpha_explicit_) {
133 // Inverse length: value in bohr^-1 = value in nm^-1 * bohr2nm.
134 alpha_ = opt.get("coulombmethod.alpha").as<double>() * tools::conv::bohr2nm;
135 }
136 // Unless given explicitly, alpha_ and k_max_ are derived in Evaluate(),
137 // where the box is known; k_max_ is always a multiple of alpha_, since
138 // the reciprocal-space weight is exp(-k^2/4 alpha^2). (A fixed k_max
139 // unrelated to alpha produced orders of magnitude too many k-vectors.)
140 k_max_explicit_ = opt.exists("coulombmethod.k_max");
141 if (k_max_explicit_) {
142 // nm^-1 -> bohr^-1, as for alpha_.
143 k_max_ = opt.get("coulombmethod.k_max").as<double>() * tools::conv::bohr2nm;
144 }
145 std::string shape_str = opt.ifExistsReturnElseReturnDefault<std::string>(
146 "coulombmethod.shape", "cube");
147 if (shape_str == "cube" || shape_str == "sphere") {
149 } else if (shape_str == "slab") {
151 } else {
152 throw std::runtime_error(
153 "EwaldBackground: coulombmethod.shape must be 'cube', 'sphere', or "
154 "'slab'");
155 }
156
157 thole_a_ = opt.ifExistsReturnElseReturnDefault<double>("polarmethod.thole_a",
158 thole_a_);
159 // Length: bohr = nm * nm2bohr. Default 1.0 nm (~18.9 bohr).
160 double r_min_nm =
161 opt.ifExistsReturnElseReturnDefault<double>("realspace.r_min", 1.0);
162 r_min_ = r_min_nm * tools::conv::nm2bohr;
164 "realspace.field_tol", field_tol_);
166 "realspace.screening_factor", screening_factor_);
167 if (screening_factor_ <= 0.0) {
168 throw std::runtime_error(
169 "EwaldBackground: realspace.screening_factor must be positive");
170 }
171
172 max_iter_ = opt.ifExistsReturnElseReturnDefault<Index>("polarmethod.max_iter",
173 max_iter_);
175 "polarmethod.tolerance", pcg_tolerance_);
176 induce_ =
177 opt.ifExistsReturnElseReturnDefault<bool>("polarmethod.induce", induce_);
178 // Debug/experimental options, intentionally not in the XML schema
179 // (see the member declarations).
181 "polarmethod.use_block_jacobi_preconditioner",
183 use_jor_ = opt.ifExistsReturnElseReturnDefault<bool>("polarmethod.use_jor",
184 use_jor_);
186 "polarmethod.jor_omega", jor_omega_);
188 "polarmethod.debug_match_legacy_first_step", match_legacy_first_step_);
189}
190
192 Logger log;
194 log.setCommonPreface("\nEWD");
195 // Single (non-job-parallel) calculator: print log messages immediately
196 // instead of buffering them until the end, so progress is visible.
197 log.setMultithreading(true);
198
199 auto t_start = std::chrono::steady_clock::now();
200 auto elapsed_s = [&](std::chrono::steady_clock::time_point since) {
201 return std::chrono::duration<double>(std::chrono::steady_clock::now() -
202 since)
203 .count();
204 };
205
206 XTP_LOG(Log::info, log) << TimeStamp()
207 << " Starting Ewald background calculation"
208 << std::flush;
209
210 PolarMapper polmap(log);
212
213 EwaldRegistry registry;
214 std::vector<Index> ids;
215 // offsets[n]: scalar index where segment ids[n] starts (3 per site),
216 // the same layout as EwaldPeriodicDipoleOperator::offsets_.
217 std::vector<Index> offsets;
218 ids.reserve(top.Segments().size());
219 offsets.reserve(top.Segments().size() + 1);
220 offsets.push_back(0);
221 for (const Segment& seg : top.Segments()) {
222 PolarSegment mol = polmap.map(seg, SegId(seg.getId(), std::string("n")));
223 registry.Register(seg.getId(), EwaldChargeState::Neutral, mol);
224 ids.push_back(seg.getId());
225 offsets.push_back(offsets.back() + 3 * mol.size());
226 }
227 const Index total_size = offsets.back();
228
229 XTP_LOG(Log::info, log) << TimeStamp() << " Mapped " << ids.size()
230 << " segments, " << (total_size / 3)
231 << " polarizable sites total (" << elapsed_s(t_start)
232 << "s)" << std::flush;
233
234 const Eigen::Matrix3d& box = top.getBox();
235 const double volume = box.col(0).dot(box.col(1).cross(box.col(2)));
236
237 // alpha and k_max are derived together by balancing the cost of the two
238 // sums; the result is independent of alpha (tested). Both cutoffs are
239 // fixed multiples of alpha, so the accuracy does not depend on it:
240 //
241 // real space EwaldRealSpaceSum truncates at screening_factor/alpha,
242 // leaving a tail of order erfc(screening_factor).
243 // reciprocal the Gaussian weight is exp(-k^2/4 alpha^2), so a cutoff
244 // at s_k*alpha leaves exp(-s_k^2/4).
245 //
246 // so with N sites in a cell of volume V the work at ONE target is
247 //
248 // N_real = (N/V)(4pi/3)(s_r/alpha)^3 falling as alpha^-3
249 // N_k = (4pi/3)(s_k alpha)^3 V/(8pi^3) rising as alpha^3
250 //
251 // and d/dalpha [A alpha^-3 + B alpha^3] = 0 gives alpha^6 = A/B, i.e.
252 //
253 // alpha = sqrt(2 pi) (N^(1/6) / V^(1/3)) sqrt(s_r/s_k)
254 //
255 // the standard Ewald result, whose N^(1/6) is what makes the method
256 // O(N^(3/2)) rather than O(N^2).
257 //
258 // The former defaults alpha = 3/L_min, k_max = 6*alpha fixed the
259 // reciprocal sum at ~98 k-vectors for any cubic cell while the
260 // real-space cutoff (2L) grew with the system (thiophene: 301,691 terms
261 // per target at 1000 segments, 1,508,063 at 5000). They also truncated
262 // the two halves at very different accuracies (erfc(6) = 2e-17 vs
263 // exp(-9) = 1.2e-4).
264 //
265 // screening_factor s_r is therefore the single accuracy parameter:
266 // eps = erfc(s_r) and s_k = 2*sqrt(-ln eps) give the same reciprocal
267 // tail (s_k = 12.39 for s_r = 6). With the balanced alpha this is 3x
268 // (216 methane) to 62x (400,000 sites) faster than the old defaults.
269 //
270 // Setting <alpha> or <k_max> explicitly still overrides either half.
272 const double s_r = screening_factor_;
273 // erfc underflows to 0 for s_r beyond ~27; clamp to keep s_k finite.
274 const double eps = std::max(std::erfc(s_r), 1e-300);
275 const double s_k = 2.0 * std::sqrt(-std::log(eps));
276
277 if (!alpha_explicit_) {
278 const double n_sites = double(total_size / 3);
279 alpha_ = std::sqrt(2.0 * tools::conv::Pi) * std::pow(n_sites, 1.0 / 6.0) /
280 std::cbrt(volume) * std::sqrt(s_r / s_k);
281
282 // Not clamped against r_min_: r_min_ only bounds the shell search
283 // by translation magnitude, while the cutoff culls by pair
284 // separation, so accuracy is erfc(s_r) either way. An r_min_
285 // beyond the cutoff only costs time; this is reported below.
286 }
287 if (!k_max_explicit_) {
288 k_max_ = s_k * alpha_;
289 }
290 }
291
292 EwaldRealSpaceSum real_sum(box, registry, alpha_, thole_a_, r_min_,
293 field_tol_, /*shell_width=*/0.945,
294 /*n_max=*/15, screening_factor_);
295 EwaldReciprocalSpaceSum recip_sum(box, registry, alpha_, k_max_);
296 EwaldShapeCorrection shape(volume, registry, shape_);
297
298 XTP_LOG(Log::info, log) << TimeStamp()
299 << " Real/reciprocal-space sums constructed ("
300 << elapsed_s(t_start) << "s)" << std::flush;
301 XTP_LOG(Log::info, log) << TimeStamp() << " Ewald split: alpha=" << alpha_
302 << " bohr^-1 (" << alpha_ * tools::conv::nm2bohr
303 << " nm^-1), k_max=" << k_max_ << " bohr^-1 ("
304 << k_max_ * tools::conv::nm2bohr << " nm^-1, "
305 << recip_sum.NumKVectors()
306 << " k-vectors), real-space cutoff "
308 << " bohr (screening_factor=" << screening_factor_
309 << ")" << std::flush;
310 // With the derived alpha the two halves should be of comparable size;
311 // a large imbalance indicates a hand-set alpha or an unsuitable cell.
312 {
313 const double n_sites = double(total_size / 3);
314 const double n_real_est = (n_sites / volume) *
315 (4.0 * tools::conv::Pi / 3.0) *
316 std::pow(screening_factor_ / alpha_, 3);
317 XTP_LOG(Log::info, log)
318 << TimeStamp()
319 << " Ewald balance (terms per target, estimated): " << "real "
320 << n_real_est << " vs reciprocal " << recip_sum.NumKVectors()
321 << std::flush;
322
323 // r_min_ beyond the cutoff forces the search through shells whose
324 // pairs are all culled: no effect on accuracy, only on cost.
325 const double cutoff = screening_factor_ / alpha_;
326 if (r_min_ > cutoff) {
327 XTP_LOG(Log::info, log)
328 << TimeStamp() << " NOTE: r_min (" << r_min_
329 << " bohr) lies beyond the real-space cutoff (" << cutoff
330 << " bohr). The shell search will walk shells that contribute "
331 "nothing before it is allowed to stop. Accuracy is unaffected "
332 "-- it is erfc(screening_factor) either way -- but lowering "
333 "r_min to at most the cutoff would save that work."
334 << std::flush;
335 }
336 }
337 XTP_LOG(Log::info, log) << TimeStamp() << " Cell: volume=" << volume
338 << " bohr^3, shape="
339 << (shape_ == EwaldShape::Cube ? "cube" : "slab")
340 << ", r_min=" << r_min_ << " bohr ("
342 << " nm), field_tol=" << field_tol_
343 << ", thole_a=" << thole_a_ << std::flush;
344 XTP_LOG(Log::info, log) << TimeStamp() << " Induction: induce="
345 << (induce_ ? "true" : "false")
346 << ", max_iter=" << max_iter_
347 << ", tolerance=" << pcg_tolerance_
348 << ", solver=" << (use_jor_ ? "JOR" : "PCG")
349 << (use_jor_ ? ""
351 ? " (block-Jacobi)"
352 : " (diagonal)"))
353 << std::flush;
354 if (use_jor_) {
355 XTP_LOG(Log::info, log)
356 << TimeStamp() << " JOR omega=" << jor_omega_ << std::flush;
357 }
359 XTP_LOG(Log::info, log)
360 << TimeStamp() << " debug_match_legacy_first_step is ON" << std::flush;
361 }
362
363 // Permanent field (all induced dipoles zero), computed once; it is the
364 // right-hand side b = +V of the solve (opposite sign to
365 // CalcInducedDipolesViaPCG, see EwaldPeriodicDipoleOperator).
366 auto t_field = std::chrono::steady_clock::now();
367 std::vector<std::pair<Index, PolarSite*>> targets;
368 targets.reserve(std::size_t(total_size / 3));
369 for (std::size_t n = 0; n < ids.size(); ++n) {
370 PolarSegment& segment = registry.Get(ids[n], EwaldChargeState::Neutral);
371 for (Index s = 0; s < segment.size(); ++s) {
372 PolarSite& site = segment[s];
373 site.setInduced_Dipole(Eigen::Vector3d::Zero());
374 site.Reset();
375 targets.push_back({ids[n], &site});
376 }
377 }
378 // Builds every neighbour list in parallel and applies it; the operator
379 // constructed below reuses these lists.
381 XTP_LOG(Log::info, log) << TimeStamp() << " Real-space permanent field done ("
382 << elapsed_s(t_field) << "s)" << std::flush;
383
384 auto t_recip = std::chrono::steady_clock::now();
385 // The reciprocal-space sum excludes nothing, so it needs no segment ids.
386 std::vector<PolarSite*> recip_targets;
387 recip_targets.reserve(targets.size());
388 for (const auto& entry : targets) {
389 recip_targets.push_back(entry.second);
390 }
391 recip_sum.AddFieldAtMany<Estatic::V>(
392 recip_targets, EwaldChargeState::Neutral,
393 [&](std::size_t done, std::size_t total) {
394 // Debug level: the k-vector count is already logged above.
395 XTP_LOG(Log::debug, log)
396 << TimeStamp() << " k-space progress: " << done << "/" << total
397 << " k-vectors (" << elapsed_s(t_recip) << "s)" << std::flush;
398 });
399 XTP_LOG(Log::info, log) << TimeStamp()
400 << " Reciprocal-space permanent field done ("
401 << elapsed_s(t_recip) << "s)" << std::flush;
402
403 // The shape field -(4 pi / 3V) M (cube) is the same at every site, but
404 // EwaldShapeCorrection::AddFieldAt sums M over the registry on every
405 // call (O(N^2) per site, ~110 s for 70k sites). Evaluate once on a
406 // probe and add, as EwaldPeriodicDipoleOperator does.
407 auto t_shape = std::chrono::steady_clock::now();
408 {
409 PolarSite shape_probe(-1, "X", Eigen::Vector3d::Zero());
411 const Eigen::Vector3d shape_field = shape_probe.V();
412 for (const auto& entry : targets) {
413 entry.second->V() += shape_field;
414 }
415 }
416 XTP_LOG(Log::info, log) << TimeStamp() << " Shape field done ("
417 << elapsed_s(t_shape) << "s)" << std::flush;
418 // Total time since the start of the real-space pass.
419 XTP_LOG(Log::info, log) << TimeStamp() << " Permanent field total ("
420 << elapsed_s(t_field) << "s)" << std::flush;
421 auto t_intra = std::chrono::steady_clock::now();
422
423 // Intramolecular static-static compensation: the reciprocal-space sum
424 // excludes nothing, so it contains the erf(alpha*r)/r part of every
425 // same-segment permanent pair. As legacy EwdInteractor::FP12_ERF_At_By
426 // does, this is subtracted so that the net intramolecular permanent
427 // interaction is zero (see
428 // EwaldRealSpaceInteractor::ApplyErfStaticFieldCorrection). All ordered
429 // pairs are visited, as in legacy's double loop.
430 //
431 // This includes i == j: the erf self-field of a permanent dipole is
432 // -(4/3)alpha^3/sqrt(pi) * mu (zero for a charge) and would otherwise
433 // bias the induced dipoles by an alpha^3-dependent amount. It is the
434 // permanent-field counterpart of SelfFieldMatrix() in
435 // EwaldPeriodicDipoleOperator; the r < kCoincidenceTol branch of
436 // ApplyErfStaticFieldCorrection handles this limit.
438 for (Index n = 0; n < Index(ids.size()); ++n) {
439 PolarSegment& segment = registry.Get(ids[n], EwaldChargeState::Neutral);
440 Index n_sites = segment.size();
441 for (Index i = 0; i < n_sites; ++i) {
442 for (Index j = 0; j < n_sites; ++j) {
444 segment[j], segment[i]);
445 }
446 }
447 }
448 XTP_LOG(Log::info, log) << TimeStamp()
449 << " Intramolecular static compensation applied ("
450 << elapsed_s(t_intra) << "s)" << std::flush;
451
452 Eigen::VectorXd b(total_size);
453 {
454 std::size_t t = 0;
455 for (std::size_t n = 0; n < ids.size(); ++n) {
456 const PolarSegment& segment =
457 registry.Get(ids[n], EwaldChargeState::Neutral);
458 Index base = offsets[n];
459 for (Index s = 0; s < segment.size(); ++s) {
460 b.segment<3>(base + 3 * s) = targets[t].second->V();
461 targets[t].second->Reset();
462 ++t;
463 }
464 }
465 }
466
467 if (induce_) {
468 XTP_LOG(Log::info, log)
469 << TimeStamp() << " Starting " << (use_jor_ ? "JOR" : "PCG")
470 << " solve (max " << max_iter_ << " iterations, tolerance "
471 << pcg_tolerance_ << ")" << std::flush;
472 auto t_pcg = std::chrono::steady_clock::now();
473
474 EwaldPeriodicDipoleOperator op(registry, real_sum, recip_sum, shape, ids,
476 Eigen::VectorXd x;
477 Index iterations;
478 double residual;
479 bool converged;
480 Index indefinite_at_iteration = -1;
481 double indefinite_curvature = 0.0;
482 double lanczos_min_eigenvalue = std::numeric_limits<double>::quiet_NaN();
483
484 // JOR uses EwaldSitePolarizabilityBlocks as D^-1 (as legacy does) and
485 // has no preconditioner choice.
486 if (use_jor_) {
487 EwaldSitePolarizabilityBlocks site_p(registry, ids);
488 auto result = SolveWithJOR(op, site_p, b, max_iter_, jor_omega_, log,
490 x = result.x;
491 iterations = result.iterations;
492 residual = result.residual;
493 converged = result.converged;
494
495 XTP_LOG(Log::info, log)
496 << TimeStamp() << " JOR finished after " << iterations
497 << " iterations, max_dU=" << result.max_dU
498 << " avg_dU=" << result.avg_dU << " (residual=" << residual
499 << ", informational) (" << elapsed_s(t_pcg) << "s)" << std::flush;
500 if (!converged) {
501 throw std::runtime_error(
502 "EwaldBackground: JOR did not converge within max_iter");
503 }
504 } else {
505
506 // The preconditioner type is a template parameter, hence two
507 // branches. EwaldBlockJacobiPreconditioner is fully built by its
508 // constructor, whereas DiagonalPreconditioner requires compute(op)
509 // before use.
511 EwaldBlockJacobiPreconditioner precond(registry, ids, alpha_, thole_a_);
512 auto result = SolveWithIndefinitenessCheck(op, precond, b, max_iter_,
513 pcg_tolerance_, log, t_pcg);
514 x = result.x;
515 iterations = result.iterations;
516 residual = result.residual;
517 converged = result.converged;
518 indefinite_at_iteration = result.indefinite_at_iteration;
519 indefinite_curvature = result.indefinite_curvature;
520 lanczos_min_eigenvalue = result.lanczos_min_eigenvalue;
521 } else {
522 Eigen::DiagonalPreconditioner<double> precond;
523 precond.compute(op);
524 auto result = SolveWithIndefinitenessCheck(op, precond, b, max_iter_,
525 pcg_tolerance_, log, t_pcg);
526 x = result.x;
527 iterations = result.iterations;
528 residual = result.residual;
529 converged = result.converged;
530 indefinite_at_iteration = result.indefinite_at_iteration;
531 indefinite_curvature = result.indefinite_curvature;
532 lanczos_min_eigenvalue = result.lanczos_min_eigenvalue;
533 }
534
535 XTP_LOG(Log::info, log)
536 << TimeStamp() << " PCG finished after " << iterations
537 << " iterations, residual " << residual << " (" << elapsed_s(t_pcg)
538 << "s)" << std::flush;
539 {
540 // Matvec phase breakdown (RawMultiplyTimings). n_calls includes
541 // the baseline_ call in the operator constructor.
542 const auto& tm = op.Timings();
543 const double n = double(std::max<Index>(tm.n_calls, 1));
544 XTP_LOG(Log::info, log)
545 << TimeStamp() << " RawMultiply phase breakdown over " << tm.n_calls
546 << " calls (total " << tm.total() << "s):" << std::flush;
547 auto line = [&](const char* nm, double t) {
548 XTP_LOG(Log::info, log)
549 << TimeStamp()
550 << (boost::format(" %1$-12s %2$8.3fs total "
551 "%3$7.3fs/call %4$5.1f%%") %
552 nm % t % (t / n) %
553 (tm.total() > 0 ? 100.0 * t / tm.total() : 0.0))
554 .str()
555 << std::flush;
556 };
557 line("setup", tm.setup);
558 line("real_space", tm.real_space);
559 line("reciprocal", tm.reciprocal);
560 line("shape", tm.shape);
561 line("assemble", tm.assemble);
562 line("intra", tm.intra);
563
564 // Neighbour-list size, which determines the real_space cost
565 // (EwaldRealSpaceSum::NeighborStats).
566 const auto ns = real_sum.GetNeighborStats();
567 XTP_LOG(Log::info, log)
568 << TimeStamp()
569 << (boost::format(
570 " neighbours: %1$.1f entries/target over %2$d targets "
571 "(%3$d kept, %4$d culled beyond %5$.1f bohr = %6$.1f%%)") %
572 ns.entries_per_target() % ns.targets % ns.entries % ns.culled %
573 real_sum.RealSpaceCutoff() % (100.0 * ns.culled_fraction()))
574 .str()
575 << std::flush;
576 }
577 if (!std::isnan(lanczos_min_eigenvalue)) {
578 XTP_LOG(Log::info, log) << TimeStamp() << " Lanczos min eigenvalue: "
579 << lanczos_min_eigenvalue << std::flush;
580 if (lanczos_min_eigenvalue < 0.0) {
581 XTP_LOG(Log::info, log) << TimeStamp()
582 << " NEGATIVE -- the operator is likely not "
583 "positive-definite."
584 << std::flush;
585 }
586 }
587
588 if (indefinite_at_iteration >= 0) {
589 throw std::runtime_error(
590 "EwaldBackground: PCG operator is NOT positive-definite "
591 "(found at iteration " +
592 std::to_string(indefinite_at_iteration) +
593 ", p.A.p = " + std::to_string(indefinite_curvature) + " <= 0).");
594 }
595
596 if (!converged) {
597 throw std::runtime_error(
598 "EwaldBackground: PCG did not converge (max_iter reached, "
599 "operator was never found indefinite along the way)");
600 }
601 }
602
603 for (std::size_t n = 0; n < ids.size(); ++n) {
604 PolarSegment& segment = registry.Get(ids[n], EwaldChargeState::Neutral);
605 Index base = offsets[n];
606 for (Index s = 0; s < segment.size(); ++s) {
607 segment[s].setInduced_Dipole(x.segment<3>(base + 3 * s));
608 }
609 }
610 } else {
611 // As legacy polarmethod.induce = 0: induced dipoles stay zero (set
612 // before the permanent-field computation).
613 XTP_LOG(Log::info, log)
614 << TimeStamp()
615 << " polarmethod.induce is false: skipping the PCG solve, "
616 "induced dipoles left at zero"
617 << std::flush;
618 }
619
620 auto t_cpt = std::chrono::steady_clock::now();
621 // Store the permanent field b in V() before writing the checkpoint, so
622 // that it can be read back (e.g. by hdf5_dump). Otherwise V() holds the
623 // last trial field of the solve, or zero if induce_ is false.
624 {
625 for (std::size_t n = 0; n < ids.size(); ++n) {
626 PolarSegment& segment = registry.Get(ids[n], EwaldChargeState::Neutral);
627 Index base = offsets[n];
628 for (Index s = 0; s < segment.size(); ++s) {
629 segment[s].V() = b.segment<3>(base + 3 * s);
630 }
631 }
632 }
633
635 CheckpointWriter w = cpf.getWriter();
636 registry.WriteToCpt(w);
637
638 // Store the convergence parameters with the converged state: a job
639 // embedding a foreground in this background must use the same alpha,
640 // k_max and shape, since alpha determines how the interaction is split
641 // between the real- and reciprocal-space sums. See EwaldParameters.
642 {
643 EwaldParameters params;
644 params.alpha = alpha_;
645 params.k_max = k_max_;
646 params.r_min = r_min_;
647 params.field_tol = field_tol_;
648 params.thole_a = thole_a_;
650 params.shape = shape_;
651 params.box = box;
652 CheckpointWriter wp = w.openChild("ewald_parameters");
653 params.WriteToCpt(wp);
654 }
655
656 XTP_LOG(Log::info, log) << TimeStamp() << " Checkpoint written to "
657 << checkpoint_file_ << " (" << elapsed_s(t_cpt)
658 << "s)" << std::flush;
659 XTP_LOG(Log::info, log) << TimeStamp()
660 << " Ewald background calculation done, total "
661 << elapsed_s(t_start) << "s" << std::flush;
662
663 return true;
664}
665
666} // namespace xtp
667} // namespace votca
668
669#endif // VOTCA_XTP_EWALDBACKGROUND_H
class to manage program options with xml serialization functionality
Definition property.h:55
Property & get(const std::string &key)
get existing property
Definition property.cc:79
bool exists(const std::string &key) const
check whether property exists
Definition property.cc:122
T as() const
return value as type
Definition property.h:283
T ifExistsReturnElseReturnDefault(const std::string &key, T defaultvalue) const
Definition property.h:332
CheckpointWriter getWriter()
CheckpointWriter openChild(const std::string &childName) const
void ParseOptions(const tools::Property &user_options)
bool Evaluate(Topology &top)
std::string Identify() const
Calculator name.
Block-Jacobi preconditioner for the PCG solve of EwaldPeriodicDipoleOperator.
void ApplyErfStaticFieldCorrection(const T &site1, PolarSite &site2, const Eigen::Vector3d &source_shift=Eigen::Vector3d::Zero()) const
NeighborStats GetNeighborStats() const
void AddFieldAtMany(const std::vector< std::pair< Index, PolarSite * > > &targets, EwaldChargeState source_state, bool include_static=true) const
void AddFieldAtMany(const std::vector< PolarSite * > &targets, EwaldChargeState source_state, const ProgressCallback &progress=ProgressCallback()) const
void WriteToCpt(CheckpointWriter &w) const
const PolarSegment & Get(Index id, EwaldChargeState state) const
void Register(Index id, EwaldChargeState state, PolarSegment segment)
void AddFieldAt(PolarSite &target, EwaldChargeState source_state) const
Logger is used for thread-safe output of messages.
Definition logger.h:164
void setReportLevel(Log::Level ReportLevel)
Definition logger.h:185
void setMultithreading(bool maverick)
Definition logger.h:186
void setCommonPreface(const std::string &preface)
Definition logger.h:198
Class to represent Atom/Site in electrostatic+polarization.
Definition polarsite.h:36
void setInduced_Dipole(const Eigen::Vector3d &induced_dipole)
Definition polarsite.h:122
const Eigen::Vector3d & V() const
Definition polarsite.h:68
void LoadMappingFile(const std::string &mapfile)
AtomContainer map(const Segment &seg, const SegId &segid) const
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
Container for segments and box and atoms.
Definition topology.h:41
std::vector< Segment > & Segments()
Definition topology.h:58
const Eigen::Matrix3d & getBox() const
Definition topology.h:64
#define XTP_LOG(level, log)
Definition logger.h:40
const double bohr2nm
Definition constants.h:46
const double Pi
Definition constants.h:36
const double nm2bohr
Definition constants.h:47
Charge transport classes.
Definition ERIs.h:28
JorResult SolveWithJOR(const EwaldPeriodicDipoleOperator &op, const EwaldSitePolarizabilityBlocks &site_p, const Eigen::VectorXd &b, Index max_iter, double omega, Logger &log, std::chrono::steady_clock::time_point t_start, bool match_legacy_first_step)
ClassicalSegment< PolarSite > PolarSegment
SegmentMapper< PolarSegment > PolarMapper
PcgIndefinitenessResult SolveWithIndefinitenessCheck(const EwaldPeriodicDipoleOperator &op, Preconditioner &precond, const Eigen::VectorXd &b, Index max_iter, double tol, Logger &log, std::chrono::steady_clock::time_point t_start)
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
The Ewald convergence parameters a background was converged with.
void WriteToCpt(CheckpointWriter &w) const