votca 2026-dev
Loading...
Searching...
No Matches
polarregion.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2020 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 <algorithm>
22#include <iomanip>
23#include <numeric>
24
25// Local VOTCA includes
27#include "votca/xtp/citations.h"
32#include "votca/xtp/qmregion.h"
34
35#include <stdexcept>
36
37namespace votca {
38namespace xtp {
39
41 max_iter_ = prop.get("max_iter").as<Index>();
42 deltaD_ = prop.get("tolerance_dipole").as<double>();
43 deltaE_ = prop.get("tolerance_energy").as<double>();
44 exp_damp_ = prop.get("exp_damp").as<double>();
45 cr_width_ = prop.ifExistsReturnElseReturnDefault<double>("cr_width", 0.7) *
47 const std::string dd = prop.ifExistsReturnElseReturnDefault<std::string>(
48 "cr_dipole_damping", "gaussian");
49 if (dd != "gaussian" && dd != "thole") {
50 throw std::runtime_error(
51 "polarregion: cr_dipole_damping must be gaussian "
52 "or thole, got " +
53 dd);
54 }
55 cr_thole_dipoles_ = (dd == "thole");
56 if (!(cr_width_ >= 0.0)) {
57 throw std::runtime_error("polarregion: cr_width must be >= 0");
58 }
60}
61
63 return std::any_of(
64 segments_.begin(), segments_.end(),
65 [](const PolarSegment& seg) { return seg.hasChargeResponse(); });
66}
67
69 Index ncr = 0;
70 for (PolarSegment& seg : segments_) {
71 if (!seg.hasChargeResponse()) {
72 continue;
73 }
74 ncr++;
75 for (PolarSite& site : seg) {
76 site.setChargeResponse(cr_width_, cr_thole_dipoles_);
77 }
78 }
79 if (ncr > 0) {
81 << TimeStamp() << " Region:" << this->identify() << " " << this->getId()
82 << ": " << ncr << " of " << segments_.size()
83 << " segments with charge response, Gaussian width "
84 << cr_width_ * tools::conv::bohr2ang << " Angstrom, "
85 << (cr_thole_dipoles_ ? "Thole-damped" : "Gaussian")
86 << " dipoles between molecules" << std::flush;
87 }
88}
89
91
92 if (!E_hist_.filled()) {
93 return false;
94 }
95 double Echange = E_hist_.getDiff().Etotal();
96 std::string info = "not converged";
97 bool converged = false;
98 if (std::abs(Echange) < deltaE_) {
99 info = "converged";
100 converged = true;
101 }
103 << TimeStamp() << " Region:" << this->identify() << " " << this->getId()
104 << " is " << info << " deltaE=" << Echange << std::flush;
105 return converged;
106}
107
109
110 eeInteractor eeinteractor;
111 double e = 0.0;
112#pragma omp parallel for reduction(+ : e)
113 for (Index i = 0; i < size(); ++i) {
114 for (Index j = 0; j < size(); ++j) {
115 if (i == j) {
116 continue;
117 }
118 PolarSegment& seg1 = segments_[i];
119 const PolarSegment& seg2 = segments_[j];
120 e += eeinteractor.ApplyStaticField<PolarSegment, Estatic::noE_V>(seg2,
121 seg1);
122 }
123 }
124
125 return 0.5 * e;
126}
127
129#pragma omp declare reduction(CustomPlus : eeInteractor::E_terms : omp_out += \
130 omp_in)
131
132 eeInteractor eeinteractor(exp_damp_);
133
135
136#pragma omp parallel for reduction(CustomPlus : terms)
137 for (Index i = 0; i < size(); ++i) {
138 for (Index j = 0; j < i; ++j) {
139 terms += eeinteractor.CalcPolarEnergy(segments_[i], segments_[j]);
140 }
141 }
142
143#pragma omp parallel for reduction(CustomPlus : terms)
144 for (Index i = 0; i < size(); ++i) {
145 terms.E_indu_indu() +=
147 }
148
149#pragma omp parallel for reduction(CustomPlus : terms)
150 for (Index i = 0; i < size(); ++i) {
151 for (const PolarSite& site : segments_[i]) {
152 terms.E_internal() += site.InternalEnergy();
153 }
154 }
155 return terms;
156}
157
159 double e = 0.0;
160#pragma omp parallel for reduction(+ : e)
161 for (Index i = 0; i < size(); ++i) {
162 for (const PolarSite& site : segments_[i]) {
163 e += site.deltaQ_V_ext();
164 }
165 }
166 return e;
167}
168
170 for (PolarSegment& seg : segments_) {
171 for (PolarSite& site : seg) {
172 site.Reset();
173 }
174 }
175}
176
178 const Energy_terms& e = this->E_hist_.back();
179 prop.add("E_static", std::to_string(e.Estatic() * tools::conv::hrt2ev));
180 prop.add("E_polar", std::to_string(e.Epolar() * tools::conv::hrt2ev));
181 prop.add("E_total", std::to_string(e.Etotal() * tools::conv::hrt2ev));
182}
183
185 Index dof_polarization = 0;
186 for (const PolarSegment& seg : segments_) {
187 dof_polarization += seg.size() * 3;
188 }
189 return dof_polarization;
190}
191
193 Index dof_polarization = CalcPolDoF();
194 Eigen::VectorXd initial_induced_dipoles =
195 Eigen::VectorXd::Zero(dof_polarization);
196 eeInteractor interactor(exp_damp_);
197 Index index = 0;
198 for (const PolarSegment& seg : segments_) {
199 initial_induced_dipoles.segment(index, 3 * seg.size()) =
200 interactor.Cholesky_IntraSegment(seg);
201 index += 3 * seg.size();
202 }
203 return initial_induced_dipoles;
204}
205
207 Index dof_polarization = CalcPolDoF();
208 Eigen::VectorXd last_induced_dipoles =
209 Eigen::VectorXd::Zero(dof_polarization);
210 Index index = 0;
211 for (const PolarSegment& seg : segments_) {
212 for (const PolarSite& site : seg) {
213 last_induced_dipoles.segment<3>(index) = site.Induced_Dipole();
214 index += 3;
215 }
216 }
217 return last_induced_dipoles;
218}
219
220void PolarRegion::WriteInducedDipolesToSegments(const Eigen::VectorXd& x) {
221 Index index = 0;
222 for (PolarSegment& seg : segments_) {
223 for (PolarSite& site : seg) {
224 site.setInduced_Dipole(x.segment<3>(index));
225 index += 3;
226 }
227 }
228}
229
231 const Eigen::VectorXd& initial_guess) {
232 Eigen::VectorXd b = Eigen::VectorXd::Zero(initial_guess.size());
233 Index index = 0;
234 for (PolarSegment& seg : segments_) {
235 for (const PolarSite& site : seg) {
236 auto V = site.V() + site.V_noE();
237 b.segment<3>(index) = -V;
238 index += 3;
239 }
240 }
241 eeInteractor interactor(exp_damp_);
242 DipoleDipoleInteraction A(interactor, segments_);
243 Eigen::ConjugateGradient<DipoleDipoleInteraction, Eigen::Lower | Eigen::Upper,
244 Eigen::DiagonalPreconditioner<double>>
245 cg;
246 cg.setMaxIterations(max_iter_);
247 cg.setTolerance(deltaD_);
248 cg.compute(A);
249 Eigen::VectorXd x = cg.solveWithGuess(b, initial_guess);
250
252 << TimeStamp() << " CG: #iterations: " << cg.iterations()
253 << ", estimated error: " << cg.error() << std::flush;
254
255 if (cg.info() == Eigen::ComputationInfo::NoConvergence) {
256 info_ = false;
257 errormsg_ = "PCG iterations did not converge";
258 }
259
260 if (cg.info() == Eigen::ComputationInfo::NumericalIssue) {
261 info_ = false;
262 errormsg_ = "PCG had a numerical issue";
263 }
264 return x;
265}
266
267void PolarRegion::Evaluate(std::vector<std::unique_ptr<Region>>& regions) {
268 citations::Cite("Thole:1981");
269
270 std::vector<double> energies = ApplyInfluenceOfOtherRegions(regions);
271 Energy_terms e_contrib;
272 e_contrib.E_static_ext() =
273 std::accumulate(energies.begin(), energies.end(), 0.0);
275 << " Calculated static-static and polar-static "
276 "interaction with other regions"
277 << std::flush;
278 e_contrib.E_static_static() = StaticInteraction();
280 << " Calculated static interaction in region "
281 << std::flush;
282 Index dof_polarization = CalcPolDoF();
284 << TimeStamp() << " Starting Solving for classical polarization with "
285 << dof_polarization << " degrees of freedom." << std::flush;
286
287 if (hasChargeResponse()) {
288 // induced charges and dipoles together (ChargeResponseSolver)
289 eeInteractor interactor(exp_damp_);
290 ChargeResponseSolver solver(segments_, interactor);
291 const ChargeResponseSolver::Info info =
292 solver.Solve(deltaD_, max_iter_, E_hist_.filled());
294 << TimeStamp() << " Charge response: " << solver.size()
295 << " unknowns, CG #iterations: " << info.iterations
296 << ", relative residual: " << info.error << std::flush;
297 if (!info.converged) {
298 info_ = false;
299 errormsg_ = "charge-response CG iterations did not converge";
300 return;
301 }
303 terms.E_internal() += solver.InternalChargeEnergy();
304 e_contrib.addInternalPolarContrib(terms);
305 e_contrib.E_polar_ext() = PolarEnergy_extern();
306 LogAndStoreEnergies(e_contrib);
307 return;
308 }
309
310 Eigen::VectorXd initial_induced_dipoles;
311 if (!E_hist_.filled() || segments_.size() == 1) {
312 initial_induced_dipoles = CalcInducedDipoleInsideSegments();
313 } else {
314 initial_induced_dipoles = ReadInducedDipolesFromLastIteration();
315 }
316
317 Eigen::VectorXd x; // if only one segment
318 // it is solved exactly through the initial guess
319 if (segments_.size() != 1) {
320 x = CalcInducedDipolesViaPCG(initial_induced_dipoles);
321 if (!info_) {
322 return;
323 }
324 } else {
325 x = initial_induced_dipoles;
326 }
327
329
332 << " Calculated polar interaction in region"
333 << std::flush;
334 e_contrib.E_polar_ext() = PolarEnergy_extern();
336 << " Calculated polar interaction with other regions"
337 << std::flush;
338
339 LogAndStoreEnergies(e_contrib);
340}
341
343 XTP_LOG(Log::info, log_) << std::setprecision(10)
344 << " Internal static energy [hrt]= "
345 << e_contrib.E_static_static() << std::flush;
346 XTP_LOG(Log::info, log_) << std::setprecision(10)
347 << " External static energy [hrt]= "
348 << e_contrib.E_static_ext() << std::flush;
350 << std::setprecision(10)
351 << " Total static energy [hrt]= " << e_contrib.Estatic() << std::flush;
352
353 XTP_LOG(Log::info, log_) << std::setprecision(10)
354 << " internal dQ-dQ energy [hrt]= "
355 << e_contrib.E_indu_indu() << std::flush;
356
357 XTP_LOG(Log::info, log_) << std::setprecision(10)
358 << " internal Q-dQ energy [hrt]= "
359 << e_contrib.E_indu_stat() << std::flush;
360
361 XTP_LOG(Log::info, log_) << std::setprecision(10)
362 << " Internal energy [hrt]= "
363 << e_contrib.E_internal() << std::flush;
364
365 XTP_LOG(Log::info, log_) << std::setprecision(10)
366 << " External polar energy [hrt]= "
367 << e_contrib.E_polar_ext() << std::flush;
368
370 << std::setprecision(10)
371 << " Total polar energy [hrt]= " << e_contrib.Epolar() << std::flush;
372
374 << std::setprecision(10) << " Total energy [hrt]= " << e_contrib.Etotal()
375 << std::flush;
376 E_hist_.push_back(e_contrib);
377 return;
378}
379
381 // QMregions always have lower ids than other regions
383 return 0.0;
384}
386 bool noE_V = true;
387 if (this->getId() < region.getId()) {
388 noE_V = false;
389 }
390
391 double e = 0;
392#pragma omp parallel for reduction(+ : e)
393 for (Index i = 0; i < Index(segments_.size()); i++) {
394 PolarSegment& pseg1 = segments_[i];
395 double e_thread = 0.0;
396 eeInteractor ee;
397 for (const PolarSegment& pseg2 : region) {
398 if (noE_V) {
400 ee.ApplyInducedField<Estatic::noE_V>(pseg2, pseg1);
401 } else {
402 e_thread += ee.ApplyStaticField<PolarSegment, Estatic::V>(pseg2, pseg1);
403 e_thread += ee.ApplyInducedField<Estatic::V>(pseg2, pseg1);
404 }
405 e += e_thread;
406 }
407 }
408 return e;
409}
410
412 // Static regions always have higher ids than other regions
413
414 double e = 0.0;
415#pragma omp parallel for reduction(+ : e)
416 for (Index i = 0; i < Index(segments_.size()); i++) {
417 double e_thread = 0.0;
418 PolarSegment& pseg = segments_[i];
419 eeInteractor ee;
420 for (const StaticSegment& sseg : region) {
421 e_thread += ee.ApplyStaticField<StaticSegment, Estatic::V>(sseg, pseg);
422 }
423 e += e_thread;
424 }
425
426 return e;
427}
428
431 w(cr_width_, "cr_width");
432 w(cr_thole_dipoles_, "cr_thole_dipoles");
433}
434
437 if (hasChargeResponse()) {
438 // written with the segments' charge response (not in older checkpoints,
439 // which have no charge-response segments)
440 r(cr_width_, "cr_width");
441 r(cr_thole_dipoles_, "cr_thole_dipoles");
443 }
444}
445
447 // Delegated: the background owns the periodic sums, the foreground
448 // bookkeeping and the parameters they must all share, so assembling
449 // them here would mean reaching into it for all three.
450 const double e = region.ApplyFieldTo(segments_);
451 // induced charges see the background potential (the energy of the
452 // induced moments with the background is in PolarEnergy_extern)
453 if (hasChargeResponse()) {
455 }
456 return e;
457}
458
459} // namespace xtp
460} // 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
Property & get(const std::string &key)
get existing property
Definition property.cc:79
T as() const
return value as type
Definition property.h:283
T ifExistsReturnElseReturnDefault(const std::string &key, T defaultvalue) const
Definition property.h:332
Induced charges and dipoles of a polar region with charge-response segments (Thole segments may be mi...
Info Solve(double tolerance, Index max_iter, bool use_previous)
void addInternalPolarContrib(const eeInteractor::E_terms &induction_terms)
The periodic Ewald background, as a Region.
Definition ewaldregion.h:76
void ApplyPotentialTo(std::vector< PolarSegment > &foreground) const
double ApplyFieldTo(std::vector< PolarSegment > &foreground) const
Index size() const override
Definition mmregion.h:45
std::vector< PolarSegment > segments_
Definition mmregion.h:87
void WriteToCpt(CheckpointWriter &w) const override
Definition mmregion.cc:45
void ReadFromCpt(CheckpointReader &r) override
Definition mmregion.cc:58
Eigen::VectorXd CalcInducedDipoleInsideSegments() const
PolarRegion(Index id, Logger &log)
Definition polarregion.h:50
void AppendResult(tools::Property &prop) const override
void WriteInducedDipolesToSegments(const Eigen::VectorXd &x)
double InteractwithEwaldRegion(const EwaldRegion &region) override
void ReadFromCpt(CheckpointReader &r) override
double InteractwithQMRegion(const QMRegion &region) override
void Initialize(const tools::Property &prop) override
bool Converged() const override
eeInteractor::E_terms PolarEnergy() const
Eigen::VectorXd CalcInducedDipolesViaPCG(const Eigen::VectorXd &initial_guess)
bool hasChargeResponse() const
Eigen::VectorXd ReadInducedDipolesFromLastIteration() const
std::string identify() const override
Definition polarregion.h:52
hist< Energy_terms > E_hist_
void WriteToCpt(CheckpointWriter &w) const override
void LogAndStoreEnergies(Energy_terms e_contrib)
double InteractwithPolarRegion(const PolarRegion &region) override
void Evaluate(std::vector< std::unique_ptr< Region > > &regions) override
void Reset() override
double InteractwithStaticRegion(const StaticRegion &region) override
double PolarEnergy_extern() const
Class to represent Atom/Site in electrostatic+polarization.
Definition polarsite.h:36
void ApplyQMFieldToPolarSegments(std::vector< PolarSegment > &segments) const
Definition qmregion.cc:799
std::vector< double > ApplyInfluenceOfOtherRegions(std::vector< std::unique_ptr< Region > > &regions)
Definition region.cc:33
Index getId() const
Definition region.h:80
std::string errormsg_
Definition region.h:92
Logger & log_
Definition region.h:106
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
Mediates interaction between polar and static sites.
double ApplyStaticField(const T &segment1, PolarSegment &segment2) const
double ApplyInducedField(const PolarSegment &segment1, PolarSegment &segment2) const
E_terms CalcPolarEnergy(const S1 &segment1, const S2 &segment2) const
Eigen::VectorXd Cholesky_IntraSegment(const PolarSegment &seg) const
double CalcPolarEnergy_IntraSegment(const PolarSegment &seg) const
#define XTP_LOG(level, log)
Definition logger.h:40
const double ang2bohr
Definition constants.h:48
const double hrt2ev
Definition constants.h:53
const double bohr2ang
Definition constants.h:49
void Cite(const std::string &key)
marks a reference as used (thread safe; unknown keys are printed as such)
Definition citations.cc:289
Charge transport classes.
Definition ERIs.h:28
ClassicalSegment< PolarSite > PolarSegment
ClassicalSegment< StaticSite > StaticSegment
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26