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