faunus
tabulate.h
1 #pragma once
2 
3 #include <iostream>
4 #include <cstdlib>
5 #include <vector>
6 #include <functional>
7 #include <algorithm>
8 #include <cmath>
9 #include <memory>
10 #include <concepts>
11 
12 namespace Faunus {
13 
25 namespace Tabulate {
26 
27 /* base class for all tabulators - no dependencies */
28 template <std::floating_point T = double> class TabulatorBase
29 {
30  protected:
31  T utol = 1e-5, ftol = -1, umaxtol = -1, fmaxtol = -1;
32  T numdr = 0.0001; // dr for derivative evaluation
33 
34  // First derivative with respect to x
35  T f1(std::function<T(T)> f, T x) const
36  {
37  return (f(x + numdr * 0.5) - f(x - numdr * 0.5)) / (numdr);
38  }
39 
40  // Second derivative with respect to x
41  T f2(std::function<T(T)> f, T x) const
42  {
43  return (f1(f, x + numdr * 0.5) - f1(f, x - numdr * 0.5)) / (numdr);
44  }
45 
46  void check() const
47  {
48  if (ftol != -1 && ftol <= 0.0) {
49  std::cerr << "ftol=" << ftol << " too small\n" << std::endl;
50  abort();
51  }
52  if (umaxtol != -1 && umaxtol <= 0.0) {
53  std::cerr << "umaxtol=" << umaxtol << " too small\n" << std::endl;
54  abort();
55  }
56  if (fmaxtol != -1 && fmaxtol <= 0.0) {
57  std::cerr << "fmaxtol=" << fmaxtol << " too small\n" << std::endl;
58  abort();
59  }
60  }
61 
62  public:
63  struct data
64  {
65  std::vector<T> r2; // r2 for intervals
66  std::vector<T> c; // c for coefficents
67  T rmin2 = 0, rmax2 = 0; // useful to save these with table
68 
69  bool empty() const { return r2.empty() && c.empty(); }
70 
71  inline size_t numKnots() const { return r2.size(); }
72  };
73 
74  void setTolerance(T _utol, T _ftol = -1, T _umaxtol = -1, T _fmaxtol = -1)
75  {
76  utol = _utol;
77  ftol = _ftol;
78  umaxtol = _umaxtol;
79  fmaxtol = _fmaxtol;
80  }
81 
82  void setNumdr(T _numdr) { numdr = _numdr; }
83 };
84 
95 template <std::floating_point T = double> class Andrea : public TabulatorBase<T>
96 {
97  private:
98  typedef TabulatorBase<T> base; // for convenience
99  int mngrid = 1200; // Max number of controlpoints
100  int ndr = 100; // Max number of trials to decr dr
101  T drfrac = 0.9; // Multiplicative factor to decr dr
102 
103  std::vector<T> SetUBuffer(T, T zlow, T, T zupp, T u0low, T u1low, T u2low, T u0upp, T u1upp,
104  T u2upp)
105  {
106 
107  // Zero potential and force return no coefficients
108  if (std::fabs(u0low) < 1e-9)
109  if (std::fabs(u1low) < 1e-9)
110  return {0, 0, 0, 0, 0, 0, 0};
111 
112  T dz1 = zupp - zlow;
113  T dz2 = dz1 * dz1;
114  T dz3 = dz2 * dz1;
115  T w0low = u0low;
116  T w1low = u1low;
117  T w2low = u2low;
118  T w0upp = u0upp;
119  T w1upp = u1upp;
120  T w2upp = u2upp;
121  T c0 = w0low;
122  T c1 = w1low;
123  T c2 = w2low * 0.5;
124  T a = 6 * (w0upp - c0 - c1 * dz1 - c2 * dz2) / dz3;
125  T b = 2 * (w1upp - c1 - 2 * c2 * dz1) / dz2;
126  T c = (w2upp - 2 * c2) / dz1;
127  T c3 = (10 * a - 12 * b + 3 * c) / 6;
128  T c4 = (-15 * a + 21 * b - 6 * c) / (6 * dz1);
129  T c5 = (2 * a - 3 * b + c) / (2 * dz2);
130 
131  return {zlow, c0, c1, c2, c3, c4, c5};
132  }
133 
139  std::vector<bool> CheckUBuffer(std::vector<T>& ubuft, T rlow, T rupp,
140  std::function<T(T)> f) const
141  {
142 
143  // Number of points to control
144  int ncheck = 11;
145  T dr = (rupp - rlow) / (ncheck - 1);
146  std::vector<bool> vb(2, false);
147 
148  for (int i = 0; i < ncheck; i++) {
149  T r1 = rlow + dr * ((T)i);
150  T r2 = r1 * r1;
151  T u0 = f(r2);
152  T u1 = base::f1(f, r2);
153  T dz = r2 - rlow * rlow;
154  T usum = ubuft.at(1) +
155  dz * (ubuft.at(2) +
156  dz * (ubuft.at(3) +
157  dz * (ubuft.at(4) + dz * (ubuft.at(5) + dz * ubuft.at(6)))));
158 
159  T fsum =
160  ubuft.at(2) +
161  dz * (2 * ubuft.at(3) +
162  dz * (3 * ubuft.at(4) + dz * (4 * ubuft.at(5) + dz * (5 * ubuft.at(6)))));
163 
164  if (std::fabs(usum - u0) > base::utol)
165  return vb;
166  if (base::ftol != -1 && std::fabs(fsum - u1) > base::ftol)
167  return vb;
168  if (base::umaxtol != -1 && std::fabs(usum) > base::umaxtol)
169  vb[1] = true;
170  if (base::fmaxtol != -1 && std::fabs(usum) > base::fmaxtol)
171  vb[1] = true;
172  }
173  vb[0] = true;
174  return vb;
175  }
176 
177  public:
184  inline T eval(const typename base::data& d, T r2) const
185  {
186  size_t pos = std::lower_bound(d.r2.begin(), d.r2.end(), r2) - d.r2.begin() - 1;
187  size_t pos6 = 6 * pos;
188  assert((pos6 + 5) < d.c.size());
189  T dz = r2 - d.r2[pos];
190  if constexpr (true) { // loop version
191  T sum = 0;
192 #pragma clang loop vectorize(enable) interleave(enable)
193  for (size_t i = 5; i > 0; i--)
194  sum = dz * (sum + d.c[pos6 + i]);
195  return sum + d.c[pos6];
196  }
197  else // manually unrolled version
198  return d.c[pos6] +
199  dz * (d.c[pos6 + 1] +
200  dz * (d.c[pos6 + 2] +
201  dz * (d.c[pos6 + 3] + dz * (d.c[pos6 + 4] + dz * (d.c[pos6 + 5])))));
202  }
203 
209  T evalDer(const typename base::data& d, T r2) const
210  {
211  size_t pos = std::lower_bound(d.r2.begin(), d.r2.end(), r2) - d.r2.begin() - 1;
212  size_t pos6 = 6 * pos;
213  T dz = r2 - d.r2[pos];
214  return (d.c[pos6 + 1] +
215  dz * (2.0 * d.c[pos6 + 2] +
216  dz * (3.0 * d.c[pos6 + 3] +
217  dz * (4.0 * d.c[pos6 + 4] + dz * (5.0 * d.c[pos6 + 5])))));
218  }
219 
223  typename base::data generate(std::function<T(T)> f, double rmin, double rmax)
224  {
225  rmin = std::sqrt(rmin);
226  rmax = std::sqrt(rmax);
227  base::check();
228  typename base::data td;
229  td.rmin2 = rmin * rmin;
230  td.rmax2 = rmax * rmax;
231 
232  T rumin = rmin;
233  T rmax2 = rmax * rmax;
234  T dr = rmax - rmin;
235  T rupp = rmax;
236  T zupp = rmax2;
237  bool repul = false; // Stop tabulation if repul is true
238 
239  td.r2.push_back(zupp);
240 
241  int i;
242  for (i = 0; i < mngrid; i++) {
243  T rlow = rupp;
244  T zlow;
245  std::vector<T> ubuft;
246  int j;
247 
248  dr = (rupp - rmin);
249 
250  for (j = 0; j < ndr; j++) {
251  zupp = rupp * rupp;
252  rlow = rupp - dr;
253  if (rumin > rlow)
254  rlow = rumin;
255 
256  zlow = rlow * rlow;
257 
258  T u0low = f(zlow);
259  T u1low = base::f1(f, zlow);
260  T u2low = base::f2(f, zlow);
261  T u0upp = f(zupp);
262  T u1upp = base::f1(f, zupp);
263  T u2upp = base::f2(f, zupp);
264 
265  ubuft =
266  SetUBuffer(rlow, zlow, rupp, zupp, u0low, u1low, u2low, u0upp, u1upp, u2upp);
267  std::vector<bool> vb = CheckUBuffer(ubuft, rlow, rupp, f);
268  repul = vb[1];
269  if (vb[0]) {
270  rupp = rlow;
271  break;
272  }
273  dr *= drfrac;
274  }
275 
276  if (j >= ndr)
277  throw std::runtime_error("Andrea spline: try to increase utol/ftol");
278  if (ubuft.size() != 7)
279  throw std::runtime_error(
280  "Andrea spline: wrong size of ubuft, min value + 6 coefficients");
281 
282  td.r2.push_back(zlow);
283  for (size_t k = 1; k < ubuft.size(); k++)
284  td.c.push_back(ubuft.at(k));
285 
286  // Entered a highly repulsive part, stop tabulation
287  if (repul) {
288  rumin = rlow;
289  td.rmin2 = rlow * rlow;
290  }
291  if (rlow <= rumin || repul)
292  break;
293  }
294 
295  if (i >= mngrid)
296  throw std::runtime_error("Andrea spline: try to increase utol/ftol");
297 
298  // create final reversed c and r2
299  assert(td.c.size() % 6 == 0);
300  assert(td.c.size() / (td.r2.size() - 1) == 6);
301  assert(std::is_sorted(td.r2.rbegin(), td.r2.rend()));
302  std::reverse(td.r2.begin(), td.r2.end()); // reverse all elements
303  for (size_t i = 0; i < td.c.size() / 2; i += 6) // reverse knot order in packets of six
304  std::swap_ranges(td.c.begin() + i, td.c.begin() + i + 6,
305  td.c.end() - i - 6); // c++17 only
306  return td;
307  }
308 };
309 } // namespace Tabulate
310 } // namespace Faunus
311 
312 #ifdef DOCTEST_LIBRARY_INCLUDED
313 TEST_CASE("[Faunus] Andrea")
314 {
315  using doctest::Approx;
316  using namespace Faunus::Tabulate;
317 
318  auto f = [](double x) { return 0.5 * x * std::sin(x) + 2; };
319  Andrea<double> spline;
320  spline.setTolerance(2e-6, 1e-4); // ftol carries no meaning
321  auto d = spline.generate(f, 0, 10);
322 
323  CHECK(d.r2.size() == 19);
324  CHECK(d.c.size() == 108);
325  CHECK(d.numKnots() == 19);
326  CHECK(d.rmin2 == Approx(0.0));
327  CHECK(d.rmax2 == Approx(10.0));
328  CHECK(d.r2.at(0) == Approx(0.0));
329  CHECK(d.r2.at(1) == Approx(0.212991));
330  CHECK(d.r2.at(2) == Approx(0.782554));
331  CHECK(d.r2.back() == Approx(10.0));
332 
333  CHECK(d.c.at(0) == Approx(2.0));
334  CHECK(d.c.at(1) == Approx(0.0));
335  CHECK(d.c.at(2) == Approx(0.5));
336  CHECK(d.c.back() == Approx(-0.0441931));
337 
338  CHECK(spline.eval(d, 1e-9) == Approx(f(1e-9)));
339  CHECK(spline.eval(d, 5) == Approx(f(5)));
340  CHECK(spline.eval(d, 10) == Approx(f(10)));
341 
342  // Check if numerical derivation of *splined* function
343  // matches the analytical solution in `evalDer()`.
344  auto f_prime = [&](double x, double dx = 1e-10) {
345  return (spline.eval(d, x + dx) - spline.eval(d, x - dx)) / (2 * dx);
346  };
347  double x = 1e-9;
348  CHECK(spline.evalDer(d, x) == Approx(f_prime(x)));
349  x = 1;
350  CHECK(spline.evalDer(d, x) == Approx(f_prime(x)));
351  x = 5;
352  CHECK(spline.evalDer(d, x) == Approx(f_prime(x)));
353 
354  // Check if analytical spline derivative matches
355  // derivative of original function
356  auto f_prime_exact = [&](double x, double dx = 1e-10) {
357  return (f(x + dx) - f(x - dx)) / (2 * dx);
358  };
359  x = 1e-9;
360  CHECK(spline.evalDer(d, x) == Approx(f_prime_exact(x)));
361  x = 1;
362  CHECK(spline.evalDer(d, x) == Approx(f_prime_exact(x)));
363  x = 5;
364  CHECK(spline.evalDer(d, x) == Approx(f_prime_exact(x)));
365 }
366 #endif
double T
floating point size
Definition: units.h:73
base::data generate(std::function< T(T)> f, double rmin, double rmax)
Tabulate f(x) in interval ]min,max].
Definition: tabulate.h:223
Definition: tabulate.h:28
Cell list class templates.
Definition: actions.cpp:11
T evalDer(const typename base::data &d, T r2) const
Get tabulated value at df(x)/dx.
Definition: tabulate.h:209
Class templates for tabulation of pair potentials.
Definition: tabulate.h:25
Andrea table with logarithmic search.
Definition: tabulate.h:95
Definition: tabulate.h:63
T eval(const typename base::data &d, T r2) const
Get tabulated value at f(x)
Definition: tabulate.h:184