votca 2026-dev
Loading...
Searching...
No Matches
latticemodel.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 <algorithm>
22#include <cmath>
23#include <limits>
24#include <numeric>
25#include <stdexcept>
26#include <utility>
27
28// VOTCA includes
30
31// Local VOTCA includes
33#include "votca/xtp/segment.h"
34
35namespace votca {
36namespace xtp {
37
39 // 53 random bits -> (0,1]
40 return (double(engine_() >> 11) + 1.0) * (1.0 / 9007199254740992.0);
41}
42
44 if (has_spare_) {
45 has_spare_ = false;
46 return spare_;
47 }
48 double u1 = Uniform();
49 double u2 = Uniform();
50 double r = std::sqrt(-2.0 * std::log(u1));
51 double phi = 2.0 * tools::conv::Pi * u2;
52 spare_ = r * std::sin(phi);
53 has_spare_ = true;
54 return r * std::cos(phi);
55}
56
58 const std::string& name) {
59 if (name == "none") {
60 return Disorder::None;
61 } else if (name == "uncorrelated") {
63 } else if (name == "correlated") {
65 }
66 throw std::runtime_error("LatticeModel: unknown disorder type '" + name +
67 "', use none, uncorrelated or correlated");
68}
69
71 if (std::any_of(opt_.dims.begin(), opt_.dims.end(),
72 [](Index d) { return d < 1; })) {
73 throw std::runtime_error("LatticeModel: dimensions must be positive");
74 }
75 if (opt_.spacing <= 0.0 || opt_.cutoff <= 0.0 || opt_.decay_length <= 0.0) {
76 throw std::runtime_error(
77 "LatticeModel: spacing, cutoff and decay length must be positive");
78 }
79 if (opt_.reorganization <= 0.0) {
80 throw std::runtime_error(
81 "LatticeModel: the reorganization energy must be positive");
82 }
83 box_ = Eigen::Matrix3d::Zero();
84 for (Index d = 0; d < 3; ++d) {
85 box_(d, d) = double(opt_.dims[d]) * opt_.spacing;
86 }
87 // a pair must not see two images of the same site
88 double half_box = 0.5 * box_.diagonal().minCoeff();
89 if (opt_.cutoff >= half_box) {
90 throw std::runtime_error(
91 "LatticeModel: the cutoff must be smaller than half the box");
92 }
93
94 positions_.reserve(opt_.dims[0] * opt_.dims[1] * opt_.dims[2]);
95 for (Index ix = 0; ix < opt_.dims[0]; ++ix) {
96 for (Index iy = 0; iy < opt_.dims[1]; ++iy) {
97 for (Index iz = 0; iz < opt_.dims[2]; ++iz) {
98 positions_.push_back(opt_.spacing * Eigen::Vector3d(double(ix) + 0.5,
99 double(iy) + 0.5,
100 double(iz) + 0.5));
101 }
102 }
103 }
105}
106
108 const Eigen::Vector3d& r1, const Eigen::Vector3d& r2) const {
109 const Eigen::Array3d box = box_.diagonal();
110 const Eigen::Array3d r12 = r2 - r1;
111 return (r12 - box * (r12 / box).round()).matrix();
112}
113
115 auto wrap = [](Index i, Index n) { return ((i % n) + n) % n; };
116 return (wrap(ix, opt_.dims[0]) * opt_.dims[1] + wrap(iy, opt_.dims[1])) *
117 opt_.dims[2] +
118 wrap(iz, opt_.dims[2]);
119}
120
121std::vector<std::pair<double, std::array<Index, 3>>> LatticeModel::Offsets(
122 double radius) const {
123 // distances from site 0 to all others; site j has the coordinates of the
124 // offset (sites are numbered ix, iy, iz with iz fastest)
125 std::vector<std::pair<double, std::array<Index, 3>>> result;
126 for (Index ix = 0; ix < opt_.dims[0]; ++ix) {
127 for (Index iy = 0; iy < opt_.dims[1]; ++iy) {
128 for (Index iz = 0; iz < opt_.dims[2]; ++iz) {
129 Index j = SiteIndex(ix, iy, iz);
130 if (j == 0) {
131 continue;
132 }
133 double d = ShortestConnection(positions_[0], positions_[j]).norm();
134 if (d <= radius) {
135 result.push_back({d, {ix, iy, iz}});
136 }
137 }
138 }
139 }
140 return result;
141}
142
143std::vector<std::vector<Index>> LatticeModel::NearestNeighbours(Index l) const {
144 Index n = NumberOfSites();
145 if (l > n - 1) {
146 throw std::runtime_error(
147 "LatticeModel: more neighbours requested for the correlated energies "
148 "(" +
149 std::to_string(l) + ") than other sites (" + std::to_string(n - 1) +
150 ")");
151 }
152 if (l <= 0) {
153 return std::vector<std::vector<Index>>(n);
154 }
155 // the l-th smallest distance is the same for every site; all offsets up
156 // to it (with ties) are the candidates, sorted per site exactly as a
157 // search over all sites would: distances compared with a tolerance, ties
158 // broken by site index, so the result does not depend on rounding
159 double tol = 1e-9 * opt_.spacing;
160 std::vector<std::pair<double, std::array<Index, 3>>> all =
161 Offsets(std::numeric_limits<double>::max());
162 std::vector<double> distances(all.size());
163 std::transform(all.begin(), all.end(), distances.begin(),
164 [](const auto& o) { return o.first; });
165 std::nth_element(distances.begin(), distances.begin() + (l - 1),
166 distances.end());
167 std::vector<std::pair<double, std::array<Index, 3>>> offsets =
168 Offsets(distances[l - 1] + tol);
169
170 auto less = [tol](const std::pair<double, Index>& a,
171 const std::pair<double, Index>& b) {
172 if (std::abs(a.first - b.first) > tol) {
173 return a.first < b.first;
174 }
175 return a.second < b.second;
176 };
177 std::vector<std::vector<Index>> result(n);
178 std::vector<std::pair<double, Index>> dist(offsets.size());
179 for (Index ix = 0; ix < opt_.dims[0]; ++ix) {
180 for (Index iy = 0; iy < opt_.dims[1]; ++iy) {
181 for (Index iz = 0; iz < opt_.dims[2]; ++iz) {
182 Index i = SiteIndex(ix, iy, iz);
183 for (Index k = 0; k < Index(offsets.size()); ++k) {
184 const std::array<Index, 3>& o = offsets[k].second;
185 Index j = SiteIndex(ix + o[0], iy + o[1], iz + o[2]);
186 dist[k] = {ShortestConnection(positions_[i], positions_[j]).norm(),
187 j};
188 }
189 std::partial_sort(dist.begin(), dist.begin() + l, dist.end(), less);
190 result[i].reserve(l);
191 for (Index m = 0; m < l; ++m) {
192 result[i].push_back(dist[m].second);
193 }
194 }
195 }
196 }
197 return result;
198}
199
201 Index n = NumberOfSites();
202 energies_.assign(n, opt_.mean_energy);
203 if (opt_.disorder == Disorder::None || opt_.sigma == 0.0) {
204 return;
205 }
206 NormalGenerator normal(opt_.seed);
207 if (opt_.disorder == Disorder::Uncorrelated) {
208 // std::generate evaluates the generator in order
209 std::vector<double> noise(energies_.size());
210 std::generate(noise.begin(), noise.end(), [&normal]() { return normal(); });
211 std::transform(energies_.begin(), energies_.end(), noise.begin(),
212 energies_.begin(),
213 [this](double e, double z) { return e + opt_.sigma * z; });
214 return;
215 }
216
217 // Correlated: eps_i = sqrt(eta_a) M^a_i + sqrt(eta_b/l_b) sum_{j in N_b(i)}
218 // M^b_j + sqrt((1-eta_a-eta_b)/l_c) sum_{j in N_c(i)} M^c_j + mean, with
219 // M ~ N(0, sigma^2) and N_l(i) the l sites nearest to i, i itself included
220 // (include_self) or not, so that the variance is sigma^2.
221 double eta_c = 1.0 - opt_.eta_a - opt_.eta_b;
222 if (opt_.eta_a < 0.0 || opt_.eta_b < 0.0 || eta_c < -1e-12) {
223 throw std::runtime_error(
224 "LatticeModel: weights eta_a, eta_b must be non-negative with "
225 "eta_a + eta_b <= 1");
226 }
227 eta_c = std::max(eta_c, 0.0);
228 if (opt_.l_b < 1 || opt_.l_c < 1) {
229 throw std::runtime_error(
230 "LatticeModel: the neighbour numbers l_b and l_c must be positive");
231 }
232 std::vector<double> Ma(n), Mb(n), Mc(n);
233 for (Index i = 0; i < n; ++i) {
234 Ma[i] = opt_.sigma * normal();
235 }
236 for (Index i = 0; i < n; ++i) {
237 Mb[i] = opt_.sigma * normal();
238 }
239 for (Index i = 0; i < n; ++i) {
240 Mc[i] = opt_.sigma * normal();
241 }
242 // with the site itself, only l - 1 other sites are needed
243 const Index self = opt_.include_self ? 1 : 0;
244 std::vector<std::vector<Index>> neighbours =
245 NearestNeighbours(std::max(opt_.l_b, opt_.l_c) - self);
246 double wa = std::sqrt(opt_.eta_a);
247 double wb = std::sqrt(opt_.eta_b / double(opt_.l_b));
248 double wc = std::sqrt(eta_c / double(opt_.l_c));
249 for (Index i = 0; i < n; ++i) {
250 double sb = opt_.include_self ? Mb[i] : 0.0;
251 for (Index m = 0; m < opt_.l_b - self; ++m) {
252 sb += Mb[neighbours[i][m]];
253 }
254 double sc = opt_.include_self ? Mc[i] : 0.0;
255 for (Index m = 0; m < opt_.l_c - self; ++m) {
256 sc += Mc[neighbours[i][m]];
257 }
258 energies_[i] += wa * Ma[i] + wb * sb + wc * sc;
259 }
260}
261
263 if (!top.Segments().empty() || top.NBList().size() > 0) {
264 throw std::runtime_error("LatticeModel: topology must be empty");
265 }
266 top.setBox(box_);
267 top.setStep(0);
268 top.setTime(0.0);
269 Index n = NumberOfSites();
270 // half of the reorganization energy on each segment, so that
271 // reorg12 = U_nX_nN(1) + U_xN_xX(2) = reorg21 = reorganization
272 double half_lambda = 0.5 * opt_.reorganization;
273 for (Index i = 0; i < n; ++i) {
274 Segment& seg = top.AddSegment(opt_.segment_name);
275 seg.push_back(Atom(0, "C", positions_[i]));
276 for (QMStateType s : opt_.states) {
277 seg.setEMpoles(s, energies_[i]);
278 seg.setU_xX_nN(0.0, s);
279 seg.setU_nX_nN(half_lambda, s);
280 seg.setU_xN_xX(half_lambda, s);
281 }
282 }
283 const std::vector<Segment>& segs = top.Segments();
284 QMNBList& nblist = top.NBList();
285 // candidates from the lattice offsets within the cutoff (O(n)); pairs are
286 // added in the order of a search over all i < j, and the cutoff is tested
287 // on the actual pair vector as before
288 std::vector<std::pair<double, std::array<Index, 3>>> offsets =
289 Offsets(opt_.cutoff + 1e-9 * opt_.spacing);
290 std::vector<Index> partners;
291 for (Index i = 0; i < n; ++i) {
292 Index ix = i / (opt_.dims[1] * opt_.dims[2]);
293 Index iy = (i / opt_.dims[2]) % opt_.dims[1];
294 Index iz = i % opt_.dims[2];
295 partners.clear();
296 for (const auto& o : offsets) {
297 Index j = SiteIndex(ix + o.second[0], iy + o.second[1], iz + o.second[2]);
298 if (j > i) {
299 partners.push_back(j);
300 }
301 }
302 std::sort(partners.begin(), partners.end());
303 partners.erase(std::unique(partners.begin(), partners.end()),
304 partners.end());
305 for (Index j : partners) {
306 Eigen::Vector3d r = ShortestConnection(positions_[i], positions_[j]);
307 double dist = r.norm();
308 if (dist >= opt_.cutoff) {
309 continue;
310 }
311 QMPair& pair = nblist.Add(segs[i], segs[j], r);
312 double J2 =
313 opt_.J2 * std::exp(-(dist - opt_.spacing) / opt_.decay_length);
314 for (QMStateType s : opt_.states) {
315 pair.setJeff2(J2, s);
316 }
317 }
318 }
319}
320
321} // namespace xtp
322} // namespace votca
Index size() const
Definition pairlist.h:53
void push_back(const T &atom)
std::vector< double > energies_
Eigen::Vector3d ShortestConnection(const Eigen::Vector3d &r1, const Eigen::Vector3d &r2) const
std::vector< std::vector< Index > > NearestNeighbours(Index l) const
indices of the l nearest other sites of every site, ties broken by index
Index NumberOfSites() const
static Disorder DisorderFromString(const std::string &name)
Index SiteIndex(Index ix, Index iy, Index iz) const
site index of lattice coordinates (taken modulo the dimensions)
void BuildTopology(Topology &top) const
Fill an empty topology with segments, box and neighbour list.
LatticeModel(const Options &opt)
std::vector< Eigen::Vector3d > positions_
std::vector< std::pair< double, std::array< Index, 3 > > > Offsets(double radius) const
QMPair & Add(const Segment &seg1, const Segment &seg2, const Eigen::Vector3d &r)
Definition qmnblist.cc:27
void setU_xX_nN(double dU, QMStateType state)
UxX - UnN.
Definition segment.h:53
void setU_nX_nN(double dU, QMStateType state)
UnX - UnN.
Definition segment.h:57
void setEMpoles(QMStateType state, double energy)
Definition segment.h:87
void setU_xN_xX(double dU, QMStateType state)
UxN - UxX.
Definition segment.h:61
Container for segments and box and atoms.
Definition topology.h:41
Segment & AddSegment(std::string segment_name)
Definition topology.cc:76
void setTime(double time)
Definition topology.h:78
void setBox(const Eigen::Matrix3d &box, csg::BoundaryCondition::eBoxtype boxtype=csg::BoundaryCondition::typeAuto)
Definition topology.cc:85
std::vector< Segment > & Segments()
Definition topology.h:58
QMNBList & NBList()
Definition topology.h:70
void setStep(Index step)
Definition topology.h:76
const double Pi
Definition constants.h:36
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