votca 2026-dev
Loading...
Searching...
No Matches
ewaldregion.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 <chrono>
22#include <iomanip>
23#include <sstream>
24#include <stdexcept>
25
26// VOTCA includes
28
29// Local VOTCA includes
34#include "votca/xtp/qmregion.h"
36
37namespace votca {
38namespace xtp {
39
40namespace {
41// Use before Initialize() is a caller error, not a missing feature: the
42// background has to be loaded before anything can be asked of it.
43[[noreturn]] void NotInitialized(const std::string& what) {
44 throw std::runtime_error(
45 "EwaldRegion::" + what +
46 " was called before Initialize(). The background must be loaded "
47 "from its checkpoint first.");
48}
49} // namespace
50
53 "checkpoint", "ewaldbackground.hdf5");
54
56 CheckpointReader r = cpf.getReader();
57 registry_.ReadFromCpt(r);
58 CheckpointReader rp = r.openChild("ewald_parameters");
59 params_.ReadFromCpt(rp);
60 loaded_ = true;
61
62 XTP_LOG(Log::info, log_) << TimeStamp() << " Ewald background read from "
63 << checkpoint_file_ << ": " << size()
64 << " segments, alpha=" << params_.alpha
65 << " bohr^-1, k_max=" << params_.k_max
66 << " bohr^-1, box volume="
67 << params_.box.determinant() << " bohr^3"
68 << std::flush;
69}
70
71void EwaldRegion::Evaluate(std::vector<std::unique_ptr<Region>>& regions) {
72 // Nothing polarizes this region, so all this does is record that the
73 // other regions had no effect on it -- the energies are all zero by
74 // construction (see the Interactwith* overrides). It is kept rather
75 // than skipped so the log still shows the region participating.
77}
78
80 // The whole periodic cell, not a share of the job's segments -- see
81 // this class's own documentation, point 3.
82 return Index(registry_.AllIds().size());
83}
84
85double EwaldRegion::charge() const {
86 if (!loaded_) {
87 NotInitialized("charge");
88 }
89 double q = 0.0;
90 for (Index id : registry_.AllIds()) {
92 continue;
93 }
94 for (const PolarSite& site : registry_.Get(id, EwaldChargeState::Neutral)) {
95 q += site.getCharge();
96 }
97 }
98 return q;
99}
100
101double EwaldRegion::Etotal() const {
102 // Zero, not a throw, and deliberately so. The background's
103 // contribution to the ENERGY is genuinely not implemented -- only its
104 // field is -- but throwing here aborts the job after the induction has
105 // already converged, destroying the very dipoles the run was for.
106 //
107 // The omission is announced instead: ApplyFieldTo logs a prominent
108 // warning on first use, and AppendResult records it in the job's own
109 // output so it survives into the result file rather than living only
110 // in a log nobody re-reads.
111 return 0.0;
112}
113
115 // Only the path is written, not the background itself. The background
116 // is large (thousands of segments) and already lives in its own
117 // checkpoint; copying it into every per-iteration job checkpoint would
118 // multiply that cost for data that cannot change -- this region is
119 // frozen by construction.
120 w(checkpoint_file_, "checkpoint_file");
121}
122
124 // Reloads from the background checkpoint named at write time -- see
125 // WriteToCpt for why the background is not stored inline. If that file
126 // has moved since, this fails loudly here rather than silently
127 // continuing with an empty background.
128 r(checkpoint_file_, "checkpoint_file");
129
131 CheckpointReader br = cpf.getReader();
132 registry_.ReadFromCpt(br);
133 CheckpointReader bp = br.openChild("ewald_parameters");
134 params_.ReadFromCpt(bp);
135 loaded_ = true;
136
137 // The lazily-built sums belong to a particular foreground geometry;
138 // after a reload there is no guarantee it is the same one, so they are
139 // dropped and rebuilt on next use.
140 real_sum_.reset();
141 recip_sum_.reset();
142 shape_.reset();
143 interactor_.reset();
144 foreground_copies_.clear();
145 built_foreground_.clear();
146}
147
149 // Deliberately a no-op rather than a throw: the background has no
150 // job-local geometry worth writing, and a PDB dump should not be able
151 // to abort a run.
152}
153
155 prop.add("E_ewald_background", "not_implemented");
156 prop.add("segments", std::to_string(size()));
157 prop.add("checkpoint", checkpoint_file_);
158}
159
160namespace {
161Eigen::Vector3d Centroid(const PolarSegment& seg) {
162 // Unweighted, matching EwaldRealSpaceSum's own convention (and
163 // legacy's PolarSeg::CalcPos). A mass-weighted centre would place the
164 // foreground copies fractionally off the positions the real-space sum
165 // suppresses, and the erf correction would then remove a copy that was
166 // never dropped.
167 Eigen::Vector3d pos = Eigen::Vector3d::Zero();
168 Index n = 0;
169 for (const PolarSite& site : seg) {
170 pos += site.getPos();
171 ++n;
172 }
173 return (n > 0) ? Eigen::Vector3d(pos / double(n)) : pos;
174}
175} // namespace
176
178 const std::vector<std::pair<Index, Eigen::Vector3d>>& foreground) {
179 // The union is disjoint by construction -- PartitionRegions marks each
180 // segment as it assigns it -- but that guarantee lives far from the
181 // code relying on it, and a repeated id would suppress the same
182 // background copy twice.
183 for (std::size_t i = 0; i < foreground.size(); ++i) {
184 for (std::size_t j = i + 1; j < foreground.size(); ++j) {
185 if (foreground[i].first == foreground[j].first) {
186 std::stringstream message;
187 message << "EwaldRegion::RegisterForeground: segment "
188 << foreground[i].first
189 << " was declared twice. The foreground is the disjoint "
190 "union of the regions that own segments.";
191 throw std::runtime_error(message.str());
192 }
193 }
194 }
195 registered_foreground_ = foreground;
196 // Anything built before the declaration was built from the wrong
197 // foreground.
198 real_sum_.reset();
199 recip_sum_.reset();
200 shape_.reset();
201 interactor_.reset();
202 foreground_copies_.clear();
203 built_foreground_.clear();
204}
205
207 const std::vector<PolarSegment>& foreground) const {
208 // The same 1e-4 bohr EwaldRealSpaceSum uses to recognise a copy
209 // (kForegroundMatchTol, private there, so restated rather than
210 // shared): it absorbs round-off, nothing larger. A foreground's
211 // positions do not move between calls within a job, so any difference
212 // above this is a different segment, not drift.
213 constexpr double kTol = 1e-4;
214 for (const PolarSegment& seg : foreground) {
215 const Eigen::Vector3d centroid = Centroid(seg);
216 bool found = false;
217 for (const auto& entry : built_foreground_) {
218 if (entry.first == seg.getId() &&
219 (centroid - entry.second).norm() <= kTol) {
220 found = true;
221 break;
222 }
223 }
224 if (!found) {
225 std::stringstream message;
226 message << "EwaldRegion: asked about segment " << seg.getId()
227 << ", which is not part of the foreground these sums were "
228 "built for. The suppression list and the real-space "
229 "neighbour cache belong to that foreground, so answering "
230 "would drop the wrong background copies and subtract erf "
231 "corrections for copies that were never dropped -- a wrong "
232 "energy with no symptom. JobTopology declares the whole "
233 "foreground with RegisterForeground before any region is "
234 "evaluated; if this fires, that declaration is missing or "
235 "incomplete.";
236 throw std::runtime_error(message.str());
237 }
238 }
239}
240
241void EwaldRegion::BuildSums(const std::vector<PolarSegment>& fallback) const {
242 std::vector<std::pair<Index, Eigen::Vector3d>> source =
244 if (source.empty()) {
245 for (const PolarSegment& seg : fallback) {
246 source.push_back({seg.getId(), Centroid(seg)});
247 }
248 }
249
250 foreground_copies_.clear();
251 built_foreground_ = source;
252 for (const auto& entry : source) {
253 const Index seg_id = entry.first;
254 if (!registry_.Has(seg_id, EwaldChargeState::Neutral)) {
255 throw std::runtime_error(
256 "EwaldRegion: the foreground contains segment " +
257 std::to_string(seg_id) +
258 ", which is absent from the periodic background. The foreground "
259 "must be carved out of the same system the background was "
260 "converged on.");
261 }
262 // Record the position of the NEUTRAL BACKGROUND COPY this foreground
263 // segment displaces -- not the foreground's own centroid.
264 //
265 // They are not the same point. A charged job maps its central
266 // segment with that charge state's own geometry, so the foreground
267 // is a relaxed cation (or anion) sitting where a neutral molecule
268 // used to be. Its centroid is therefore displaced from the
269 // background copy's by far more than the 1e-4 bohr tolerance
270 // EwaldRealSpaceSum uses to recognise a copy -- that tolerance is
271 // there to absorb round-off, not geometry relaxation.
272 //
273 // Recording the foreground's own centroid therefore meant the
274 // real-space sum found NO match and silently left the neutral copy
275 // in the background, while the reciprocal and shape exclusions
276 // (which match by address) removed it correctly. The copy was
277 // counted in the erfc half and not the erf half: a mismatch that
278 // only appears for a charged foreground, and that breaks
279 // alpha-independence because erfc and erf shift weight with alpha.
280 //
281 // Snapping to the nearest lattice image rather than requiring an
282 // exact hit also makes this robust to any future geometry that
283 // differs between charge states.
284 const PolarSegment& bg_copy =
286 const Eigen::Vector3d bg_centroid = Centroid(bg_copy);
287 const Eigen::Vector3d delta = entry.second - bg_centroid;
288 const Eigen::Vector3d fractional = params_.box.inverse() * delta;
289 const Eigen::Vector3d image =
290 params_.box * fractional.array().round().matrix();
291 const Eigen::Vector3d residual = delta - image;
292
293 // A residual much larger than a molecule means the foreground
294 // segment is not where the background thinks that id lives, which
295 // is a mapping error rather than a relaxation. Loud, because the old
296 // behaviour for exactly this case was to carry on silently.
297 constexpr double kResidualWarn = 5.0; // bohr
298 if (residual.norm() > kResidualWarn) {
300 << TimeStamp() << " WARNING: foreground segment " << seg_id
301 << " sits " << residual.norm()
302 << " bohr from the nearest periodic image of its background "
303 "copy. That is too far to be geometry relaxation, so the "
304 "copy this code is about to suppress may not be the one the "
305 "foreground actually displaces."
306 << std::flush;
307 }
308
309 foreground_copies_.push_back({seg_id, bg_centroid + image});
310 }
311
312 real_sum_ = std::make_unique<EwaldRealSpaceSum>(
313 params_.box, registry_, params_.alpha, params_.thole_a, params_.r_min,
314 params_.field_tol, 0.945, 15, params_.screening_factor,
316 recip_sum_ = std::make_unique<EwaldReciprocalSpaceSum>(
317 params_.box, registry_, params_.alpha, params_.k_max);
318 shape_ = std::make_unique<EwaldShapeCorrection>(params_.box.determinant(),
319 registry_, params_.shape);
320 interactor_ = std::make_unique<EwaldRealSpaceInteractor>(params_.alpha,
321 params_.thole_a);
322}
323
325 const std::vector<Eigen::Vector3d>& points) const {
326 if (!loaded_) {
327 NotInitialized("PotentialAt");
328 }
329 if (registered_foreground_.empty()) {
330 throw std::runtime_error(
331 "EwaldRegion::PotentialAt: no foreground has been declared. "
332 "ApplyFieldTo can fall back on the segments it is handed, but a "
333 "list of points carries no segment identity, so the copies to "
334 "suppress cannot be inferred. JobTopology declares the foreground "
335 "with RegisterForeground before any region is evaluated.");
336 }
337 if (!real_sum_) {
338 BuildSums(std::vector<PolarSegment>());
339 }
340
341 // A foreground segment's id. It decides only whether the
342 // zero-translation self-pair is skipped, and that skip is disabled for
343 // sources that have a foreground copy -- which is what a point that is
344 // not a site of its own wants.
345 const Index probe_segment_id = built_foreground_.front().first;
346
347 using Clock = std::chrono::steady_clock;
348 auto seconds_since = [](Clock::time_point t) {
349 return std::chrono::duration<double>(Clock::now() - t).count();
350 };
351 auto t_start = Clock::now();
352 Eigen::VectorXd phi = real_sum_->PotentialAtMany(probe_segment_id, points,
354 const double t_real = seconds_since(t_start);
355 auto t_recip_start = Clock::now();
356 phi += recip_sum_->PotentialAtMany(points, EwaldChargeState::Neutral);
357 const double t_recip = seconds_since(t_recip_start);
358 auto t_corr_start = Clock::now();
359
360 const Index n_points = Index(points.size());
361
362 // Shape term: depends on the point only through its position relative to
363 // the background's moments, which are computed once.
364 phi += shape_->PotentialAtMany(points, EwaldChargeState::Neutral);
365
366 // Remove the erf-screened half of the neutral foreground copies that the
367 // reciprocal sum necessarily put back -- the same copies, with the same
368 // shift, as step (4) of ApplyFieldTo. Their sites are flattened once.
369 struct ProbeSource {
370 Eigen::Vector3d pos;
371 double q;
372 Eigen::Vector3d mu;
373 };
374 std::vector<ProbeSource> erf_sources;
375 for (const auto& copy : foreground_copies_) {
376 const PolarSegment& bg =
377 registry_.Get(copy.first, EwaldChargeState::Neutral);
378 const Eigen::Vector3d shift = copy.second - Centroid(bg);
379 for (const PolarSite& source : bg) {
380 erf_sources.push_back(
381 {source.getPos() + shift, source.getCharge(),
382 source.getStaticDipole() + source.getInducedDipole()});
383 }
384 }
385#pragma omp parallel for schedule(static)
386 for (Index p = 0; p < n_points; ++p) {
387 const Eigen::Vector3d& point = points[std::size_t(p)];
388 double extra = 0.0;
389 for (const ProbeSource& source : erf_sources) {
390 extra -=
391 interactor_->ErfPotential(source.q, source.mu, point - source.pos);
392 }
393 phi[p] += extra;
394 }
396 << TimeStamp() << " Ewald potential at " << n_points
397 << " points: real space " << std::setprecision(4) << t_real
398 << " s, reciprocal " << t_recip << " s, shape + erf correction "
399 << seconds_since(t_corr_start) << " s (" << foreground_copies_.size()
400 << " foreground copies)" << std::flush;
401 return phi;
402}
403
404double EwaldRegion::ApplyFieldTo(std::vector<PolarSegment>& foreground) const {
405 if (!loaded_) {
406 NotInitialized("ApplyFieldTo");
407 }
408 if (!real_sum_) {
409 BuildSums(foreground);
410 }
411 // Also on the first call: with a registered foreground BuildSums ignores
412 // its argument, so this is what catches a client asking about a segment
413 // nobody declared.
414 CheckForegroundIsSubset(foreground);
415
416 // Flat target list: the reciprocal sum takes them in one batch, which
417 // is what makes its structure factors worth computing once.
418 std::vector<PolarSite*> targets;
419 std::vector<Index> target_segment_ids;
420 for (PolarSegment& seg : foreground) {
421 for (PolarSite& site : seg) {
422 targets.push_back(&site);
423 target_segment_ids.push_back(seg.getId());
424 }
425 }
426
427 // SIGN CONVENTION. The Ewald code and the region framework store V
428 // with OPPOSITE signs, and this is the boundary between them:
429 //
430 // EwaldBackground builds its solver's rhs as b = +V
431 // PolarRegion builds its solver's rhs as b = -(V + V_noE)
432 //
433 // Both are internally consistent; neither is wrong on its own. But the
434 // sums below write in the Ewald convention, and the polar region is
435 // about to negate whatever it finds -- so handing it the field as-is
436 // drives the induction BACKWARDS. Measured on a neutral MM/MM job,
437 // where the foreground should reproduce the background it was carved
438 // from: deviations of 100-700%, flat with distance rather than growing
439 // outward, which is what first exposed this.
440 //
441 // So this region's own contribution is negated before it is handed
442 // over. Only the contribution is touched: whatever other regions have
443 // already accumulated is preserved, which is why the prior values are
444 // recorded rather than assuming V starts at zero.
445 std::vector<Eigen::Vector3d> v_before;
446 v_before.reserve(targets.size());
447 for (const PolarSite* target : targets) {
448 v_before.push_back(target->V());
449 }
450
451 // (1) real space, foreground copies suppressed. Through AddFieldAtMany,
452 // not an omp loop around AddFieldAt: on the first call no target
453 // has a neighbour list yet, AddFieldAt inserts one into a shared
454 // unordered_map on every miss, and concurrent insertion silently
455 // loses entries. The energy loops below then find no list for
456 // those targets and throw -- which is how this surfaced, on a
457 // 28-thread production QMMM job.
458 const Index n_targets = Index(targets.size());
459 {
460 std::vector<std::pair<Index, PolarSite*>> real_targets;
461 real_targets.reserve(targets.size());
462 for (Index i = 0; i < n_targets; ++i) {
463 real_targets.push_back(
464 {target_segment_ids[std::size_t(i)], targets[std::size_t(i)]});
465 }
466 real_sum_->AddFieldAtMany<Estatic::V>(real_targets,
468 }
469
470 // Permanent-permanent (Q-Q) energy with the background, over exactly
471 // the neighbour set the field above used -- same suppression, same
472 // cull. Only the PERMANENT part: PolarRegion accounts for the induced
473 // contribution itself, as E_polar_ext = sum of mu_ind . V, computed
474 // from the very field being delivered here. Adding it again would
475 // double-count.
476 //
477 // Runs after the field loop, not inside it, because the neighbour
478 // cache must exist first and CalcStaticEnergyAt deliberately refuses
479 // to build one.
480 double e_real = 0.0;
481#pragma omp parallel for schedule(dynamic, 16) reduction(+ : e_real)
482 for (Index i = 0; i < n_targets; ++i) {
483 e_real += real_sum_->CalcStaticEnergyAt(*targets[std::size_t(i)],
485 }
486 double energy = e_real;
487
488 // (1b) [foreground permanent] x [background INDUCED], real space.
489 // The corner of the permanent/induced product that nothing else
490 // covers: PolarRegion's E_polar_ext contracts the FOREGROUND's
491 // induced dipoles against the delivered field, which gives
492 // [fg induced] x [bg anything]; e_real above gives
493 // [fg permanent] x [bg permanent]. Without this term a job's
494 // induced energy comes out short -- measurably so, by a factor
495 // near two on a neutral foreground, against legacy's _pu channel.
496 //
497 // Same neighbour set, same cache, same suppression as e_real.
498 double e_real_pu = 0.0;
499#pragma omp parallel for schedule(dynamic, 16) reduction(+ : e_real_pu)
500 for (Index i = 0; i < n_targets; ++i) {
501 e_real_pu += real_sum_->CalcInducedSourceEnergyAt(
502 *targets[std::size_t(i)], EwaldChargeState::Neutral);
503 }
504 energy += e_real_pu;
505
506 // (2) reciprocal space, over the full periodic density
507 recip_sum_->AddFieldAtMany<Estatic::V>(targets, EwaldChargeState::Neutral);
508
509 // (3) shape/surface. Target-independent, so evaluated once.
510 {
511 PolarSite probe(-1, "X", Eigen::Vector3d::Zero());
512 shape_->AddFieldAt<Estatic::V>(probe, EwaldChargeState::Neutral);
513 const Eigen::Vector3d shape_field = probe.V();
514 for (PolarSite* target : targets) {
515 target->V() += shape_field;
516 }
517 }
518
519 // (4) remove the erf-screened field of the neutral foreground copies
520 // that step (2) necessarily put back. See ApplyFieldTo's own
521 // declaration for why these use the background's own multipoles
522 // and dipoles rather than the job's charge state.
523#pragma omp parallel for schedule(dynamic, 16)
524 for (Index i = 0; i < n_targets; ++i) {
525 PolarSite& target = *targets[std::size_t(i)];
526 for (const auto& copy : foreground_copies_) {
527 const PolarSegment& bg =
528 registry_.Get(copy.first, EwaldChargeState::Neutral);
529 const Eigen::Vector3d shift = copy.second - Centroid(bg);
530 for (const PolarSite& source : bg) {
531 interactor_->ApplyErfStaticFieldCorrection<PolarSite, Estatic::V>(
532 source, target, shift);
533 interactor_->ApplyErfInducedFieldCorrection<Estatic::V>(source, target,
534 shift);
535 }
536 }
537 }
538 // (5) reciprocal-space and shape Q-Q energy between the foreground and
539 // the rest of the cell.
540 //
541 // NOTHING is held out of S_bg. Every segment contributes at every
542 // image; what is removed instead is the erf-screened energy of the
543 // COINCIDENT copy of each foreground segment, because that is
544 // exactly what real space suppresses.
545 //
546 // Two earlier versions were wrong in opposite directions and the
547 // answer sits between them. Holding the whole foreground out of
548 // S_bg also deletes its periodic IMAGES -- a lattice of vacancies
549 // rather than one carved-out cavity, worth 1e-3 eV of
550 // alpha-dependence on a charged 18-segment job. Holding out only
551 // the target's own segment fixed that but still deleted each
552 // segment's interaction with its own images, and was needed only
553 // because EwaldRealSpaceSum was skipping that segment at every
554 // translation. With that skip gone, real space suppresses the same
555 // set for every target, so one structure factor is correct.
556 //
557 // recip + shape together are the erf interaction over all images
558 // (the shape term IS the k=0 limit the reciprocal sum omits), so
559 // subtracting a pair's erf energy removes that pair exactly. For a
560 // segment's own coincident copy that subtraction is the r -> 0
561 // branch of CalcErfStaticEnergy -- whose sign had to be fixed
562 // before this change was possible.
563 {
564 double e_recip = 0.0;
565 double e_shape = 0.0;
566 double e_erf = 0.0;
567
568 std::vector<std::pair<const PolarSite*, Eigen::Vector3d>> fg_sites;
569 fg_sites.reserve(targets.size());
570 for (std::size_t n = 0; n < targets.size(); ++n) {
571 fg_sites.push_back({targets[n], targets[n]->getPos()});
572 }
573 const std::vector<const PolarSite*> no_exclusions;
574
575 e_recip = recip_sum_->CalcStaticEnergyBetween(fg_sites, no_exclusions,
577 e_shape = shape_->CalcStaticEnergyBetween(fg_sites, no_exclusions,
579
580 // Every foreground copy, the target's own included. Real space
581 // suppressed exactly these, so exactly these come back out.
582 for (const auto& copy : foreground_copies_) {
583 const PolarSegment& bg_copy =
584 registry_.Get(copy.first, EwaldChargeState::Neutral);
585 const Eigen::Vector3d shift = copy.second - Centroid(bg_copy);
586 for (const PolarSite& source : bg_copy) {
587 for (const auto& entry : fg_sites) {
588 e_erf += interactor_->CalcErfStaticEnergy<PolarSite, PolarSite>(
589 source, *entry.first, shift);
590 }
591 }
592 }
593 energy += e_recip + e_shape - e_erf;
594
595 // (5b), (6b) reciprocal and shape partners of the induced-source
596 // term added at (1b), with exactly the same exclusion structure
597 // as (5) above and for exactly the same reason.
598 double e_recip_pu = recip_sum_->CalcInducedSourceEnergyBetween(
599 fg_sites, no_exclusions, EwaldChargeState::Neutral);
600 double e_shape_pu = shape_->CalcInducedSourceEnergyBetween(
601 fg_sites, no_exclusions, EwaldChargeState::Neutral);
602 double e_erf_pu = 0.0;
603 for (const auto& copy : foreground_copies_) {
604 const PolarSegment& bg_copy =
605 registry_.Get(copy.first, EwaldChargeState::Neutral);
606 const Eigen::Vector3d shift = copy.second - Centroid(bg_copy);
607 for (const PolarSite& source : bg_copy) {
608 for (const auto& entry : fg_sites) {
609 e_erf_pu += interactor_->CalcErfInducedSourceEnergy(
610 source, *entry.first, shift);
611 }
612 }
613 }
614 energy += e_recip_pu + e_shape_pu - e_erf_pu;
615
616 // Term-by-term report, for comparison against the legacy `ewald`
617 // job calculator's terms_o block. The correspondence is now
618 // one-to-one -- real <-> R_pp, recip <-> K_pp, shape <-> J_pp,
619 // erf <-> C_pp -- since this code stopped excluding foreground
620 // copies from S_bg and started subtracting their erf energy the way
621 // legacy does. Measured agreement on an 18-segment job: every term
622 // to legacy's six printed figures, on both a rank-0 and an
623 // artificially dipolar methane. Printed in eV, the unit legacy
624 // reports and the job XML carries.
625 const double h2ev = tools::conv::hrt2ev;
627 << TimeStamp()
628 << " Ewald energy [eV], permanent x permanent: real = " << e_real * h2ev
629 << " recip = " << e_recip * h2ev << " shape = " << e_shape * h2ev
630 << " erf = " << e_erf * h2ev << std::flush;
632 << TimeStamp()
633 << " Ewald energy [eV], fg permanent x bg induced: real = "
634 << e_real_pu * h2ev << " recip = " << e_recip_pu * h2ev
635 << " shape = " << e_shape_pu * h2ev << " erf = " << e_erf_pu * h2ev
636 << std::flush;
638 << TimeStamp() << " Ewald energy [eV], total = " << energy * h2ev
639 << std::flush;
641 << TimeStamp() << " Ewald split: alpha = " << params_.alpha
642 << " 1/bohr (" << params_.alpha * 18.8972612 << " 1/nm)"
643 << ", k_max = " << params_.k_max << " 1/bohr ("
644 << params_.k_max * 18.8972612 << " 1/nm)"
645 << ", r_min = " << params_.r_min
646 << " bohr, V = " << params_.box.determinant()
647 << " bohr^3, thole_a = " << params_.thole_a << std::flush;
649 << TimeStamp() << " Foreground: " << foreground_copies_.size()
650 << " segments, " << targets.size() << " sites, carved from "
651 << registry_.AllIds().size() << " registered" << std::flush;
652
653 // Suppression audit. A shortfall means a foreground copy was left in
654 // the background -- silent otherwise, and exactly the failure this
655 // check exists for. Every copy is suppressed for every target, the
656 // target's own segment included, hence size() and not size() - 1.
658 real_sum_->GetNeighborStats();
659 const Index expected =
660 Index(targets.size()) * Index(foreground_copies_.size());
662 << TimeStamp() << " Foreground copies suppressed: " << stats.foreground
663 << " of " << expected
664 << ((stats.foreground == expected) ? " (ok)" : " <-- MISMATCH")
665 << std::flush;
666 }
667
668 // Convert this region's contribution into the convention the polar
669 // region expects -- see the note where v_before is captured.
670 for (std::size_t i = 0; i < targets.size(); ++i) {
671 const Eigen::Vector3d ewald_contribution = targets[i]->V() - v_before[i];
672 targets[i]->V() = v_before[i] - ewald_contribution;
673 }
674
675 if (!warned_no_energy_) {
676 warned_no_energy_ = true;
678 << TimeStamp()
679 << " NOTE: the background's own internal energy is deliberately "
680 "absent -- it is a constant that cancels in any charge-state "
681 "difference. Everything else is here; see ApplyFieldTo's "
682 "declaration for the full accounting."
683 << std::flush;
684 }
685 return energy;
686}
687
688} // namespace xtp
689} // namespace votca
class to manage program options with xml serialization functionality
Definition property.h:55
Property & add(const std::string &key, const std::string &value)
add a new property to structure
Definition property.cc:108
T ifExistsReturnElseReturnDefault(const std::string &key, T defaultvalue) const
Definition property.h:332
CheckpointReader getReader()
CheckpointReader openChild(const std::string &childName) const
EwaldParameters params_
double Etotal() const override
void Initialize(const tools::Property &prop) override
void CheckForegroundIsSubset(const std::vector< PolarSegment > &foreground) const
Eigen::VectorXd PotentialAt(const std::vector< Eigen::Vector3d > &points) const
void Evaluate(std::vector< std::unique_ptr< Region > > &regions) override
std::vector< std::pair< Index, Eigen::Vector3d > > built_foreground_
void WritePDB(csg::PDBWriter &writer) const override
EwaldRegistry registry_
std::unique_ptr< EwaldShapeCorrection > shape_
double charge() const override
std::unique_ptr< EwaldReciprocalSpaceSum > recip_sum_
std::unique_ptr< EwaldRealSpaceSum > real_sum_
std::vector< std::pair< Index, Eigen::Vector3d > > registered_foreground_
void RegisterForeground(const std::vector< std::pair< Index, Eigen::Vector3d > > &foreground)
std::unique_ptr< EwaldRealSpaceInteractor > interactor_
std::string checkpoint_file_
Index size() const override
void AppendResult(tools::Property &prop) const override
double ApplyFieldTo(std::vector< PolarSegment > &foreground) const
std::vector< std::pair< Index, Eigen::Vector3d > > foreground_copies_
void BuildSums(const std::vector< PolarSegment > &fallback) const
void ReadFromCpt(CheckpointReader &r) override
void WriteToCpt(CheckpointWriter &w) const override
Class to represent Atom/Site in electrostatic+polarization.
Definition polarsite.h:36
const Eigen::Vector3d & V() const
Definition polarsite.h:68
std::vector< double > ApplyInfluenceOfOtherRegions(std::vector< std::unique_ptr< Region > > &regions)
Definition region.cc:33
Logger & log_
Definition region.h:106
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
#define XTP_LOG(level, log)
Definition logger.h:40
const double hrt2ev
Definition constants.h:53
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