votca 2026-dev
Loading...
Searching...
No Matches
crfit.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 <filesystem>
22#include <fstream>
23#include <iomanip>
24
25// Third party includes
26#include <boost/format.hpp>
27
28// VOTCA includes
30
31// Local VOTCA includes
33#include "votca/xtp/orbitals.h"
34
35// Local private VOTCA includes
36#include "crfit.h"
37
38namespace votca {
39namespace xtp {
40
42 xyzfile_ = job_name_ + ".xyz";
43 dftoptions_ = options.get(".dftpackage");
44 if (dftoptions_.get("name").as<std::string>() != "xtp") {
45 throw std::runtime_error("crfit: needs the xtp dftpackage");
46 }
47 const std::string method = options.get(".method").as<std::string>();
48 if (method != "analytic" && method != "finite_differences") {
49 throw std::runtime_error(
50 "crfit: method must be analytic or finite_differences");
51 }
52 fit_.analytic = (method == "analytic");
53 fit_.tolerance = options.get(".tolerance").as<double>();
54 fit_.step = options.get(".step").as<double>();
55 scf_error_ = options.get(".scf_error").as<double>();
56 fit_.use_inversion = options.get(".inversion").as<bool>();
57 output_ = options.ifExistsReturnElseReturnDefault<std::string>(
58 ".output", job_name_ + "_cr");
59 if (options.exists(".mpsfile")) {
60 mpsfile_ = options.get(".mpsfile").as<std::string>();
61 }
62 // each SCF at least as tight as scf_error (the differences divide by step)
63 tools::Property& conv = dftoptions_.get("xtpdft.convergence");
64 const double error = conv.get("error").as<double>();
65 auto str = [](double x) {
66 std::stringstream s;
67 s << std::setprecision(17) << x;
68 return s.str();
69 };
70 if (error > scf_error_) {
71 conv.set("error", str(scf_error_));
72 }
73 const double energy = conv.get("energy").as<double>();
74 if (energy > 1e-2 * scf_error_) {
75 conv.set("energy", str(1e-2 * scf_error_));
76 }
77}
78
79bool CRFit::Run() {
80 log_.setReportLevel(Log::current_level);
81 log_.setMultithreading(true);
82 log_.setCommonPreface("\n... ...");
83
84 Orbitals orb0;
86 const Index charge = dftoptions_.get("charge").as<Index>();
87 const Index spin = dftoptions_.get("spin").as<Index>();
88 if (spin != 1) {
89 throw std::runtime_error(
90 "crfit: closed-shell molecules only (spin 1), got spin " +
91 std::to_string(spin));
92 }
93 orb0.setChargeAndSpin(charge, spin);
94 const QMMolecule& mol = orb0.QMAtoms();
95 const Index n_atoms = mol.size();
96
98 << TimeStamp() << " Charge-response fit of " << n_atoms << " atoms from "
99 << xyzfile_ << std::flush;
102
103 const double A3 = std::pow(tools::conv::bohr2ang, 3);
104 auto eig = [&](const Eigen::Matrix3d& m) {
105 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> es(m,
106 Eigen::EigenvaluesOnly);
107 return Eigen::Vector3d(es.eigenvalues() * A3);
108 };
110 << std::setprecision(4) << TimeStamp()
111 << " alpha eigenvalues [A^3]: " << eig(r.alpha).transpose()
112 << "\n charge flow X^T Pi X: " << eig(r.alpha_ct).transpose()
113 << "\n residual: " << eig(r.alpha_res).transpose()
114 << "\n Pi: asymmetry before symmetrizing " << r.pi_asymmetry
115 << ", eigenvalues " << r.pi_eigenvalues(1) << " .. "
116 << r.pi_eigenvalues(n_atoms - 1) << " e^2/Hartree" << std::flush;
117
118 WriteCrm(mol, r);
119 WriteJson(mol, r);
120 if (!mpsfile_.empty()) {
121 WriteMps(mol, r);
122 }
123 return true;
124}
125
126std::string CRFit::MethodLabel() const {
127 std::stringstream s;
128 if (fit_.analytic) {
129 s << "coupled-perturbed KS, tolerance " << fit_.tolerance;
130 } else {
131 s << "finite differences, step " << fit_.step;
132 }
133 return s.str();
134}
135
137 const ChargeResponseFit::Result& r) const {
138 const std::string file = output_ + ".crm";
139 std::ofstream ofs(file);
140 if (!ofs.is_open()) {
141 throw std::runtime_error("crfit: cannot write " + file);
142 }
143 ofs << "! charge-response matrix (Hirshfeld), "
144 << dftoptions_.get("functional").as<std::string>() << "/"
145 << dftoptions_.get("basisset").as<std::string>() << ", from " << xyzfile_
146 << " (xtp_tools -e crfit, " << MethodLabel() << ")\n";
147 ofs << "! dq = -Pi phi; Pi in e^2/Hartree, atom order as below (Angstrom)\n";
148 ofs << mol.size() << "\n";
149 for (const QMAtom& atom : mol) {
150 const Eigen::Vector3d p = atom.getPos() * tools::conv::bohr2ang;
151 ofs << boost::format("%-2s %12.6f %12.6f %12.6f\n") % atom.getElement() %
152 p(0) % p(1) % p(2);
153 }
154 for (Index j = 0; j < r.pi.rows(); ++j) {
155 for (Index k = 0; k < r.pi.cols(); ++k) {
156 ofs << boost::format("%+.10e") % r.pi(j, k)
157 << (k + 1 < r.pi.cols() ? " " : "\n");
158 }
159 }
160 XTP_LOG(Log::error, log_) << TimeStamp() << " Wrote " << file << std::flush;
161}
162
163namespace {
164std::string Json(const Eigen::MatrixXd& m) {
165 std::stringstream s;
166 s << std::setprecision(15) << "[";
167 for (Index i = 0; i < m.rows(); ++i) {
168 s << (i ? ", [" : "[");
169 for (Index j = 0; j < m.cols(); ++j) {
170 s << (j ? ", " : "") << m(i, j);
171 }
172 s << "]";
173 }
174 return s.str() + "]";
175}
176std::string JsonVector(const Eigen::VectorXd& v) {
177 std::stringstream s;
178 s << std::setprecision(15);
179 for (Index i = 0; i < v.size(); ++i) {
180 s << (i ? ", " : "") << v(i);
181 }
182 return s.str();
183}
184} // namespace
185
187 const ChargeResponseFit::Result& r) const {
188 // the keys of xtp_cr_fit, so that xtp_cr_mps reads both
189 const std::string file = output_ + ".json";
190 std::ofstream ofs(file);
191 if (!ofs.is_open()) {
192 throw std::runtime_error("crfit: cannot write " + file);
193 }
194 const double A3 = std::pow(tools::conv::bohr2ang, 3);
195 Eigen::MatrixXd coords(mol.size(), 3);
196 std::string elements;
197 for (Index k = 0; k < mol.size(); ++k) {
198 coords.row(k) = mol[k].getPos().transpose();
199 elements += (k ? ", \"" : "\"") + mol[k].getElement() + "\"";
200 }
201 std::string res_atoms;
202 for (std::size_t k = 0; k < r.alpha_res_atoms.size(); ++k) {
203 res_atoms += (k ? ", " : "") + Json(r.alpha_res_atoms[k]);
204 }
205 ofs << "{\n"
206 << " \"xyz\": \"" << xyzfile_ << "\",\n"
207 << " \"xc\": \"" << dftoptions_.get("functional").as<std::string>()
208 << "\",\n"
209 << " \"basis\": \"" << dftoptions_.get("basisset").as<std::string>()
210 << "\",\n"
211 << " \"charge\": " << dftoptions_.get("charge").as<Index>() << ",\n"
212 << " \"method\": \"xtp_tools -e crfit (" << MethodLabel() << ")\",\n"
213 << " \"units\": \"atomic (bohr, e, Hartree); Pi in e^2/Hartree\",\n"
214 << " \"elements\": [" << elements << "],\n"
215 << " \"coords_bohr\": " << Json(coords) << ",\n"
216 << " \"alpha_dft_au\": " << Json(r.alpha) << ",\n"
217 << " \"alpha_dft_A3\": " << Json(r.alpha * A3) << ",\n"
218 << " \"Pi\": " << Json(r.pi) << ",\n"
219 << " \"Pi_eigenvalues\": [" << JsonVector(r.pi_eigenvalues) << "],\n"
220 << std::setprecision(6) << " \"Pi_asymmetry\": " << r.pi_asymmetry
221 << ",\n"
222 << " \"alpha_ct_A3\": " << Json(r.alpha_ct * A3) << ",\n"
223 << " \"alpha_res_A3\": " << Json(r.alpha_res * A3) << ",\n"
224 << " \"alpha_res_weights\": [" << JsonVector(r.res_weights) << "],\n"
225 << " \"alpha_res_atoms_au\": [" << res_atoms << "]\n"
226 << "}\n";
227 XTP_LOG(Log::error, log_) << TimeStamp() << " Wrote " << file << std::flush;
228}
229
231 const ChargeResponseFit::Result& r) const {
232 PolarSegment tmpl("template", 0);
234 if (tmpl.size() != mol.size()) {
235 throw std::runtime_error(
236 "crfit: " + mpsfile_ + " has " + std::to_string(tmpl.size()) +
237 " sites, the molecule " + std::to_string(mol.size()) + " atoms");
238 }
239 Eigen::MatrixX3d P(mol.size(), 3), Q(mol.size(), 3);
240 for (Index k = 0; k < mol.size(); ++k) {
241 if (tmpl[k].getElement() != mol[k].getElement()) {
242 throw std::runtime_error("crfit: site " + std::to_string(k) + " of " +
243 mpsfile_ + " is " + tmpl[k].getElement() +
244 ", atom " + std::to_string(k) + " is " +
245 mol[k].getElement());
246 }
247 P.row(k) = tmpl[k].getPos().transpose();
248 Q.row(k) = mol[k].getPos().transpose();
249 }
250 // rigid alignment (Kabsch) of the fit geometry onto the template, to
251 // rotate the residual tensors into the template frame
252 const Eigen::MatrixX3d P0 = P.rowwise() - P.colwise().mean();
253 const Eigen::MatrixX3d Q0 = Q.rowwise() - Q.colwise().mean();
254 Eigen::JacobiSVD<Eigen::Matrix3d> svd(
255 Q0.transpose() * P0, Eigen::ComputeFullU | Eigen::ComputeFullV);
256 Eigen::Matrix3d D = Eigen::Matrix3d::Identity();
257 D(2, 2) = (svd.matrixU() * svd.matrixV().transpose()).determinant() < 0 ? -1.0
258 : 1.0;
259 const Eigen::Matrix3d U =
260 svd.matrixV() * D * svd.matrixU().transpose(); // fit -> template
261 const double rmsd =
262 std::sqrt((Q0 * U.transpose() - P0).rowwise().squaredNorm().mean());
263 if (rmsd * tools::conv::bohr2ang > 0.05) {
265 << TimeStamp() << " WARNING: template and fit geometry differ by "
266 << rmsd * tools::conv::bohr2ang << " A rms after alignment"
267 << std::flush;
268 }
269 for (Index k = 0; k < mol.size(); ++k) {
270 tmpl[k].setpolarization(U * r.alpha_res_atoms[std::size_t(k)] *
271 U.transpose());
272 }
273 const std::string file = output_ + ".mps";
274 tmpl.WriteMPS(file, "CRFIT (permanent multipoles of " +
275 std::filesystem::path(mpsfile_).filename().string() +
276 ", residual polarizabilities of the fit)");
277 XTP_LOG(Log::error, log_) << TimeStamp() << " Wrote " << file << std::flush;
278}
279
280} // namespace xtp
281} // namespace votca
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
const Eigen::Vector3d & getPos() const
Logger log_
Definition crfit.h:57
void WriteJson(const QMMolecule &mol, const ChargeResponseFit::Result &r) const
Definition crfit.cc:186
void WriteMps(const QMMolecule &mol, const ChargeResponseFit::Result &r) const
Definition crfit.cc:230
tools::Property dftoptions_
Definition crfit.h:58
std::string mpsfile_
Definition crfit.h:60
std::string xyzfile_
Definition crfit.h:59
std::string MethodLabel() const
Definition crfit.cc:126
bool Run() final
Definition crfit.cc:79
ChargeResponseFit::FitOptions fit_
Definition crfit.h:62
void ParseOptions(const tools::Property &options) final
Definition crfit.cc:41
std::string output_
Definition crfit.h:61
double scf_error_
Definition crfit.h:63
void WriteCrm(const QMMolecule &mol, const ChargeResponseFit::Result &r) const
Definition crfit.cc:136
static Result Fit(const tools::Property &dftoptions, Orbitals &orb, const FitOptions &fit, Logger &log)
void LoadFromFile(std::string filename)
void WriteMPS(std::string filename, std::string header) const
Container for molecular orbitals and derived one-particle data.
Definition orbitals.h:47
const QMMolecule & QMAtoms() const
Return read-only access to the molecular geometry.
Definition orbitals.h:262
void setChargeAndSpin(Index charge, Index spin)
Definition orbitals.h:246
container for QM atoms
Definition qmatom.h:37
void LoadFromFile(std::string filename)
Definition qmmolecule.cc:45
std::string job_name_
Definition qmtool.h:50
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 bohr2ang
Definition constants.h:49
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
static Level current_level
Definition globals.h:30
std::vector< Eigen::Matrix3d > alpha_res_atoms