votca 2026-dev
Loading...
Searching...
No Matches
stochasticnetworkfit.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 <array>
23#include <cmath>
24#include <limits>
25#include <numeric>
26#include <stdexcept>
27
28// Third party includes
29#include <boost/math/special_functions/erf.hpp>
30
31// VOTCA includes
33
34// Local VOTCA includes
36
37namespace votca {
38namespace xtp {
39
40namespace {
41
42double HalfBox(const Eigen::Vector3d& box) { return 0.4999 * box.minCoeff(); }
43
44double Log10J2eV(double J2) {
45 return std::log10(J2 * tools::conv::hrt2ev * tools::conv::hrt2ev);
46}
47
48double Mean(const std::vector<double>& x) {
49 return std::accumulate(x.begin(), x.end(), 0.0) / double(x.size());
50}
51
52double Variance(const std::vector<double>& x, double mean) {
53 double v = 0.0;
54 for (double y : x) {
55 v += (y - mean) * (y - mean);
56 }
57 return v / double(x.size());
58}
59
61std::array<double, 2> Line(const std::vector<double>& x,
62 const std::vector<double>& y) {
63 double mx = Mean(x);
64 double my = Mean(y);
65 double sxy = 0.0;
66 double sxx = 0.0;
67 for (std::size_t k = 0; k < x.size(); ++k) {
68 sxy += (x[k] - mx) * (y[k] - my);
69 sxx += (x[k] - mx) * (x[k] - mx);
70 }
71 double b = sxx > 0.0 ? sxy / sxx : 0.0;
72 return {my - b * mx, b};
73}
74
75StochasticNetworkFit::Curve Bins(double bin, double rmax) {
77 Index n = Index(std::ceil(rmax / bin));
78 for (Index k = 0; k < n; ++k) {
79 c.r.push_back((double(k) + 0.5) * bin);
80 }
81 c.value.assign(n, 0.0);
82 c.count.assign(n, 0.0);
83 return c;
84}
85
86} // namespace
87
88// ------------------------------------------------------------------ data
89
91 const Topology& top, const std::vector<QMStateType>& carriers) {
92 Data d;
93 const Eigen::Matrix3d& box = top.getBox();
94 Eigen::Matrix3d off = box;
95 off.diagonal().setZero();
96 if (off.cwiseAbs().maxCoeff() > 1e-9 * box.diagonal().maxCoeff()) {
97 throw std::runtime_error(
98 "networkfit: the box of the state file must be orthorhombic");
99 }
100 d.box = box.diagonal();
101 d.carriers = carriers;
102 const std::vector<Segment>& segs = top.Segments();
103 const Index n = Index(segs.size());
104 if (n < 2) {
105 throw std::runtime_error("networkfit: need at least two segments");
106 }
107 d.positions.resize(n);
108 d.energies.assign(carriers.size(), std::vector<double>(n));
109 d.U_xX_nN.assign(carriers.size(), 0.0);
110 d.U_nX_nN.assign(carriers.size(), 0.0);
111 d.U_xN_xX.assign(carriers.size(), 0.0);
112 for (Index i = 0; i < n; ++i) {
113 const Segment& seg = segs[i];
114 if (seg.getId() != i) {
115 throw std::runtime_error(
116 "networkfit: segment ids must be consecutive and start at 0");
117 }
118 Eigen::Vector3d p = seg.getPos();
119 for (Index k = 0; k < 3; ++k) {
120 p[k] -= d.box[k] * std::floor(p[k] / d.box[k]);
121 if (p[k] >= d.box[k]) {
122 p[k] = 0.0;
123 }
124 }
125 d.positions[i] = p;
126 for (std::size_t c = 0; c < carriers.size(); ++c) {
127 d.energies[c][i] = seg.getSiteEnergy(carriers[c]);
128 d.U_xX_nN[c] += seg.getU_xX_nN(carriers[c]) / double(n);
129 d.U_nX_nN[c] += seg.getU_nX_nN(carriers[c]) / double(n);
130 d.U_xN_xX[c] += seg.getU_xN_xX(carriers[c]) / double(n);
131 }
132 }
133 for (const QMPair* pair : top.NBList()) {
134 Data::Pair p;
135 p.i = pair->Seg1()->getId();
136 p.j = pair->Seg2()->getId();
137 p.dist = pair->R().norm();
138 for (QMStateType s : carriers) {
139 p.J2.push_back(pair->getJeff2(s));
140 }
141 d.pairs.push_back(std::move(p));
142 }
143 if (d.pairs.empty()) {
144 throw std::runtime_error("networkfit: the state file has no pairs");
145 }
146 return d;
147}
148
150 const StochasticNetwork& net, const std::vector<QMStateType>& carriers,
151 const Eigen::Vector3d& box) {
152 Data d;
153 d.box = box;
154 d.carriers = carriers;
155 d.positions = net.Positions();
156 for (std::size_t c = 0; c < carriers.size(); ++c) {
157 d.energies.push_back(net.SiteEnergies(Index(c)));
158 }
159 d.U_xX_nN.assign(carriers.size(), 0.0);
160 d.U_nX_nN.assign(carriers.size(), 0.0);
161 d.U_xN_xX.assign(carriers.size(), 0.0);
162 for (const StochasticNetwork::Pair& p : net.Pairs()) {
163 d.pairs.push_back({p.i, p.j, p.R.norm(), p.J2});
164 }
165 return d;
166}
167
168// ------------------------------------------------------------ statistics
169
171 const std::vector<Eigen::Vector3d>& points, const Eigen::Vector3d& box) {
172 double radius = std::min(HalfBox(box),
173 2.0 * std::cbrt(box.prod() / double(points.size())));
174 PeriodicCells cells(box, radius, points);
175 std::vector<double> d(points.size());
176 for (Index i = 0; i < Index(points.size()); ++i) {
177 std::vector<Index> nb = cells.Nearest(i, 2);
178 Index j = nb[0] == i ? nb[1] : nb[0];
179 d[i] = cells.Connection(points[i], points[j]).norm();
180 }
181 return d;
182}
183
185 const std::vector<Eigen::Vector3d>& points, const Eigen::Vector3d& box,
186 Index probes, std::uint64_t seed) {
187 double radius = std::min(HalfBox(box),
188 2.0 * std::cbrt(box.prod() / double(points.size())));
189 PeriodicCells cells(box, radius, points);
190 ModelRandom random(seed, 7);
191 std::vector<double> d(probes);
192 for (Index k = 0; k < probes; ++k) {
193 Eigen::Vector3d p;
194 for (Index c = 0; c < 3; ++c) {
195 p[c] = (1.0 - random.Uniform()) * box[c];
196 }
197 Index j = cells.NearestTo(p, 1)[0];
198 d[k] = cells.Connection(p, points[j]).norm();
199 }
200 return d;
201}
202
204 const std::vector<double>& x, const std::vector<double>& r) {
205 std::vector<double> s = x;
206 std::sort(s.begin(), s.end());
207 Curve c;
208 c.r = r;
209 for (double y : r) {
210 c.value.push_back(
211 double(std::upper_bound(s.begin(), s.end(), y) - s.begin()) /
212 double(s.size()));
213 }
214 return c;
215}
216
218 const std::vector<Eigen::Vector3d>& points, const Eigen::Vector3d& box,
219 double bin, double rmax) {
220 Curve c = Bins(bin, rmax);
221 const Index nb = Index(c.r.size());
222 PeriodicCells cells(box, rmax, points);
223 for (Index i = 0; i < Index(points.size()); ++i) {
224 cells.Within(points[i], rmax,
225 [&](Index j, const Eigen::Vector3d&, double dist) {
226 Index b = Index(dist / bin);
227 if (j > i && b < nb) {
228 c.count[b] += 1.0;
229 }
230 });
231 }
232 const double n = double(points.size());
233 const double density = n / box.prod();
234 for (Index b = 0; b < nb; ++b) {
235 double r0 = double(b) * bin;
236 double r1 = r0 + bin;
237 double shell = 4.0 / 3.0 * tools::conv::Pi * (r1 * r1 * r1 - r0 * r0 * r0);
238 c.value[b] = c.count[b] / (shell * density * 0.5 * n);
239 }
240 return c;
241}
242
244 const std::vector<Eigen::Vector3d>& points, const Eigen::Vector3d& box,
245 const std::vector<double>& z, double bin, double rmax,
246 const std::vector<char>& skip) {
247 Curve c = Bins(bin, rmax);
248 const Index nb = Index(c.r.size());
249 PeriodicCells cells(box, rmax, points);
250 auto skipped = [&skip](Index i) { return !skip.empty() && skip[i] != 0; };
251 for (Index i = 0; i < Index(points.size()); ++i) {
252 if (skipped(i)) {
253 continue;
254 }
255 cells.Within(points[i], rmax,
256 [&](Index j, const Eigen::Vector3d&, double dist) {
257 Index b = Index(dist / bin);
258 if (j > i && b < nb && !skipped(j)) {
259 c.count[b] += 1.0;
260 c.value[b] += z[i] * z[j];
261 }
262 });
263 }
264 for (Index b = 0; b < nb; ++b) {
265 if (c.count[b] > 0.0) {
266 c.value[b] /= c.count[b];
267 }
268 }
269 return c;
270}
271
272std::vector<StochasticNetworkFit::Curve> StochasticNetworkFit::Overlaps(
273 const std::vector<Eigen::Vector3d>& points, const Eigen::Vector3d& box,
274 const std::vector<Index>& ls, double bin, double rmax) {
275 const Index n = Index(points.size());
276 const Index lmax = *std::max_element(ls.begin(), ls.end());
277 if (lmax > n) {
278 throw std::runtime_error("networkfit: l larger than the number of sites");
279 }
280 PeriodicCells cells(box, rmax, points);
281 std::vector<Index> nearest(n * lmax);
282#pragma omp parallel for schedule(dynamic, 64)
283 for (Index i = 0; i < n; ++i) {
284 std::vector<Index> nb = cells.Nearest(i, lmax);
285 std::copy(nb.begin(), nb.end(), nearest.begin() + i * lmax);
286 }
287 Curve bins = Bins(bin, rmax);
288 const Index nbins = Index(bins.r.size());
289 std::vector<Curve> result(ls.size(), bins);
290 std::vector<Index> rank(n, -1);
291 std::vector<double> hist(lmax);
292 for (Index i = 0; i < n; ++i) {
293 for (Index k = 0; k < lmax; ++k) {
294 rank[nearest[i * lmax + k]] = k;
295 }
296 cells.Within(points[i], rmax,
297 [&](Index j, const Eigen::Vector3d&, double dist) {
298 Index b = Index(dist / bin);
299 if (j <= i || b >= nbins) {
300 return;
301 }
302 // a common neighbour with ranks ri, rj belongs to both
303 // sets of size l if max(ri, rj) < l
304 std::fill(hist.begin(), hist.end(), 0.0);
305 for (Index k = 0; k < lmax; ++k) {
306 Index ri = rank[nearest[j * lmax + k]];
307 if (ri >= 0) {
308 hist[std::max(ri, k)] += 1.0;
309 }
310 }
311 std::partial_sum(hist.begin(), hist.end(), hist.begin());
312 for (std::size_t q = 0; q < ls.size(); ++q) {
313 Curve& c = result[q];
314 c.count[b] += 1.0;
315 c.value[b] += hist[ls[q] - 1] / double(ls[q]);
316 }
317 });
318 for (Index k = 0; k < lmax; ++k) {
319 rank[nearest[i * lmax + k]] = -1;
320 }
321 }
322 for (Curve& c : result) {
323 for (Index b = 0; b < nbins; ++b) {
324 if (c.count[b] > 0.0) {
325 c.value[b] /= c.count[b];
326 }
327 }
328 }
329 return result;
330}
331
332double StochasticNetworkFit::FitWeights(const Curve& kappa, const Curve& Ob,
333 const Curve& Oc, double& w_b,
334 double& w_c) {
335 // normal equations of the weighted problem
336 double bb = 0.0, bc = 0.0, cc = 0.0, bk = 0.0, ck = 0.0, n = 0.0;
337 for (std::size_t q = 0; q < kappa.r.size(); ++q) {
338 double w = kappa.count.empty() ? 1.0 : kappa.count[q];
339 if (w <= 0.0) {
340 continue;
341 }
342 double b = Ob.value[q];
343 double c = Oc.value[q];
344 double k = kappa.value[q];
345 bb += w * b * b;
346 bc += w * b * c;
347 cc += w * c * c;
348 bk += w * b * k;
349 ck += w * c * k;
350 n += w;
351 }
352 if (n <= 0.0) {
353 throw std::runtime_error("networkfit: no pairs for the correlation fit");
354 }
355 // the weighted mean square, summed directly (the expansion in the normal
356 // equation sums cancels to rounding errors of order 1e-17 near a perfect
357 // fit)
358 auto cost = [&](double x, double y) {
359 double sum = 0.0;
360 for (std::size_t q = 0; q < kappa.r.size(); ++q) {
361 double w = kappa.count.empty() ? 1.0 : kappa.count[q];
362 if (w <= 0.0) {
363 continue;
364 }
365 double d = x * Ob.value[q] + y * Oc.value[q] - kappa.value[q];
366 sum += w * d * d;
367 }
368 return sum / n;
369 };
370 auto clamp01 = [](double x) { return std::min(1.0, std::max(0.0, x)); };
371 std::vector<std::array<double, 2>> cand;
372 cand.push_back({0.0, 0.0});
373 double det = bb * cc - bc * bc;
374 if (det > 1e-14 * bb * cc) {
375 cand.push_back({(bk * cc - ck * bc) / det, (ck * bb - bk * bc) / det});
376 }
377 if (bb > 0.0) {
378 cand.push_back({clamp01(bk / bb), 0.0});
379 }
380 if (cc > 0.0) {
381 cand.push_back({0.0, clamp01(ck / cc)});
382 }
383 // on w_b + w_c = 1: w_b = t, w_c = 1 - t
384 double a2 = bb - 2.0 * bc + cc;
385 if (a2 > 0.0) {
386 double t = clamp01((bk - ck - bc + cc) / a2);
387 cand.push_back({t, 1.0 - t});
388 }
389 double best = std::numeric_limits<double>::infinity();
390 for (const auto& x : cand) {
391 if (x[0] < 0.0 || x[1] < 0.0 || x[0] + x[1] > 1.0 + 1e-12) {
392 continue;
393 }
394 double f = cost(x[0], x[1]);
395 if (f < best) {
396 best = f;
397 w_b = x[0];
398 w_c = x[1];
399 }
400 }
401 return std::max(0.0, best);
402}
403
405 return -std::sqrt(2.0) * boost::math::erfc_inv(2.0 * u);
406}
407
409 double bin,
410 double rmax) {
411 Curve c = Bins(bin, rmax);
412 for (const Data::Pair& p : data.pairs) {
413 Index b = Index(p.dist / bin);
414 if (b < Index(c.r.size())) {
415 c.value[b] += 2.0 / double(data.positions.size());
416 c.count[b] += 1.0;
417 }
418 }
419 return c;
420}
421
423 Index carrier,
424 double bin,
425 double rmax) {
426 Curve c = Bins(bin, rmax);
427 for (const Data::Pair& p : data.pairs) {
428 Index b = Index(p.dist / bin);
429 if (b < Index(c.r.size()) && p.J2[carrier] > 0.0) {
430 c.value[b] += Log10J2eV(p.J2[carrier]);
431 c.count[b] += 1.0;
432 }
433 }
434 for (std::size_t b = 0; b < c.r.size(); ++b) {
435 if (c.count[b] > 0.0) {
436 c.value[b] /= c.count[b];
437 }
438 }
439 return c;
440}
441
442// ------------------------------------------------------------------- fit
443
445 const Settings& settings)
446 : data_(data), settings_(settings) {
447 if (settings_.carriers.empty()) {
448 settings_.carriers = data_.carriers;
449 }
450 if (settings_.carriers != data_.carriers) {
451 throw std::runtime_error(
452 "networkfit: the data were read for other carriers");
453 }
454 if (!(settings_.bin > 0.0) || settings_.realizations <= 0) {
455 throw std::runtime_error("networkfit: bin and realizations must be > 0");
456 }
457}
458
460 const Index n = Index(data_.positions.size());
461 const Eigen::Vector3d& box = data_.box;
462 const Index probes = std::max(Index(4000), n);
463 const std::uint64_t probe_seed = 4711;
464 PointFit fit;
465 std::vector<double> nn = NearestDistances(data_.positions, box);
466 std::vector<double> contact =
467 ContactDistances(data_.positions, box, probes, probe_seed);
468 const double step = 0.2 * settings_.bin;
469 auto grid = [step](double rmax) {
470 std::vector<double> r;
471 for (double x = 0.0; x <= rmax; x += step) {
472 r.push_back(x);
473 }
474 return r;
475 };
476 std::vector<double> rD = grid(1.5 * *std::max_element(nn.begin(), nn.end()));
477 std::vector<double> rH =
478 grid(1.5 * *std::max_element(contact.begin(), contact.end()));
479 std::vector<double> sorted = nn;
480 std::sort(sorted.begin(), sorted.end());
481 const double q01 = sorted[std::size_t(0.01 * double(n - 1))];
482 const double q50 = sorted[std::size_t(0.5 * double(n - 1))];
483 const double rg = std::min(HalfBox(box), 4.0 * q50);
484 fit.D = Cdf(nn, rD);
485 fit.H = Cdf(contact, rH);
486 fit.g = PairCorrelation(data_.positions, box, settings_.bin, rg);
487
489 base.candidates = settings_.candidates;
490 base.max_iterations = settings_.max_iterations;
491 auto process = [&base](const std::array<double, 3>& x) {
493 pp.hard_core = std::abs(x[0]);
494 pp.radius_mean = std::abs(x[1]);
495 pp.radius_variance = pp.radius_mean > 0.0 ? x[2] * x[2] : 0.0;
496 return pp;
497 };
498 auto msd = [](const Curve& a, const Curve& b) {
499 double s = 0.0;
500 for (std::size_t k = 0; k < a.value.size(); ++k) {
501 s += (a.value[k] - b.value[k]) * (a.value[k] - b.value[k]);
502 }
503 return s / double(a.value.size());
504 };
505 auto evaluate = [&](const std::array<double, 3>& x, PointFit* store) {
506 StochasticNetwork::PointProcess pp = process(x);
507 Curve D = fit.D, H = fit.H, g = fit.g;
508 for (Curve* c : {&D, &H, &g}) {
509 std::fill(c->value.begin(), c->value.end(), 0.0);
510 }
511 const Index nr = settings_.realizations;
512 std::vector<std::array<Curve, 3>> curves(nr);
513 std::vector<char> saturated(nr, 0);
514#pragma omp parallel for schedule(dynamic, 1)
515 for (Index k = 0; k < nr; ++k) {
516 std::vector<Eigen::Vector3d> pos;
517 ModelRandom random(settings_.seed, 100 + std::uint64_t(k));
518 try {
519 std::vector<double> radii;
520 StochasticNetwork::SamplePoints(pp, box, n, random, pos, radii);
521 } catch (const std::runtime_error&) {
522 saturated[k] = 1; // radii too large
523 continue;
524 }
525 curves[k] = {Cdf(NearestDistances(pos, box), rD),
526 Cdf(ContactDistances(pos, box, probes, probe_seed), rH),
527 PairCorrelation(pos, box, settings_.bin, rg)};
528 }
529 if (std::any_of(saturated.begin(), saturated.end(),
530 [](char s) { return s != 0; })) {
531 return 1e3;
532 }
533 for (Index k = 0; k < nr; ++k) {
534 const Curve& d = curves[k][0];
535 const Curve& h = curves[k][1];
536 const Curve& gg = curves[k][2];
537 double f = 1.0 / double(nr);
538 for (std::size_t q = 0; q < D.value.size(); ++q) {
539 D.value[q] += f * d.value[q];
540 }
541 for (std::size_t q = 0; q < H.value.size(); ++q) {
542 H.value[q] += f * h.value[q];
543 }
544 for (std::size_t q = 0; q < g.value.size(); ++q) {
545 g.value[q] += f * gg.value[q];
546 }
547 }
548 if (store != nullptr) {
549 store->D_model = D;
550 store->H_model = H;
551 store->g_model = g;
552 }
553 return msd(D, fit.D) + msd(H, fit.H) + 0.25 * msd(g, fit.g);
554 };
555
556 // coarse grid, then Nelder-Mead
557 std::array<double, 3> best{0.0, 0.0, 0.0};
558 double fbest = std::numeric_limits<double>::infinity();
559 Index evaluations = 0;
560 for (double a : {0.6, 0.8, 1.0}) {
561 for (double b : {0.25, 0.5, 0.75, 1.0}) {
562 for (double c : {0.05, 0.15}) {
563 double rh = a * q01;
564 std::array<double, 3> x{rh, b * std::max(q50 - rh, 0.1 * q50), c * q50};
565 double f = evaluate(x, nullptr);
566 ++evaluations;
567 if (f < fbest) {
568 fbest = f;
569 best = x;
570 }
571 }
572 }
573 }
574 std::array<std::array<double, 3>, 4> s;
575 std::array<double, 4> fs;
576 s[0] = best;
577 fs[0] = fbest;
578 for (Index k = 0; k < 3; ++k) {
579 s[k + 1] = best;
580 s[k + 1][k] += 0.1 * q50;
581 fs[k + 1] = evaluate(s[k + 1], nullptr);
582 ++evaluations;
583 }
584 auto combine = [](const std::array<double, 3>& a,
585 const std::array<double, 3>& b, double t) {
586 // a + t (b - a)
587 return std::array<double, 3>{a[0] + t * (b[0] - a[0]),
588 a[1] + t * (b[1] - a[1]),
589 a[2] + t * (b[2] - a[2])};
590 };
591 while (evaluations < settings_.max_evaluations) {
592 std::array<Index, 4> o{0, 1, 2, 3};
593 std::sort(o.begin(), o.end(),
594 [&fs](Index a, Index b) { return fs[a] < fs[b]; });
595 double size = 0.0;
596 for (Index k = 1; k < 4; ++k) {
597 for (Index d = 0; d < 3; ++d) {
598 size = std::max(size, std::abs(s[o[k]][d] - s[o[0]][d]));
599 }
600 }
601 if (size < 1e-3 * q50) {
602 break;
603 }
604 std::array<double, 3> centroid{0.0, 0.0, 0.0};
605 for (Index k = 0; k < 3; ++k) {
606 for (Index d = 0; d < 3; ++d) {
607 centroid[d] += s[o[k]][d] / 3.0;
608 }
609 }
610 const Index w = o[3];
611 std::array<double, 3> xr = combine(centroid, s[w], -1.0);
612 double fr = evaluate(xr, nullptr);
613 ++evaluations;
614 if (fr < fs[o[0]]) {
615 std::array<double, 3> xe = combine(centroid, s[w], -2.0);
616 double fe = evaluate(xe, nullptr);
617 ++evaluations;
618 if (fe < fr) {
619 s[w] = xe;
620 fs[w] = fe;
621 } else {
622 s[w] = xr;
623 fs[w] = fr;
624 }
625 } else if (fr < fs[o[2]]) {
626 s[w] = xr;
627 fs[w] = fr;
628 } else {
629 bool outside = fr < fs[w];
630 std::array<double, 3> xc = combine(centroid, outside ? xr : s[w], 0.5);
631 double fc = evaluate(xc, nullptr);
632 ++evaluations;
633 if (fc < std::min(fr, fs[w])) {
634 s[w] = xc;
635 fs[w] = fc;
636 } else {
637 // shrink towards the best
638 for (Index k = 1; k < 4; ++k) {
639 s[o[k]] = combine(s[o[0]], s[o[k]], 0.5);
640 fs[o[k]] = evaluate(s[o[k]], nullptr);
641 ++evaluations;
642 }
643 }
644 }
645 }
646 Index ib = Index(std::min_element(fs.begin(), fs.end()) - fs.begin());
647 fit.process = process(s[ib]);
648 fit.objective = evaluate(s[ib], &fit);
649 fit.evaluations = evaluations + 1;
650 return fit;
651}
652
654 const double bin = settings_.bin;
655 double dmax = 0.0;
656 for (const Data::Pair& p : data_.pairs) {
657 dmax = std::max(dmax, p.dist);
658 }
659 const Index nb = Index(std::floor(dmax / bin)) + 1;
660 const double rmax = double(nb) * bin;
661 if (!(rmax < HalfBox(data_.box))) {
662 throw std::runtime_error(
663 "networkfit: the longest pair is longer than half the box; the "
664 "connection probability cannot be sampled");
665 }
666 std::vector<double> pairs(nb, 0.0), all(nb, 0.0);
667 PeriodicCells cells(data_.box, rmax, data_.positions);
668 // distances of pairs and of all site pairs from the same positions
669 for (const Data::Pair& p : data_.pairs) {
670 double d =
671 cells.Connection(data_.positions[p.i], data_.positions[p.j]).norm();
672 pairs[std::min(Index(d / bin), nb - 1)] += 1.0;
673 }
674 for (Index i = 0; i < Index(data_.positions.size()); ++i) {
675 cells.Within(data_.positions[i], rmax,
676 [&](Index j, const Eigen::Vector3d&, double dist) {
677 Index b = Index(dist / bin);
678 if (j > i && b < nb) {
679 all[b] += 1.0;
680 }
681 });
682 }
683 Curve c;
684 Index first = 0;
685 while (first < nb && all[first] == 0.0) {
686 ++first;
687 }
688 for (Index b = 0; b < nb; ++b) {
689 double f;
690 if (b < first) {
691 f = std::min(1.0, pairs[first] / all[first]);
692 } else if (all[b] > 0.0) {
693 f = std::min(1.0, pairs[b] / all[b]);
694 } else {
695 f = c.value.back();
696 }
697 c.r.push_back((double(b) + 0.5) * bin);
698 c.value.push_back(f);
699 c.count.push_back(all[b]);
700 }
701 c.r.push_back(rmax);
702 c.value.push_back(0.0);
703 c.count.push_back(0.0);
704 return c;
705}
706
708 Index carrier, std::vector<double>& standardized) const {
709 CouplingFit fit;
710 const double nm = tools::conv::bohr2nm;
711 std::vector<double> x, y;
712 std::vector<Index> index;
713 for (std::size_t k = 0; k < data_.pairs.size(); ++k) {
714 const Data::Pair& p = data_.pairs[k];
715 if (p.J2[carrier] > 0.0) {
716 x.push_back(p.dist * nm);
717 y.push_back(Log10J2eV(p.J2[carrier]));
718 index.push_back(Index(k));
719 } else {
720 ++fit.excluded;
721 }
722 }
723 fit.used = Index(x.size());
724 standardized.assign(data_.pairs.size(),
725 std::numeric_limits<double>::quiet_NaN());
726 if (fit.used < 3) {
727 throw std::runtime_error("networkfit: carrier " +
728 data_.carriers[carrier].ToString() +
729 " has fewer than three nonzero couplings");
730 }
731 std::array<double, 2> m = Line(x, y);
732 std::vector<double> e2(x.size());
733 for (std::size_t k = 0; k < x.size(); ++k) {
734 double e = y[k] - m[0] - m[1] * x[k];
735 e2[k] = e * e;
736 }
737 std::array<double, 2> v = Line(x, e2);
738 double xmin = *std::min_element(x.begin(), x.end());
739 double xmax = *std::max_element(x.begin(), x.end());
740 if (!(v[0] + v[1] * xmin > 0.0) || !(v[0] + v[1] * xmax > 0.0)) {
741 v = {Mean(e2), 0.0}; // constant variance
742 }
743 fit.model.mean_offset = m[0];
744 fit.model.mean_slope = m[1];
745 if (Mean(e2) < 1e-12) {
746 // couplings are a function of the distance (model systems)
747 fit.model.var_offset = 0.0;
748 fit.model.var_slope = 0.0;
749 return fit;
750 }
751 fit.model.var_offset = v[0];
752 fit.model.var_slope = v[1];
753 std::vector<double> u(x.size());
754 for (std::size_t k = 0; k < x.size(); ++k) {
755 double var = std::max(v[0] + v[1] * x[k], 1e-300);
756 u[k] = (y[k] - m[0] - m[1] * x[k]) / std::sqrt(var);
757 standardized[index[k]] = u[k];
758 }
759 double mu = Mean(u);
760 double var = Variance(u, mu);
761 double m3 = 0.0, m4 = 0.0;
762 for (double z : u) {
763 m3 += std::pow(z - mu, 3);
764 m4 += std::pow(z - mu, 4);
765 }
766 m3 /= double(u.size());
767 m4 /= double(u.size());
768 fit.skewness = var > 0.0 ? m3 / std::pow(var, 1.5) : 0.0;
769 fit.excess_kurtosis = var > 0.0 ? m4 / (var * var) - 3.0 : 0.0;
770 Index K = std::min(settings_.quantiles, fit.used / 10);
771 if (K >= 2) {
772 std::sort(u.begin(), u.end());
773 for (Index k = 0; k < K; ++k) {
774 double pos = (double(k) + 0.5) / double(K) * double(u.size()) - 0.5;
775 pos = std::min(std::max(pos, 0.0), double(u.size() - 1));
776 std::size_t lo = std::size_t(std::floor(pos));
777 std::size_t hi = std::min(lo + 1, u.size() - 1);
778 double t = pos - double(lo);
779 fit.model.residual_quantiles.push_back(u[lo] + t * (u[hi] - u[lo]));
780 }
781 }
782 return fit;
783}
784
786 Result res;
787 const Index n = Index(data_.positions.size());
788 const std::size_t ncar = data_.carriers.size();
790 opt.box = data_.box;
791 opt.sites = n;
792 opt.seed = settings_.seed;
793
794 if (settings_.fit_points) {
795 res.points = FitPoints();
796 } else {
797 res.points.process = settings_.points;
798 }
799 opt.points = res.points.process;
800 opt.points.candidates = settings_.candidates;
801 opt.points.max_iterations = settings_.max_iterations;
802
804 opt.connection_r = res.connection.r;
805 opt.connection_p = res.connection.value;
806
807 // site energies
808 double rc = settings_.correlation_cutoff > 0.0
809 ? std::min(settings_.correlation_cutoff, HalfBox(data_.box))
810 : HalfBox(data_.box);
811 // trap sites are left out of the bulk statistics
812 std::vector<char> trap_site(n, 0);
813 for (const TrapInput& t : settings_.traps) {
814 if (t.carrier < 0 || t.carrier >= Index(ncar) || t.sites.empty()) {
815 throw std::runtime_error("networkfit: invalid trap input");
816 }
817 for (Index i : t.sites) {
818 if (i < 0 || i >= n) {
819 throw std::runtime_error("networkfit: trap site out of range");
820 }
821 trap_site[i] = 1;
822 }
823 }
824 const Index nbulk =
825 n - Index(std::count(trap_site.begin(), trap_site.end(), 1));
826 if (nbulk < 2) {
827 throw std::runtime_error("networkfit: no bulk sites left");
828 }
829 std::vector<std::vector<double>> bulk_energies(ncar);
830 for (std::size_t c = 0; c < ncar; ++c) {
831 for (Index i = 0; i < n; ++i) {
832 if (!trap_site[i]) {
833 bulk_energies[c].push_back(data_.energies[c][i]);
834 }
835 }
836 }
837 std::vector<std::vector<double>> z(ncar);
838 bool any = false;
839 for (std::size_t c = 0; c < ncar; ++c) {
840 EnergyFit& e = res.energies.emplace_back();
841 e.mean = Mean(bulk_energies[c]);
842 e.sigma = std::sqrt(Variance(bulk_energies[c], e.mean));
843 // below 1 micro-eV the energies are equal up to rounding
844 e.fitted = e.sigma > 1e-6 * tools::conv::ev2hrt;
845 if (!e.fitted) {
846 e.sigma = 0.0;
847 }
848 if (e.fitted) {
849 any = true;
850 for (Index i = 0; i < n; ++i) {
851 z[c].push_back(
852 trap_site[i] ? 0.0 : (data_.energies[c][i] - e.mean) / e.sigma);
853 }
854 }
855 }
856 std::vector<Index> lb, lc, ls;
857 for (Index l : settings_.l_b) {
858 if (l >= 1 && l <= n) {
859 lb.push_back(l);
860 }
861 }
862 for (Index l : settings_.l_c) {
863 if (l >= 1 && l <= n) {
864 lc.push_back(l);
865 }
866 }
867 if (any && (lb.empty() || lc.empty())) {
868 throw std::runtime_error(
869 "networkfit: the l_b and l_c grids have no values in [1, sites]");
870 }
871 std::vector<Curve> overlaps;
872 if (any) {
873 ls = lb;
874 ls.insert(ls.end(), lc.begin(), lc.end());
875 std::sort(ls.begin(), ls.end());
876 ls.erase(std::unique(ls.begin(), ls.end()), ls.end());
877 overlaps = Overlaps(data_.positions, data_.box, ls, settings_.bin, rc);
878 }
879 auto overlap = [&](Index l) -> const Curve& {
880 return overlaps[std::lower_bound(ls.begin(), ls.end(), l) - ls.begin()];
881 };
882 if (ncar > 1 && res.energies[0].fitted && res.energies[1].fitted) {
883 double s = 0.0;
884 for (Index i = 0; i < n; ++i) {
885 s += z[0][i] * z[1][i]; // zero on trap sites
886 }
887 opt.energy_correlation = std::min(1.0, std::max(-1.0, s / double(nbulk)));
888 }
889 // The microscopic kappa is estimated with the sample mean and variance,
890 // which biases it: for a moving average with mean correlation cbar over
891 // all pairs (cbar = (w_a + w_b l_b + w_c l_c) / N), the estimate is about
892 // (kappa - cbar) / (1 - cbar). The fit compares in this form, so that a
893 // large model network has the unbiased kappa.
894 std::vector<std::vector<double>> expected(ncar); // unbiased model kappa
895 double cbar0 = 0.0;
896 for (std::size_t c = 0; c < ncar; ++c) {
897 EnergyFit& e = res.energies[c];
898 if (!e.fitted) {
899 continue;
900 }
901 e.kappa = Correlation(data_.positions, data_.box, z[c], settings_.bin, rc,
902 trap_site);
903 const Index nb = Index(e.kappa.value.size());
904 const double rho = c == 1 ? opt.energy_correlation : 0.0;
905 const double r2 = rho * rho;
906 if (c == 1 && std::abs(rho) > 0.999) {
907 const EnergyFit& f = res.energies[0];
908 e.w_a = f.w_a;
909 e.w_b = f.w_b;
910 e.w_c = f.w_c;
911 e.l_b = f.l_b;
912 e.l_c = f.l_c;
913 expected[c] = expected[0];
914 e.kappa_model = f.kappa_model;
915 e.cbar = f.cbar;
916 e.rms = f.rms;
917 } else {
918 // the second carrier: kappa = rho^2 kappa_1 + (1 - rho^2) kappa_w
919 std::vector<double> base(nb, 0.0);
920 if (r2 > 0.0) {
921 base = expected[0];
922 }
923 auto model = [&](Index b, Index cc, double wb, double wc,
924 std::vector<double>& m) {
925 double wa = std::max(0.0, 1.0 - wb - wc);
926 double cw = (wa + wb * double(b) + wc * double(cc)) / double(nbulk);
927 m.resize(nb);
928 for (Index q = 0; q < nb; ++q) {
929 m[q] = r2 * base[q] + (1.0 - r2) * (wb * overlap(b).value[q] +
930 wc * overlap(cc).value[q]);
931 }
932 return r2 * cbar0 + (1.0 - r2) * cw; // cbar of the model
933 };
934 auto sample_residual = [&](const std::vector<double>& m, double cbar) {
935 double s = 0.0, w = 0.0;
936 for (Index q = 0; q < nb; ++q) {
937 double d = (m[q] - cbar) / (1.0 - cbar) - e.kappa.value[q];
938 s += e.kappa.count[q] * d * d;
939 w += e.kappa.count[q];
940 }
941 return s / w;
942 };
943 double best = std::numeric_limits<double>::infinity();
944 std::vector<double> m;
945 Curve target = e.kappa;
946 e.l_c_scan.r.assign(lc.begin(), lc.end());
947 e.l_c_scan.value.assign(lc.size(),
948 std::numeric_limits<double>::infinity());
949 for (Index b : lb) {
950 for (std::size_t ic = 0; ic < lc.size(); ++ic) {
951 const Index cc = lc[ic];
952 if (cc <= b) {
953 continue;
954 }
955 double wb = 0.0, wc = 0.0, cbar = 0.0;
956 for (Index iter = 0; iter < 8; ++iter) {
957 for (Index q = 0; q < nb; ++q) {
958 double unbiased = cbar + (1.0 - cbar) * e.kappa.value[q];
959 target.value[q] = (unbiased - r2 * base[q]) / (1.0 - r2);
960 }
961 FitWeights(target, overlap(b), overlap(cc), wb, wc);
962 cbar = model(b, cc, wb, wc, m);
963 }
964 double f = sample_residual(m, cbar);
965 e.l_c_scan.value[ic] = std::min(e.l_c_scan.value[ic], std::sqrt(f));
966 if (f < best) {
967 best = f;
968 e.l_b = b;
969 e.l_c = cc;
970 e.w_b = wb;
971 e.w_c = wc;
972 }
973 }
974 }
975 if (best == std::numeric_limits<double>::infinity()) {
976 throw std::runtime_error(
977 "networkfit: no l_c larger than l_b in the grids");
978 }
979 e.w_a = std::max(0.0, 1.0 - e.w_b - e.w_c);
980 double cbar = model(e.l_b, e.l_c, e.w_b, e.w_c, expected[c]);
981 if (c == 0) {
982 cbar0 = cbar;
983 }
984 e.cbar = cbar;
985 e.l_b_at_edge = lb.size() > 1 && e.l_b == lb.back();
986 e.l_c_at_edge = lc.size() > 1 && e.l_c == lc.back();
987 e.rms = std::sqrt(sample_residual(expected[c], cbar));
988 e.kappa_model = e.kappa;
989 for (Index q = 0; q < nb; ++q) {
990 e.kappa_model.value[q] = (expected[c][q] - cbar) / (1.0 - cbar);
991 }
992 }
993 }
994
995 for (EnergyFit& e : res.energies) {
996 e.sigma_model = e.fitted ? e.sigma / std::sqrt(1.0 - e.cbar) : 0.0;
997 }
998
999 // couplings
1000 std::vector<std::vector<double>> standardized(ncar);
1001 for (std::size_t c = 0; c < ncar; ++c) {
1002 res.couplings.push_back(FitCoupling(Index(c), standardized[c]));
1003 }
1004 if (ncar > 1) {
1005 std::vector<std::array<double, 2>> both;
1006 for (std::size_t k = 0; k < data_.pairs.size(); ++k) {
1007 if (std::isfinite(standardized[0][k]) &&
1008 std::isfinite(standardized[1][k])) {
1009 both.push_back({standardized[0][k], standardized[1][k]});
1010 }
1011 }
1012 if (both.size() > 2) {
1013 // normal scores of the ranks
1014 std::vector<std::vector<double>> score(2,
1015 std::vector<double>(both.size()));
1016 for (Index a = 0; a < 2; ++a) {
1017 std::vector<std::size_t> o(both.size());
1018 std::iota(o.begin(), o.end(), 0);
1019 std::sort(o.begin(), o.end(), [&](std::size_t p, std::size_t q) {
1020 return both[p][a] < both[q][a];
1021 });
1022 for (std::size_t r = 0; r < o.size(); ++r) {
1023 score[a][o[r]] =
1024 NormalQuantile((double(r) + 0.5) / double(both.size()));
1025 }
1026 }
1027 double sxy = 0.0, sxx = 0.0, syy = 0.0;
1028 for (std::size_t k = 0; k < both.size(); ++k) {
1029 sxy += score[0][k] * score[1][k];
1030 sxx += score[0][k] * score[0][k];
1031 syy += score[1][k] * score[1][k];
1032 }
1034 std::min(1.0, std::max(-1.0, sxy / std::sqrt(sxx * syy)));
1035 }
1036 }
1037
1038 for (std::size_t c = 0; c < ncar; ++c) {
1040 car.state = data_.carriers[c];
1041 const EnergyFit& e = res.energies[c];
1042 // the model energies are the site energies without the internal energy
1043 car.U_xX_nN = data_.U_xX_nN[c];
1044 car.U_nX_nN = data_.U_nX_nN[c];
1045 car.U_xN_xX = data_.U_xN_xX[c];
1046 car.energy.mean = e.mean - car.U_xX_nN;
1047 car.energy.sigma = e.sigma_model;
1048 car.energy.w_a = e.fitted ? e.w_a : 1.0;
1049 car.energy.w_b = e.fitted ? e.w_b : 0.0;
1050 car.energy.l_b = e.l_b;
1051 car.energy.l_c = e.l_c;
1052 car.coupling = res.couplings[c].model;
1053 opt.carriers.push_back(car);
1054 }
1055 // trap templates: energies relative to the bulk mean, all carriers
1056 for (const TrapInput& t : settings_.traps) {
1058 trap.carrier = t.carrier;
1059 trap.density = 1.0 / double(n);
1060 trap.release_rate = t.release_rate;
1061 for (Index i : t.sites) {
1062 std::vector<double> o(ncar);
1063 for (std::size_t c = 0; c < ncar; ++c) {
1064 o[c] = data_.energies[c][i] - res.energies[c].mean;
1065 }
1066 trap.offsets.push_back(o);
1067 }
1068 opt.traps.push_back(trap);
1069 }
1070 return res;
1071}
1072
1073} // namespace xtp
1074} // namespace votca
const Eigen::Vector3d & getPos() const
Platform independent random numbers for the stochastic models: mt19937_64 with self-written transform...
Periodic orthorhombic cell list for neighbour searches.
std::vector< Index > NearestTo(const Eigen::Vector3d &p, Index l) const
indices of the l points nearest to any position p
Eigen::Vector3d Connection(const Eigen::Vector3d &r1, const Eigen::Vector3d &r2) const
shortest periodic connection r2 - r1
void Within(const Eigen::Vector3d &p, double radius, const std::function< void(Index, const Eigen::Vector3d &, double)> &f) const
std::vector< Index > Nearest(Index i, Index l) const
double getU_nX_nN(QMStateType state) const
Definition segment.h:71
double getU_xX_nN(QMStateType state) const
Definition segment.h:67
double getSiteEnergy(QMStateType state) const
Definition segment.h:79
double getU_xN_xX(QMStateType state) const
Definition segment.h:75
static Curve PairCorrelation(const std::vector< Eigen::Vector3d > &points, const Eigen::Vector3d &box, double bin, double rmax)
pair correlation function in bins of width bin up to rmax
static Curve Cdf(const std::vector< double > &x, const std::vector< double > &r)
empirical distribution function on a grid
static std::vector< double > NearestDistances(const std::vector< Eigen::Vector3d > &points, const Eigen::Vector3d &box)
nearest neighbour distances
static Curve PairsPerSite(const Data &data, double bin, double rmax)
pairs per site in distance bins (connected pairs of the data)
static double NormalQuantile(double u)
inverse of the standard normal distribution function
static std::vector< double > ContactDistances(const std::vector< Eigen::Vector3d > &points, const Eigen::Vector3d &box, Index probes, std::uint64_t seed)
distances of probes random positions to the nearest point
static Data FromNetwork(const StochasticNetwork &net, const std::vector< QMStateType > &carriers, const Eigen::Vector3d &box)
the same data from a model network (validation)
StochasticNetworkFit(const Data &data, const Settings &settings)
static std::vector< Curve > Overlaps(const std::vector< Eigen::Vector3d > &points, const Eigen::Vector3d &box, const std::vector< Index > &l, double bin, double rmax)
overlap curves O_l(r) for the given l
CouplingFit FitCoupling(Index carrier, std::vector< double > &standardized) const
static double FitWeights(const Curve &kappa, const Curve &Ob, const Curve &Oc, double &w_b, double &w_c)
static Curve MeanLog10J2(const Data &data, Index carrier, double bin, double rmax)
mean of log10(J^2/eV^2) in distance bins
static Curve Correlation(const std::vector< Eigen::Vector3d > &points, const Eigen::Vector3d &box, const std::vector< double > &z, double bin, double rmax, const std::vector< char > &skip={})
static Data FromTopology(const Topology &top, const std::vector< QMStateType > &carriers)
Stochastic network model of a molecular charge transport network (Baumeier, Stenzel,...
static Index SamplePoints(const PointProcess &pp, const Eigen::Vector3d &box, Index sites, ModelRandom &random, std::vector< Eigen::Vector3d > &positions, std::vector< double > &radii)
const std::vector< double > & SiteEnergies(Index c) const
site energies of carrier c (index into Options::carriers)
const std::vector< Eigen::Vector3d > & Positions() const
const std::vector< Pair > & Pairs() const
Container for segments and box and atoms.
Definition topology.h:41
std::vector< Segment > & Segments()
Definition topology.h:58
const Eigen::Matrix3d & getBox() const
Definition topology.h:64
QMNBList & NBList()
Definition topology.h:70
const double ev2hrt
Definition constants.h:54
const double bohr2nm
Definition constants.h:46
const double hrt2ev
Definition constants.h:53
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
std::vector< Eigen::Vector3d > positions
std::vector< std::vector< double > > energies
Curve kappa_model
the model on the microscopic sites, in the same (sample) form
std::vector< Trap > traps
trap templates, inserted after the Gaussian energies
std::vector< double > connection_r
connection probability table: f at distances r (ascending, bohr)
double candidates
candidates per iteration in units of the number of sites
std::vector< std::vector< double > > offsets
offsets[k][c]: site k (deepest first), carrier c, Hartree