votca 2026-dev
Loading...
Searching...
No Matches
chargeresponse.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 <fstream>
22#include <sstream>
23#include <stdexcept>
24
25// Third party includes
26#include <boost/lexical_cast.hpp>
27
28// VOTCA includes
29#include <votca/tools/getline.h>
31
32// Local VOTCA includes
34
35namespace votca {
36namespace xtp {
37
38namespace {
39// Relative tolerance of the checks of a .crm file: the fitting script
40// writes 9 significant digits, so a correct file is symmetric and
41// charge conserving to about 1e-8 of its largest element.
42constexpr double crm_tolerance = 1e-6;
43} // namespace
44
45ChargeResponse::ChargeResponse(std::vector<std::string> elements,
46 Eigen::MatrixXd pi)
47 : elements_(std::move(elements)), pi_(std::move(pi)) {
48 Check("ChargeResponse constructor");
49}
50
51void ChargeResponse::LoadFromFile(const std::string& filename) {
52 std::ifstream in(filename);
53 if (!in.is_open()) {
54 throw std::runtime_error("File:" + filename + " could not be opened");
55 }
56 // all non-comment tokens in file order
57 std::vector<std::string> tokens;
58 std::string line;
59 while (in.good()) {
60 tools::getline(in, line);
61 tools::Tokenizer toker(line, " \t");
62 std::vector<std::string> split = toker.ToVector();
63 if (split.empty() || split[0].substr(0, 1) == "!") {
64 continue;
65 }
66 tokens.insert(tokens.end(), split.begin(), split.end());
67 }
68 auto fail = [&](const std::string& what) {
69 throw std::runtime_error("Charge-response file " + filename + ": " + what);
70 };
71 if (tokens.empty()) {
72 fail("no data");
73 }
74 Index n = 0;
75 try {
76 n = boost::lexical_cast<Index>(tokens[0]);
77 } catch (const boost::bad_lexical_cast&) {
78 fail("the first entry must be the number of atoms, got '" + tokens[0] +
79 "'");
80 }
81 if (n <= 0) {
82 fail("number of atoms must be positive");
83 }
84 const std::size_t expected = 1 + 4 * std::size_t(n) + std::size_t(n * n);
85 if (tokens.size() != expected) {
86 fail("expected " + std::to_string(expected) + " entries for " +
87 std::to_string(n) +
88 " atoms (N, N lines 'El x y z', N x N matrix), "
89 "found " +
90 std::to_string(tokens.size()));
91 }
92 elements_.clear();
93 std::size_t k = 1;
94 try {
95 for (Index i = 0; i < n; ++i) {
96 elements_.push_back(tokens[k]);
97 // the geometry of the fit is informational: Pi is rotation invariant
98 // and the sites come from the .mps file
99 for (Index c = 0; c < 3; ++c) {
100 (void)boost::lexical_cast<double>(tokens[k + 1 + std::size_t(c)]);
101 }
102 k += 4;
103 }
104 pi_.resize(n, n);
105 for (Index i = 0; i < n; ++i) {
106 for (Index j = 0; j < n; ++j) {
107 pi_(i, j) = boost::lexical_cast<double>(tokens[k++]);
108 }
109 }
110 } catch (const boost::bad_lexical_cast&) {
111 fail("could not read entry " + std::to_string(k) + " ('" + tokens[k] +
112 "') as a number");
113 }
114 Check(filename);
115}
116
117void ChargeResponse::Check(const std::string& source) {
118 auto fail = [&](const std::string& what) {
119 throw std::runtime_error("Charge-response matrix from " + source + ": " +
120 what);
121 };
122 const Index n = Index(pi_.rows());
123 if (pi_.cols() != n || n == 0) {
124 fail("must be a non-empty square matrix");
125 }
126 if (Index(elements_.size()) != n) {
127 fail("has " + std::to_string(n) + " rows but " +
128 std::to_string(elements_.size()) + " atoms");
129 }
130 const double scale = pi_.cwiseAbs().maxCoeff();
131 if (!(scale > 0.0) || !pi_.allFinite()) {
132 fail("must be finite and not zero");
133 }
134 const double asym = (pi_ - pi_.transpose()).cwiseAbs().maxCoeff();
135 if (asym > crm_tolerance * scale) {
136 fail("is not symmetric (largest |Pi_ij - Pi_ji| " + std::to_string(asym) +
137 ")");
138 }
139 const double rowsum = pi_.rowwise().sum().cwiseAbs().maxCoeff();
140 if (rowsum > crm_tolerance * scale * double(n)) {
141 fail("does not conserve charge (largest row sum " + std::to_string(rowsum) +
142 ")");
143 }
144 // remove the rounding of the file: symmetrize, project out the constant
145 // vector (charge conservation)
146 const Eigen::MatrixXd P = Eigen::MatrixXd::Identity(n, n) -
147 Eigen::MatrixXd::Constant(n, n, 1.0 / double(n));
148 pi_ = P * (0.5 * (pi_ + pi_.transpose())) * P;
149 pi_ = 0.5 * (pi_ + pi_.transpose());
150 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(pi_,
151 Eigen::EigenvaluesOnly);
152 const double lowest = es.eigenvalues()(0);
153 const double highest = es.eigenvalues()(n - 1);
154 if (lowest < -crm_tolerance * highest) {
155 fail("is not positive semidefinite (lowest eigenvalue " +
156 std::to_string(lowest) + ", highest " + std::to_string(highest) + ")");
157 }
158}
159
160ChargeResponse ChargeResponse::Permuted(const std::vector<Index>& order) const {
161 const Index n = size();
162 if (Index(order.size()) != n) {
163 throw std::runtime_error(
164 "ChargeResponse::Permuted: the charge response of " +
165 std::to_string(n) + " atoms needs all of them, got " +
166 std::to_string(order.size()) +
167 " (a subset of a molecule does not conserve charge)");
168 }
169 std::vector<bool> seen(std::size_t(n), false);
170 for (Index i : order) {
171 if (i < 0 || i >= n || seen[std::size_t(i)]) {
172 throw std::runtime_error(
173 "ChargeResponse::Permuted: the atom order is not a permutation of "
174 "0.." +
175 std::to_string(n - 1));
176 }
177 seen[std::size_t(i)] = true;
178 }
179 ChargeResponse result;
180 result.pi_.resize(n, n);
181 for (Index i = 0; i < n; ++i) {
182 result.elements_.push_back(elements_[std::size_t(order[std::size_t(i)])]);
183 for (Index j = 0; j < n; ++j) {
184 result.pi_(i, j) = pi_(order[std::size_t(i)], order[std::size_t(j)]);
185 }
186 }
187 return result;
188}
189
191 std::string elements;
192 for (const std::string& e : elements_) {
193 elements += (elements.empty() ? "" : " ") + e;
194 }
195 w(elements, "elements");
196 w(pi_, "Pi");
197}
198
200 std::string elements;
201 r(elements, "elements");
202 std::istringstream in(elements);
203 elements_.clear();
204 std::string e;
205 while (in >> e) {
206 elements_.push_back(e);
207 }
208 r(pi_, "Pi");
209 Check("checkpoint");
210}
211
212} // namespace xtp
213} // namespace votca
break string into words
Definition tokenizer.h:72
std::vector< T > ToVector()
store all words in a vector of type T, does type conversion.
Definition tokenizer.h:109
std::vector< std::string > elements_
void WriteToCpt(CheckpointWriter &w) const
ChargeResponse Permuted(const std::vector< Index > &order) const
void ReadFromCpt(CheckpointReader &r)
void Check(const std::string &source)
void LoadFromFile(const std::string &filename)
Reads a .crm file and checks it (see Check). Positions are in bohr.
STL namespace.
std::istream & getline(std::istream &is, std::string &str)
Wrapper for a getline function.
Definition getline.h:35
Charge transport classes.
Definition ERIs.h:28
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26