2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2016,2019,2020,2021, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
37 * \defgroup module_tables Classes for table interpolation
38 * \ingroup group_utilitymodules
40 * \brief Table interpolation from analytical or numerical input
42 * This module provides quadratic spline interpolation tables used
43 * both for the nonbonded kernels and listed interactions.
45 * \author Erik Lindahl <erik.lindahl@scilifelab.se>
49 /*! \libinternal \file
51 * Declares classes for quadratic spline table
54 * \author Erik Lindahl <erik.lindahl@gmail.com>
55 * \ingroup module_tables
57 #ifndef GMX_TABLES_QUADRATICSPLINETABLE_H
58 #define GMX_TABLES_QUADRATICSPLINETABLE_H
61 #include <initializer_list>
65 #include "gromacs/simd/simd.h"
66 #include "gromacs/tables/tableinput.h"
67 #include "gromacs/utility/alignedallocator.h"
68 #include "gromacs/utility/classhelpers.h"
69 #include "gromacs/utility/exceptions.h"
70 #include "gromacs/utility/gmxassert.h"
71 #include "gromacs/utility/real.h"
76 /*! \libinternal \brief Quadratic spline interpolation table.
78 * This class interpolates a function specified either as an analytical
79 * expression or from user-provided table data.
81 * At initialization, you provide the reference function of vectors
82 * as a list of tuples that contain a brief name, the function, and
83 * derivative for each function to tabulate. To create a table with
84 * two functions this initializer list can for instance look like
86 * { {"LJ6", lj6Func, lj6Der}, {"LJ12", lj12Func, lj12Der} }
88 * The names are only used so exceptions during initialization can
89 * be traced to a specific table.
91 * When interpolating, there are methods to interpolate either 1, 2, or 3
92 * functions in one go. By default these interpolation routines will
93 * operate on tables with the same number of functions as specified in
94 * the interpolation method (debug builds check that this is consistent with
95 * the table). However, it is also possible to use optional template
96 * parameters that specify the total number of functions in a table, and
97 * what function index to interpolate. For instance, to interpolate the
98 * derivative of the second function (i.e., index 1) in a
99 * multi-function-table with three functions in total, you can write
101 * table.evaluateDerivative<3,1>(x,&der);
103 * Here too, debug builds will check that the template parameters are
104 * consistent with the table.
106 * The table data is internally adjusted to guarantee that the interpolated
107 * derivative is the true derivative of the interpolated potential, which is
108 * important to avoid systematic errors for the common case when the derivative
109 * is concave/convex in the entire interval.
110 * We do this by expressing the difference in the function value
111 * at a small offset h relative to a reference value in position 0 with a forward
112 * Taylor series expanded around 0, and then doing the opposite of expressing
113 * difference in the function at position 0 relative to a reference value in
114 * position h when using a backward Taylor expansion:
117 * \Delta V & = & hV'(0) + \frac{1}{2} h^2 V''(0) + \frac{1}{6} h^3 V'''(0) + O(h^4) \\
118 * \Delta V & = & hV'(h) - \frac{1}{2} h^2 V''(h) + \frac{1}{6} h^3 V'''(h) + O(h^4)
121 * Summing the equations leads to
124 * 2 \Delta V = h(V'(0) + V'(h)) + \frac{1}{2} h^2 (V''(0)-V''(h)) +
125 * \frac{1}{6}h^3(V'''(0)+V'''(h)) + O(h^4) \f]
127 * To make the second term symmetric too, we can replace it with the average of
128 * the Taylor expansion at 0 and h (i.e., using the third derivative). This gives
131 * 2 \Delta V = h(V'(0) + V'(h)) - \frac{1}{12} h^3 (V'''(0)+V'''(h)) + O(h^4)
134 * Thus, if we replace the derivative in the internal quadratic table data with
137 * V' - \frac{1}{12}h^2 V'''
140 * we will cancel the h^3 term in the error. This will make the integral of the
141 * forces match the potential much better (The h^4 term actually disappears, so
142 * when summing over 1/h points the remaining error will be O(h^4).
144 * While it is possible to create tables only from function values
145 * (i.e., no derivatives), it is recommended to provide derivatives for higher
146 * accuracy and to avoid issues with numerical differentiation. Note that the
147 * table input should be smooth, i.e. it should not contain noise e.g. from an
148 * (iterative) Boltzmann inversion procedure - you have been warned.
150 * \note This class is responsible for fundamental interpolation of any function,
151 * which might or might not correspond to a potential. For this reason
152 * both input and output derivatives are proper function derivatives, and
153 * we do not swap any signs to get forces directly from the table.
155 * \note There will be a small additional accuracy loss from the internal
156 * operation where we calculate the epsilon offset from the nearest table
157 * point, since the integer part we subtract can get large in those cases.
158 * The absolute such error both in the function and derivative value will
159 * be roughly f''*x*GMX_REAL_EPS, where x is the argument and f'' the
161 * While this is technically possible to solve with extended precision
162 * arithmetics, that would introduce extra instructions in some highly
163 * performance-sensitive code parts. For typical GROMACS interaction
164 * functions the derivatives will decay faster than the potential, which
165 * means it will never play any role. For other functions it will only
166 * cause a small increase in the relative error for arguments where the
167 * magnitude of the function or derivative is very small.
168 * Since we typically sum several results in GROMACS, this should never
169 * show up in any real cases, and for this reason we choose not to do
170 * the extended precision arithmetics.
172 * \note These routines are not suitable for table ranges starting far away
173 * from zero, since we allocate memory and calculate indices starting from
174 * range zero for efficiency reasons.
176 class QuadraticSplineTable
179 /*! \brief Change that function value falls inside range when debugging
181 * \tparam T Lookup argument floating-point type, typically SimdReal or real.
182 * \param r Lookup argument to test
184 * \throws Debug builds will throw gmx::RangeError for values that are
185 * larger than the upper limit of the range, or smaller than 0.
186 * We allow the table to be called with arguments between 0 and
187 * the lower limit of the range, since this might in theory occur
188 * once-in-a-blue-moon with some algorithms.
191 void rangeCheck(T gmx_unused r) const
194 // Check that all values fall in range when debugging
195 if (anyTrue(r < T(0.0) || T(range_.second) <= r))
197 GMX_THROW(RangeError("Interpolation input value falls outside table definition range"));
203 /*! \brief Default tolerance for tables is 10*GMX_FLOAT_EPS
205 * \note Even for double precision builds we set the tolerance to
206 * one order of magnitude above the single precision epsilon.
208 static const real defaultTolerance;
210 /*! \brief Initialize table data from function
212 * \param analyticalInputList Initializer list with one or more functions to tabulate,
213 * specified as pairs containing analytical
214 * functions and their derivatives. The function will also
215 * be called for values smaller than the lower limit of the
216 * range, but we avoid calling it for 0.0 if that value
217 * is not included in the range.
218 * \param range Range over which the function will be tabulated.
219 * Constructor will throw gmx::APIError for negative values.
220 * Due to the way the numerical derivative evaluation depends
221 * on machine precision internally, this range must be
222 * at least 0.001, or the constructor throws gmx::APIError.
223 * \param tolerance Requested accuracy of the table. This will be used to
224 * calculate the required internal spacing. If this cannot
225 * be achieved (for instance because the table would require
226 * too much memory) the constructor will throw gmx::ToleranceError.
228 * \note The functions are always defined in double precision to avoid
229 * losing accuracy when constructing tables.
231 * \note Since we fill the table for values below range.first, you can achieve
232 * a smaller table by using a smaller range where the tolerance has to be
233 * met, and accept that a few function calls below range.first do not
234 * quite reach the tolerance.
236 * \warning For efficiency reasons (since this code is used in some inner
237 * (kernels), we always allocate memory and calculate table indices
238 * for the complete interval [0,range.second], although the data will
239 * not be valid outside the definition range to avoid calling the
240 * function there. This means you should \a not use this class
241 * to tabulate functions for small ranges very far away from zero,
242 * since you would both waste a huge amount of memory and incur
243 * truncation errors when calculating the index.
245 * \throws gmx::ToleranceError if the requested tolerance cannot be achieved,
246 * and gmx::APIError for other incorrect input.
248 QuadraticSplineTable(std::initializer_list<AnalyticalSplineTableInput> analyticalInputList,
249 const std::pair<real, real>& range,
250 real tolerance = defaultTolerance);
252 /*! \brief Initialize table data from tabulated values and derivatives
254 * \param numericalInputList Initializer list with one or more functions to tabulate,
255 * specified as pairs containing containing vectors for the
256 * function values and their derivatives. Data points are
257 * separated by the spacing parameter, starting from 0.
258 * Values below the lower limit of the range will be used to
259 * attempt defining the table, but we avoid using index 0
260 * unless 0.0 is included in the range. Some extra points beyond
261 * range.second are required to re-interpolate values, so add
262 * some margin. The constructor will throw gmx::APIError if the
263 * input vectors are too short to cover the requested range
264 * (and they must always be at least five points).
265 * \param range Range over which the function will be tabulated.
266 * Constructor will throw gmx::APIError for negative values,
267 * or if the value/derivative vector does not cover the
269 * \param tolerance Requested accuracy of the table in the range. This will be
270 * used to calculate the required internal spacing and possibly
271 * re-interpolate. The constructor will throw
272 * gmx::ToleranceError if the input spacing is too coarse
273 * to achieve this accuracy.
275 * \note The input data vectors are always double precision to avoid
276 * losing accuracy when constructing tables.
278 * \note Since we fill the table for values below range.first, you can achieve
279 * a smaller table by using a smaller range where the tolerance has to be
280 * met, and accept that a few function calls below range.first do not
281 * quite reach the tolerance.
283 * \warning For efficiency reasons (since this code is used in some inner
284 * (kernels), we always allocate memory and calculate table indices
285 * for the complete interval [0,range.second], although the data will
286 * not be valid outside the definition range to avoid calling the
287 * function there. This means you should \a not use this class
288 * to tabulate functions for small ranges very far away from zero,
289 * since you would both waste a huge amount of memory and incur
290 * truncation errors when calculating the index.
292 QuadraticSplineTable(std::initializer_list<NumericalSplineTableInput> numericalInputList,
293 const std::pair<real, real>& range,
294 real tolerance = defaultTolerance);
297 /************************************************************
298 * Evaluation methods for single functions *
299 ************************************************************/
301 /*! \brief Evaluate both function and derivative, single table function
303 * This is a templated method where the template can be either real or SimdReal.
305 * \tparam numFuncInTable Number of separate functions in table, default is 1
306 * \tparam funcIndex Index of function to evaluate in table, default is 0
307 * \tparam T Type (SimdReal or real) of lookup and result
308 * \param r Points for which to evaluate function and derivative
309 * \param[out] functionValue Function value
310 * \param[out] derivativeValue Function derivative
312 * For debug builds we assert that the input values fall in the range
313 * specified when constructing the table.
315 template<int numFuncInTable = 1, int funcIndex = 0, typename T>
316 void evaluateFunctionAndDerivative(T r, T* functionValue, T* derivativeValue) const
319 GMX_ASSERT(numFuncInTable == numFuncInTable_,
320 "Evaluation method not matching number of functions in table");
321 GMX_ASSERT(funcIndex < numFuncInTable,
322 "Function index not in range of the number of tables");
324 T rTable = r * T(tableScale_);
325 auto tabIndex = cvttR2I(rTable); // type is either std::int32_t or SimdInt32
326 T eps = rTable - trunc(rTable);
332 // Load Derivative, Delta, Function, and Zero values for each table point.
333 // The 4 refers to these four values - not any SIMD width.
334 gatherLoadBySimdIntTranspose<4 * numFuncInTable>(
335 ddfzMultiTableData_.data() + 4 * funcIndex, tabIndex, &t0, &t1, &t2, &t3);
338 *functionValue = fma(eps * T(halfSpacing_), t0 + t1, t2);
339 *derivativeValue = t1;
342 /*! \brief Evaluate function value only, single table function
344 * This is a templated method where the template can be either real or SimdReal.
346 * \tparam numFuncInTable Number of separate functions in table, default is 1
347 * \tparam funcIndex Index of function to evaluate in table, default is 0
348 * \tparam T Type (SimdReal or real) of lookup and result
349 * \param r Points for which to evaluate function value
350 * \param[out] functionValue Function value
352 * For debug builds we assert that the input values fall in the range
353 * specified when constructing the table.
355 template<int numFuncInTable = 1, int funcIndex = 0, typename T>
356 void evaluateFunction(T r, T* functionValue) const
360 evaluateFunctionAndDerivative<numFuncInTable, funcIndex>(r, functionValue, &der);
363 /*! \brief Evaluate function derivative only, single table function
365 * This is a templated method where the template can be either real or SimdReal.
367 * \tparam numFuncInTable Number of separate functions in table, default is 1
368 * \tparam funcIndex Index of function to evaluate in table, default is 0
369 * \tparam T Type (SimdReal or real) of lookup and result
370 * \param r Points for which to evaluate function derivative
371 * \param[out] derivativeValue Function derivative
373 * For debug builds we assert that the input values fall in the range
374 * specified when constructing the table.
376 template<int numFuncInTable = 1, int funcIndex = 0, typename T>
377 void evaluateDerivative(T r, T* derivativeValue) const
380 GMX_ASSERT(numFuncInTable == numFuncInTable_,
381 "Evaluation method not matching number of functions in table");
382 GMX_ASSERT(funcIndex < numFuncInTable,
383 "Function index not in range of the number of tables");
385 T rTable = r * T(tableScale_);
386 auto tabIndex = cvttR2I(rTable); // type is either std::int32_t or SimdInt32
387 T eps = rTable - trunc(rTable);
392 if (numFuncInTable == 1)
394 gatherLoadUBySimdIntTranspose<numFuncInTable>(
395 derivativeMultiTableData_.data() + funcIndex, tabIndex, &t0, &t1); // works for scalar T too
399 // This is not ideal, but we need a version of gatherLoadUBySimdIntTranspose that
400 // only loads a single value from memory to implement it better (will be written)
401 gatherLoadUBySimdIntTranspose<numFuncInTable>(
402 derivativeMultiTableData_.data() + funcIndex, tabIndex, &t0, &t2); // works for scalar T too
403 gatherLoadUBySimdIntTranspose<numFuncInTable>(derivativeMultiTableData_.data() + funcIndex,
406 &t2); // works for scalar T too
409 // (1-eps)*t0 + eps*t1
410 *derivativeValue = fma(t1 - t0, eps, t0);
413 /************************************************************
414 * Evaluation methods for two functions *
415 ************************************************************/
417 /*! \brief Evaluate both function and derivative, two table functions
419 * This is a templated method where the template can be either real or SimdReal.
421 * \tparam numFuncInTable Number of separate functions in table, default is 2
422 * \tparam funcIndex0 Index of 1st function to evaluate in table, default is 0
423 * \tparam funcIndex1 Index of 2nd function to evaluate in table, default is 1
424 * \tparam T Type (SimdReal or real) of lookup and result
425 * \param r Points for which to evaluate function and derivative
426 * \param[out] functionValue1 Interpolated value for first function
427 * \param[out] derivativeValue1 Interpolated derivative for first function
428 * \param[out] functionValue2 Interpolated value for second function
429 * \param[out] derivativeValue2 Interpolated derivative for second function
431 * For debug builds we assert that the input values fall in the range
432 * specified when constructing the table.
434 template<int numFuncInTable = 2, int funcIndex0 = 0, int funcIndex1 = 1, typename T>
435 void evaluateFunctionAndDerivative(T r, T* functionValue1, T* derivativeValue1, T* functionValue2, T* derivativeValue2) const
438 GMX_ASSERT(numFuncInTable == numFuncInTable_,
439 "Evaluation method not matching number of functions in table");
440 GMX_ASSERT(funcIndex0 < numFuncInTable && funcIndex1 < numFuncInTable,
441 "Function index not in range of the number of tables");
443 T rTable = r * T(tableScale_);
444 auto tabIndex = cvttR2I(rTable); // type is either std::int32_t or SimdInt32
445 T eps = rTable - trunc(rTable);
451 // Load Derivative, Delta, Function, and Zero values for each table point.
452 // The 4 refers to these four values - not any SIMD width.
453 gatherLoadBySimdIntTranspose<4 * numFuncInTable>(
454 ddfzMultiTableData_.data() + 4 * funcIndex0, tabIndex, &t0, &t1, &t2, &t3);
456 *functionValue1 = fma(eps * T(halfSpacing_), t0 + t1, t2);
457 *derivativeValue1 = t1;
459 gatherLoadBySimdIntTranspose<4 * numFuncInTable>(
460 ddfzMultiTableData_.data() + 4 * funcIndex1, tabIndex, &t0, &t1, &t2, &t3);
462 *functionValue2 = fma(eps * T(halfSpacing_), t0 + t1, t2);
463 *derivativeValue2 = t1;
466 /*! \brief Evaluate function value only, two table functions
468 * This is a templated method where the template can be either real or SimdReal.
470 * \tparam numFuncInTable Number of separate functions in table, default is 2
471 * \tparam funcIndex0 Index of 1st function to evaluate in table, default is 0
472 * \tparam funcIndex1 Index of 2nd function to evaluate in table, default is 1
473 * \tparam T Type (SimdReal or real) of lookup and result
474 * \param r Points for which to evaluate function value
475 * \param[out] functionValue1 Interpolated value for first function
476 * \param[out] functionValue2 Interpolated value for second function
478 * For debug builds we assert that the input values fall in the range
479 * specified when constructing the table.
481 template<int numFuncInTable = 2, int funcIndex0 = 0, int funcIndex1 = 1, typename T>
482 void evaluateFunction(T r, T* functionValue1, T* functionValue2) const
487 evaluateFunctionAndDerivative<numFuncInTable, funcIndex0, funcIndex1>(
488 r, functionValue1, &der1, functionValue2, &der2);
491 /*! \brief Evaluate function derivative only, two table functions
493 * This is a templated method where the template can be either real or SimdReal.
495 * \tparam numFuncInTable Number of separate functions in table, default is 2
496 * \tparam funcIndex0 Index of 1st function to evaluate in table, default is 0
497 * \tparam funcIndex1 Index of 2nd function to evaluate in table, default is 1
498 * \tparam T Type (SimdReal or real) of lookup and result
499 * \param r Points for which to evaluate function derivative
500 * \param[out] derivativeValue1 Interpolated derivative for first function
501 * \param[out] derivativeValue2 Interpolated derivative for second function
503 * For debug builds we assert that the input values fall in the range
504 * specified when constructing the table.
506 template<int numFuncInTable = 2, int funcIndex0 = 0, int funcIndex1 = 1, typename T>
507 void evaluateDerivative(T r, T* derivativeValue1, T* derivativeValue2) const
510 GMX_ASSERT(numFuncInTable == numFuncInTable_,
511 "Evaluation method not matching number of functions in table");
512 GMX_ASSERT(funcIndex0 < numFuncInTable && funcIndex1 < numFuncInTable,
513 "Function index not in range of the number of tables");
515 T rTable = r * T(tableScale_);
516 auto tabIndex = cvttR2I(rTable); // type is either std::int32_t or SimdInt32
517 T eps = rTable - trunc(rTable);
519 if (numFuncInTable == 2 && funcIndex0 == 0 && funcIndex1 == 1)
521 T t0A, t0B, t1A, t1B;
522 gatherLoadUBySimdIntTranspose<numFuncInTable>(
523 derivativeMultiTableData_.data(), tabIndex, &t0A, &t0B); // works for scalar T too
524 gatherLoadUBySimdIntTranspose<numFuncInTable>(
525 derivativeMultiTableData_.data() + 2, tabIndex, &t1A, &t1B); // works for scalar T too
526 *derivativeValue1 = fma(t1A - t0A, eps, t0A);
527 *derivativeValue2 = fma(t1B - t0B, eps, t0B);
532 // This is not ideal, but we need a version of gatherLoadUBySimdIntTranspose that
533 // only loads a single value from memory to implement it better (will be written)
534 gatherLoadUBySimdIntTranspose<numFuncInTable>(
535 derivativeMultiTableData_.data() + funcIndex0, tabIndex, &t0, &t2); // works for scalar T too
536 gatherLoadUBySimdIntTranspose<numFuncInTable>(derivativeMultiTableData_.data() + funcIndex0,
539 &t2); // works for scalar T too
540 *derivativeValue1 = fma(t1 - t0, eps, t0);
542 gatherLoadUBySimdIntTranspose<numFuncInTable>(
543 derivativeMultiTableData_.data() + funcIndex1, tabIndex, &t0, &t2); // works for scalar T too
544 gatherLoadUBySimdIntTranspose<numFuncInTable>(derivativeMultiTableData_.data() + funcIndex1,
547 &t2); // works for scalar T too
548 *derivativeValue2 = fma(t1 - t0, eps, t0);
552 /************************************************************
553 * Evaluation methods for three functions *
554 ************************************************************/
557 /*! \brief Evaluate both function and derivative, three table functions
559 * This is a templated method where the template can be either real or SimdReal.
561 * \tparam numFuncInTable Number of separate functions in table, default is 3
562 * \tparam funcIndex0 Index of 1st function to evaluate in table, default is 0
563 * \tparam funcIndex1 Index of 2nd function to evaluate in table, default is 1
564 * \tparam funcIndex2 Index of 3rd function to evaluate in table, default is 2
565 * \tparam T Type (SimdReal or real) of lookup and result
566 * \param r Points for which to evaluate function and derivative
567 * \param[out] functionValue1 Interpolated value for first function
568 * \param[out] derivativeValue1 Interpolated derivative for first function
569 * \param[out] functionValue2 Interpolated value for second function
570 * \param[out] derivativeValue2 Interpolated derivative for second function
571 * \param[out] functionValue3 Interpolated value for third function
572 * \param[out] derivativeValue3 Interpolated derivative for third function
574 * For debug builds we assert that the input values fall in the range
575 * specified when constructing the table.
577 template<int numFuncInTable = 3, int funcIndex0 = 0, int funcIndex1 = 1, int funcIndex2 = 2, typename T>
578 void evaluateFunctionAndDerivative(T r,
584 T* derivativeValue3) const
587 GMX_ASSERT(numFuncInTable == numFuncInTable,
588 "Evaluation method not matching number of functions in table");
589 GMX_ASSERT(funcIndex0 < numFuncInTable && funcIndex1 < numFuncInTable && funcIndex2 < numFuncInTable,
590 "Function index not in range of the number of tables");
592 T rTable = r * T(tableScale_);
593 auto tabIndex = cvttR2I(rTable); // type is either std::int32_t or SimdInt32
594 T eps = rTable - trunc(rTable);
600 gatherLoadBySimdIntTranspose<4 * numFuncInTable>(
601 ddfzMultiTableData_.data() + 4 * funcIndex0, tabIndex, &t0, &t1, &t2, &t3);
603 *functionValue1 = fma(eps * T(halfSpacing_), t0 + t1, t2);
604 *derivativeValue1 = t1;
606 gatherLoadBySimdIntTranspose<4 * numFuncInTable>(
607 ddfzMultiTableData_.data() + 4 * funcIndex1, tabIndex, &t0, &t1, &t2, &t3);
609 *functionValue2 = fma(eps * T(halfSpacing_), t0 + t1, t2);
610 *derivativeValue2 = t1;
612 gatherLoadBySimdIntTranspose<4 * numFuncInTable>(
613 ddfzMultiTableData_.data() + 4 * funcIndex2, tabIndex, &t0, &t1, &t2, &t3);
615 *functionValue3 = fma(eps * T(halfSpacing_), t0 + t1, t2);
616 *derivativeValue3 = t1;
619 /*! \brief Evaluate function value only, three table functions
621 * This is a templated method where the template can be either real or SimdReal.
623 * \tparam numFuncInTable Number of separate functions in table, default is 3
624 * \tparam funcIndex0 Index of 1st function to evaluate in table, default is 0
625 * \tparam funcIndex1 Index of 2nd function to evaluate in table, default is 1
626 * \tparam funcIndex2 Index of 3rd function to evaluate in table, default is 2
627 * \tparam T Type (SimdReal or real) of lookup and result
628 * \param r Points for which to evaluate function value
629 * \param[out] functionValue1 Interpolated value for first function
630 * \param[out] functionValue2 Interpolated value for second function
631 * \param[out] functionValue3 Interpolated value for third function
633 * For debug builds we assert that the input values fall in the range
634 * specified when constructing the table.
636 template<int numFuncInTable = 3, int funcIndex0 = 0, int funcIndex1 = 1, int funcIndex2 = 2, typename T>
637 void evaluateFunction(T r, T* functionValue1, T* functionValue2, T* functionValue3) const
643 evaluateFunctionAndDerivative<numFuncInTable, funcIndex0, funcIndex1, funcIndex2>(
644 r, functionValue1, &der1, functionValue2, &der2, functionValue3, &der3);
647 /*! \brief Evaluate function derivative only, three table functions
649 * This is a templated method where the template can be either real or SimdReal.
651 * \tparam numFuncInTable Number of separate functions in table, default is 3
652 * \tparam funcIndex0 Index of 1st function to evaluate in table, default is 0
653 * \tparam funcIndex1 Index of 2nd function to evaluate in table, default is 1
654 * \tparam funcIndex2 Index of 3rd function to evaluate in table, default is 2
655 * \tparam T Type (SimdReal or real) of lookup and result
656 * \param r Points for which to evaluate function derivative
657 * \param[out] derivativeValue1 Interpolated derivative for first function
658 * \param[out] derivativeValue2 Interpolated derivative for second function
659 * \param[out] derivativeValue3 Interpolated derivative for third function
661 * For debug builds we assert that the input values fall in the range
662 * specified when constructing the table.
664 template<int numFuncInTable = 3, int funcIndex0 = 0, int funcIndex1 = 1, int funcIndex2 = 2, typename T>
665 void evaluateDerivative(T r, T* derivativeValue1, T* derivativeValue2, T* derivativeValue3) const
668 GMX_ASSERT(numFuncInTable == numFuncInTable_,
669 "Evaluation method not matching number of functions in table");
670 GMX_ASSERT(funcIndex0 < numFuncInTable && funcIndex1 < numFuncInTable && funcIndex2 < numFuncInTable,
671 "Function index not in range of the number of tables");
673 T rTable = r * T(tableScale_);
674 auto tabIndex = cvttR2I(rTable); // type is either std::int32_t or SimdInt32
675 T eps = rTable - trunc(rTable);
677 if (numFuncInTable == 3 && funcIndex0 == 0 && funcIndex1 == 1 && funcIndex2 == 2)
679 T t0A, t0B, t0C, t1A, t1B, t1C;
680 gatherLoadUBySimdIntTranspose<numFuncInTable>(
681 derivativeMultiTableData_.data(), tabIndex, &t0A, &t0B);
682 gatherLoadUBySimdIntTranspose<numFuncInTable>(
683 derivativeMultiTableData_.data() + 2, tabIndex, &t0C, &t1A);
684 gatherLoadUBySimdIntTranspose<numFuncInTable>(
685 derivativeMultiTableData_.data() + 4, tabIndex, &t1B, &t1C);
686 *derivativeValue1 = fma(t1A - t0A, eps, t0A);
687 *derivativeValue2 = fma(t1B - t0B, eps, t0B);
688 *derivativeValue3 = fma(t1C - t0C, eps, t0C);
693 // This is not ideal, but we need a version of gatherLoadUBySimdIntTranspose that
694 // only loads a single value from memory to implement it better (will be written)
695 gatherLoadUBySimdIntTranspose<numFuncInTable>(
696 derivativeMultiTableData_.data() + funcIndex0, tabIndex, &t0, &t2); // works for scalar T too
697 gatherLoadUBySimdIntTranspose<numFuncInTable>(derivativeMultiTableData_.data() + funcIndex0,
700 &t2); // works for scalar T too
701 *derivativeValue1 = fma(t1 - t0, eps, t0);
703 gatherLoadUBySimdIntTranspose<numFuncInTable>(
704 derivativeMultiTableData_.data() + funcIndex1, tabIndex, &t0, &t2); // works for scalar T too
705 gatherLoadUBySimdIntTranspose<numFuncInTable>(derivativeMultiTableData_.data() + funcIndex1,
708 &t2); // works for scalar T too
709 *derivativeValue2 = fma(t1 - t0, eps, t0);
711 gatherLoadUBySimdIntTranspose<numFuncInTable>(
712 derivativeMultiTableData_.data() + funcIndex2, tabIndex, &t0, &t2); // works for scalar T too
713 gatherLoadUBySimdIntTranspose<numFuncInTable>(derivativeMultiTableData_.data() + funcIndex2,
716 &t2); // works for scalar T too
717 *derivativeValue3 = fma(t1 - t0, eps, t0);
721 /*! \brief Return the table spacing (distance between points)
723 * You should never have to use this for normal code, but due to the
724 * way tables are constructed internally we need this in the unit tests
725 * to check relative tolerances over each interval.
727 * \return table spacing.
729 real tableSpacing() const { return 1.0 / tableScale_; }
732 std::size_t numFuncInTable_; //!< Number of separate tabluated functions
733 std::pair<real, real> range_; //!< Range for which table evaluation is allowed
734 real tableScale_; //!< Table scale (inverse of spacing between points)
735 real halfSpacing_; //!< 0.5*spacing (used for DDFZ table data)
737 //!< Derivative values only, with the third-derivative subtraction described in the class documentation.
738 std::vector<real> derivativeMultiTableData_;
740 /*! \brief Combined derivative, difference to next derivative, value, and zero.
742 * For table point i, this vector contains the four values:
744 * - (derivative[i+1]-derivative[i])
748 * For the derivative terms we have subtracted the third-derivative term described
749 * in the main class documentation.
751 * This is typically more efficient than the individual tables, in particular
752 * when using SIMD. The result should be identical outside the class, so this
753 * is merely an internal implementation optimization. However, to allow
754 * aligned SIMD loads we need to use an aligned allocator for this container.
755 * We occasionally abbreviate this data as DDFZ.
757 std::vector<real, AlignedAllocator<real>> ddfzMultiTableData_;
759 // There should never be any reason to copy the table since it is read-only
760 GMX_DISALLOW_COPY_AND_ASSIGN(QuadraticSplineTable);
766 #endif // GMX_TABLES_QUADRATICSPLINETABLE_H