Add cool quote
[alexxy/gromacs.git] / src / gromacs / tables / cubicsplinetable.h
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2016, 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.
8  *
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.
13  *
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.
18  *
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.
23  *
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.
31  *
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.
34  */
35
36 /*! \libinternal \file
37  * \brief
38  * Declares classes for cubic spline table
39  *
40  * \inlibraryapi
41  * \author Erik Lindahl <erik.lindahl@gmail.com>
42  * \ingroup module_tables
43  */
44 #ifndef GMX_TABLES_CUBICSPLINETABLE_H
45 #define GMX_TABLES_CUBICSPLINETABLE_H
46
47 #include <initializer_list>
48 #include <vector>
49
50 #include "gromacs/simd/simd.h"
51 #include "gromacs/tables/tableinput.h"
52 #include "gromacs/utility/alignedallocator.h"
53 #include "gromacs/utility/classhelpers.h"
54 #include "gromacs/utility/exceptions.h"
55 #include "gromacs/utility/gmxassert.h"
56 #include "gromacs/utility/real.h"
57
58 namespace gmx
59 {
60
61 /*! \libinternal \brief Cubic spline interpolation table.
62  *
63  * This class interpolates a function specified either as an analytical
64  * expression or from user-provided table data.
65  *
66  * At initialization, you provide the reference function of vectors
67  * as a list of tuples that contain a brief name, the function, and
68  * derivative for each function to tabulate. To create a table with
69  * two functions this initializer list can for instance look like
70  *
71  *     { {"LJ6", lj6Func, lj6Der}, {"LJ12", lj12Func, lj12Der} }
72  *
73  * The names are only used so exceptions during initialization can
74  * be traced to a specific table.
75  *
76  * When interpolating, there are methods to interpolate either 1, 2, or 3
77  * functions in one go. By default these interpolation routines will
78  * operate on tables with the same number of functions as specified in
79  * the interpolation method (debug builds check that this is consistent with
80  * the table). However, it is also possible to use optional template
81  * parameters that specify the total number of functions in a table, and
82  * what function index to interpolate. For instance, to interpolate the
83  * derivative of the second function (i.e., index 1) in a
84  * multi-function-table with three functions in total, you can write
85  *
86  *     table.evaluateDerivative<3,1>(x,&der);
87  *
88  * Here too, debug builds will check that the template parameters are
89  * consistent with the table.
90  *
91  * This class interpolates a function specified either as an analytical
92  * expression or from user-provided table data. The coefficients for each
93  * table point are precalculated such that we simply evaluate
94  *
95  * \f{eqnarray*}{
96  * V(x)  = Y + F \epsilon + G \epsilon^2 + H \epsilon^3
97  * V'(x) = (F + 2 G \epsilon + 3 H \epsilon^2)/h
98  * \f}
99  *
100  * Where h is the spacing and epsilon the fractional offset from table point.
101  *
102  * While it is possible to create tables only from function values
103  * (i.e., no derivatives), it is recommended to provide derivatives for higher
104  * accuracy and to avoid issues with numerical differentiation. Note that the
105  * table input should be smooth, i.e. it should not contain noise e.g. from an
106  * (iterative) Boltzmann inversion procedure - you have been warned.
107  *
108  * \note This class is responsible for fundamental interpolation of any function,
109  *       which might or might not correspond to a potential. For this reason
110  *       both input and output derivatives are proper function derivatives, and
111  *       we do not swap any signs to get forces directly from the table.
112  *
113  * \note There will be a small additional accuracy loss from the internal
114  *       operation where we calculate the epsilon offset from the nearest table
115  *       point, since the integer part we subtract can get large in those cases.
116  *
117  *       While this is technically possible to solve with extended precision
118  *       arithmetics, that would introduce extra instructions in some highly
119  *       performance-sensitive code parts. For typical GROMACS interaction
120  *       functions the derivatives will decay faster than the potential, which
121  *       means it will never play any role. For other functions it will only
122  *       cause a small increase in the relative error for arguments where the
123  *       magnitude of the function or derivative is very small.
124  *       Since we typically sum several results in GROMACS, this should never
125  *       show up in any real cases, and for this reason we choose not to do
126  *       the extended precision arithmetics.
127  *
128  * \note These routines are not suitable for table ranges starting far away
129  *       from zero, since we allocate memory and calculate indices starting from
130  *       range zero for efficiency reasons.
131  */
132 class CubicSplineTable
133 {
134     private:
135         /*! \brief Change that function value falls inside range when debugging
136          *
137          *  \tparam T   Lookup argument floating-point type, typically SimdReal or real.
138          *  \param  r   Lookup argument to test
139          *
140          *  \throws Debug builds will throw gmx::RangeError for values that are
141          *          larger than the upper limit of the range, or smaller than 0.
142          *          We allow the table to be called with arguments between 0 and
143          *          the lower limit of the range, since this might in theory occur
144          *          once-in-a-blue-moon with some algorithms.
145          */
146         template <typename T>
147         void
148         rangeCheck(T gmx_unused r) const
149         {
150 #ifndef NDEBUG
151             // Check that all values fall in range when debugging
152             if (anyTrue( r < T(0.0) || T(range_.second) <= r ) )
153             {
154                 GMX_THROW(RangeError("Interpolation input value falls outside table definition range"));
155             }
156 #endif
157         }
158
159     public:
160
161         /*! \brief Default tolerance for cubic spline tables
162          *
163          * This is 10*GMX_FLOAT_EPS in single precision, and
164          * 1e-10 for double precision. It might not be worth setting
165          * this tolerance lower than 1e-10 in double precision, both because
166          * you will end up with very large tables, and because
167          * functions like r^-12 become so large for small values of r the
168          * table generation code will lead to some precision loss even
169          * in double precision.
170          */
171         static const real defaultTolerance;
172
173         /*! \brief Initialize table data from function
174          *
175          * \param analyticalInputList Initializer list with one or more functions to tabulate,
176          *                            specified as elements with a string description and
177          *                            the function as well as derivative. The function will also
178          *                            be called for values smaller than the lower limit of the
179          *                            range, but we avoid calling it for 0.0 if that value
180          *                            is not included in the range.
181          *                            Constructor will throw gmx::APIError for negative values.
182          *                            Due to the way the numerical derivative evaluation depends
183          *                            on machine precision internally, this range must be
184          *                            at least 0.001, or the constructor throws gmx::APIError.
185          * \param range                Range over which the function will be tabulated.
186          *                             Constructor will throw gmx::APIError for negative values,
187          *                             or if the value/derivative vector does not cover the
188          *                             range.
189          * \param tolerance           Requested accuracy of the table. This will be used to
190          *                            calculate the required internal spacing. If this cannot
191          *                            be achieved (for instance because the table would require
192          *                            too much memory) the constructor will throw gmx::ToleranceError.
193          *
194          * \note The functions are always defined in double precision to avoid
195          *       losing accuracy when constructing tables.
196          *
197          * \note Since we fill the table for values below range.first, you can achieve
198          *       a smaller table by using a smaller range where the tolerance has to be
199          *       met, and accept that a few function calls below range.first do not
200          *       quite reach the tolerance.
201          *
202          * \warning For efficiency reasons (since this code is used in some inner
203          *       (kernels), we always allocate memory and calculate table indices
204          *       for the complete interval [0,range.second], although the data will
205          *       not be valid outside the definition range to avoid calling the
206          *       function there. This means you should \a not use this class
207          *       to tabulate functions for small ranges very far away from zero,
208          *       since you would both waste a huge amount of memory and incur
209          *       truncation errors when calculating the index.
210          *
211          * \throws gmx::ToleranceError if the requested tolerance cannot be achieved,
212          *         and gmx::APIError for other incorrect input.
213          */
214         CubicSplineTable(std::initializer_list<AnalyticalSplineTableInput>  analyticalInputList,
215                          const std::pair<real, real>                       &range,
216                          real                                               tolerance = defaultTolerance);
217
218         /*! \brief Initialize table data from tabulated values and derivatives
219          *
220          * \param numericalInputList  Initializer list with one or more functions to tabulate,
221          *                            specified as a string description, vectors with function and
222          *                            derivative values, and the input spacing. Data points are
223          *                            separated by the spacing parameter, starting from 0.
224          *                            Values below the lower limit of the range will be used to
225          *                            attempt defining the table, but we avoid using index 0
226          *                            unless 0.0 is included in the range. Some extra points beyond
227          *                            range.second are required to re-interpolate values, so add
228          *                            some margin. The constructor will throw gmx::APIError if the
229          *                            input vectors are too short to cover the requested range
230          *                            (and they must always be at least five points).
231          * \param range               Range over which the function will be tabulated.
232          *                            Constructor will throw gmx::APIError for negative values,
233          *                            or if the value/derivative vector does not cover the
234          *                            range.
235          * \param tolerance           Requested accuracy of the table. This will be used to
236          *                            calculate the required internal spacing and possibly
237          *                            re-interpolate. The constructor will throw
238          *                            gmx::ToleranceError if the input spacing is too coarse
239          *                            to achieve this accuracy.
240          *
241          * \note The input data vectors are always double precision to avoid
242          *       losing accuracy when constructing tables.
243          *
244          * \note Since we fill the table for values below range.first, you can achieve
245          *       a smaller table by using a smaller range where the tolerance has to be
246          *       met, and accept that a few function calls below range.first do not
247          *       quite reach the tolerance.
248          *
249          * \warning For efficiency reasons (since this code is used in some inner
250          *       (kernels), we always allocate memory and calculate table indices
251          *       for the complete interval [0,range.second], although the data will
252          *       not be valid outside the definition range to avoid calling the
253          *       function there. This means you should \a not use this class
254          *       to tabulate functions for small ranges very far away from zero,
255          *       since you would both waste a huge amount of memory and incur
256          *       truncation errors when calculating the index.
257          */
258         CubicSplineTable(std::initializer_list<NumericalSplineTableInput>  numericalInputList,
259                          const std::pair<real, real>                      &range,
260                          real                                              tolerance = defaultTolerance);
261
262
263         /************************************************************
264          *           Evaluation methods for single functions        *
265          ************************************************************/
266
267         /*! \brief Evaluate both function and derivative, single table function
268          *
269          *  This is a templated method where the template can be either real or SimdReal.
270          *
271          *  \tparam     numFuncInTable  Number of separate functions in table, default is 1
272          *  \tparam     funcIndex       Index of function to evaluate in table, default is 0
273          *  \tparam     T               Type (SimdReal or real) of lookup and result
274          *  \param      r               Points for which to evaluate function and derivative
275          *  \param[out] functionValue   Function value
276          *  \param[out] derivativeValue Function derivative
277          *
278          *  For debug builds we assert that the input values fall in the range
279          *  specified when constructing the table.
280          */
281         template <int numFuncInTable = 1, int funcIndex = 0, typename T>
282         void
283         evaluateFunctionAndDerivative(T     r,
284                                       T *   functionValue,
285                                       T *   derivativeValue) const
286         {
287             rangeCheck(r);
288             GMX_ASSERT(numFuncInTable == numFuncInTable_, "Evaluation method not matching number of functions in table");
289             GMX_ASSERT(funcIndex < numFuncInTable, "Function index not in range of the number of tables");
290
291             T     rTable   = r * T(tableScale_);
292             auto  tabIndex = cvttR2I(rTable); // type is either std::int32_t or SimdInt32
293             T     eps      = rTable - trunc(rTable);
294             T     Y, F, G, H;
295
296             // Load Derivative, Delta, Function, and Zero values for each table point.
297             // The 4 refers to these four values - not any SIMD width.
298             gatherLoadBySimdIntTranspose<4*numFuncInTable>(yfghMultiTableData_.data() + 4*funcIndex, tabIndex, &Y, &F, &G, &H);
299             *functionValue   = fma(fma(fma(H, eps, G), eps, F), eps, Y);
300             *derivativeValue = tableScale_ * fma(fma(T(3.0)*H, eps, T(2.0)*G), eps, F);
301         }
302
303         /*! \brief Evaluate function value only, single table function
304          *
305          *  This is a templated method where the template can be either real or SimdReal.
306          *
307          *  \tparam     numFuncInTable  Number of separate functions in table, default is 1
308          *  \tparam     funcIndex       Index of function to evaluate in table, default is 0
309          *  \tparam     T               Type (SimdReal or real) of lookup and result
310          *  \param      r               Points for which to evaluate function value
311          *  \param[out] functionValue   Function value
312          *
313          *  For debug builds we assert that the input values fall in the range
314          *  specified when constructing the table.
315          */
316         template <int numFuncInTable = 1, int funcIndex = 0, typename T>
317         void
318         evaluateFunction(T     r,
319                          T *   functionValue) const
320         {
321             T     der gmx_unused;
322
323             evaluateFunctionAndDerivative<numFuncInTable, funcIndex>(r, functionValue, &der);
324         }
325
326         /*! \brief Evaluate function derivative only, single table function
327          *
328          *  This is a templated method where the template can be either real or SimdReal.
329          *
330          *  \tparam     numFuncInTable  Number of separate functions in table, default is 1
331          *  \tparam     funcIndex       Index of function to evaluate in table, default is 0
332          *  \tparam     T               Type (SimdReal or real) of lookup and result
333          *  \param      r               Points for which to evaluate function derivative
334          *  \param[out] derivativeValue Function derivative
335          *
336          *  For debug builds we assert that the input values fall in the range
337          *  specified when constructing the table.
338          */
339         template <int numFuncInTable = 1, int funcIndex = 0, typename T>
340         void
341         evaluateDerivative(T     r,
342                            T *   derivativeValue) const
343         {
344             rangeCheck(r);
345             GMX_ASSERT(numFuncInTable == numFuncInTable_, "Evaluation method not matching number of functions in table");
346             GMX_ASSERT(funcIndex < numFuncInTable, "Function index not in range of the number of tables");
347
348             T     rTable   = r * T(tableScale_);
349             auto  tabIndex = cvttR2I(rTable); // type is either std::int32_t or SimdInt32
350             T     eps      = rTable - trunc(rTable);
351             T     Y, F, G, H;
352
353             // Load Derivative, Delta, Function, and Zero values for each table point.
354             // The 4 refers to these four values - not any SIMD width.
355             gatherLoadBySimdIntTranspose<4*numFuncInTable>(yfghMultiTableData_.data() + 4 * funcIndex, tabIndex, &Y, &F, &G, &H);
356             *derivativeValue = tableScale_ * fma(fma(T(3.0)*H, eps, T(2.0)*G), eps, F);
357         }
358
359         /************************************************************
360          *             Evaluation methods for two functions         *
361          ************************************************************/
362
363         /*! \brief Evaluate both function and derivative, two table functions
364          *
365          *  This is a templated method where the template can be either real or SimdReal.
366          *
367          *  \tparam     numFuncInTable  Number of separate functions in table, default is 2
368          *  \tparam     funcIndex0       Index of 1st function to evaluate in table, default is 0
369          *  \tparam     funcIndex1       Index of 2nd function to evaluate in table, default is 1
370          *  \tparam     T                Type (SimdReal or real) of lookup and result
371          *  \param      r                Points for which to evaluate function and derivative
372          *  \param[out] functionValue0   Interpolated value for first function
373          *  \param[out] derivativeValue0 Interpolated derivative for first function
374          *  \param[out] functionValue1   Interpolated value for second function
375          *  \param[out] derivativeValue1 Interpolated derivative for second function
376          *
377          *  For debug builds we assert that the input values fall in the range
378          *  specified when constructing the table.
379          */
380         template <int numFuncInTable = 2, int funcIndex0 = 0, int funcIndex1 = 1, typename T>
381         void
382         evaluateFunctionAndDerivative(T     r,
383                                       T *   functionValue0,
384                                       T *   derivativeValue0,
385                                       T *   functionValue1,
386                                       T *   derivativeValue1) const
387         {
388             rangeCheck(r);
389             GMX_ASSERT(numFuncInTable == numFuncInTable_, "Evaluation method not matching number of functions in table");
390             GMX_ASSERT(funcIndex0 < numFuncInTable && funcIndex1 < numFuncInTable, "Function index not in range of the number of tables");
391
392             T     rTable   = r * T(tableScale_);
393             auto  tabIndex = cvttR2I(rTable); // type is either std::int32_t or SimdInt32
394             T     eps      = rTable - trunc(rTable);
395             T     Y, F, G, H;
396
397             // Load Derivative, Delta, Function, and Zero values for each table point.
398             // The 4 refers to these four values - not any SIMD width.
399             gatherLoadBySimdIntTranspose<4*numFuncInTable>(yfghMultiTableData_.data() + 4*funcIndex0, tabIndex, &Y, &F, &G, &H);
400             *functionValue0   = fma(fma(fma(H, eps, G), eps, F), eps, Y);
401             *derivativeValue0 = tableScale_ * fma(fma(T(3.0)*H, eps, T(2.0)*G), eps, F);
402
403             gatherLoadBySimdIntTranspose<4*numFuncInTable>(yfghMultiTableData_.data() + 4*funcIndex1, tabIndex, &Y, &F, &G, &H);
404             *functionValue1   = fma(fma(fma(H, eps, G), eps, F), eps, Y);
405             *derivativeValue1 = tableScale_ * fma(fma(T(3.0)*H, eps, T(2.0)*G), eps, F);
406         }
407
408         /*! \brief Evaluate function value only, two table functions
409          *
410          *  This is a templated method where the template can be either real or SimdReal.
411          *
412          *  \tparam     numFuncInTable   Number of separate functions in table, default is 2
413          *  \tparam     funcIndex0       Index of 1st function to evaluate in table, default is 0
414          *  \tparam     funcIndex1       Index of 2nd function to evaluate in table, default is 1
415          *  \tparam     T                Type (SimdReal or real) of lookup and result
416          *  \param      r                Points for which to evaluate function value
417          *  \param[out] functionValue0   Interpolated value for first function
418          *  \param[out] functionValue1   Interpolated value for second function
419          *
420          *  For debug builds we assert that the input values fall in the range
421          *  specified when constructing the table.
422          */
423         template <int numFuncInTable = 2, int funcIndex0 = 0, int funcIndex1 = 1, typename T>
424         void
425         evaluateFunction(T     r,
426                          T *   functionValue0,
427                          T *   functionValue1) const
428         {
429             T     der0 gmx_unused;
430             T     der1 gmx_unused;
431
432             evaluateFunctionAndDerivative<numFuncInTable, funcIndex0, funcIndex1>(r, functionValue0, &der0, functionValue1, &der1);
433         }
434
435         /*! \brief Evaluate function derivative only, two table functions
436          *
437          *  This is a templated method where the template can be either real or SimdReal.
438          *
439          *  \tparam     numFuncInTable   Number of separate functions in table, default is 2
440          *  \tparam     funcIndex0       Index of 1st function to evaluate in table, default is 0
441          *  \tparam     funcIndex1       Index of 2nd function to evaluate in table, default is 1
442          *  \tparam     T                Type (SimdReal or real) of lookup and result
443          *  \param      r                Points for which to evaluate function derivative
444          *  \param[out] derivativeValue0 Interpolated derivative for first function
445          *  \param[out] derivativeValue1 Interpolated derivative for second function
446          *
447          *  For debug builds we assert that the input values fall in the range
448          *  specified when constructing the table.
449          */
450         template <int numFuncInTable = 2, int funcIndex0 = 0, int funcIndex1 = 1, typename T>
451         void
452         evaluateDerivative(T     r,
453                            T *   derivativeValue0,
454                            T *   derivativeValue1) const
455         {
456             rangeCheck(r);
457             GMX_ASSERT(numFuncInTable == numFuncInTable_, "Evaluation method not matching number of functions in table");
458             GMX_ASSERT(funcIndex0 < numFuncInTable && funcIndex1 < numFuncInTable, "Function index not in range of the number of tables");
459
460             T     rTable   = r * T(tableScale_);
461             auto  tabIndex = cvttR2I(rTable); // type is either std::int32_t or SimdInt32
462             T     eps      = rTable - trunc(rTable);
463             T     Y, F, G, H;
464
465             // Load Derivative, Delta, Function, and Zero values for each table point.
466             // The 4 refers to these four values - not any SIMD width.
467             gatherLoadBySimdIntTranspose<4*numFuncInTable>(yfghMultiTableData_.data() + 4 * funcIndex0, tabIndex, &Y, &F, &G, &H);
468             *derivativeValue0 = tableScale_ * fma(fma(T(3.0)*H, eps, T(2.0)*G), eps, F);
469
470             gatherLoadBySimdIntTranspose<4*numFuncInTable>(yfghMultiTableData_.data() + 4 * funcIndex1, tabIndex, &Y, &F, &G, &H);
471             *derivativeValue1 = tableScale_ * fma(fma(T(3.0)*H, eps, T(2.0)*G), eps, F);
472         }
473
474         /************************************************************
475          *            Evaluation methods for three functions        *
476          ************************************************************/
477
478
479         /*! \brief Evaluate both function and derivative, three table functions
480          *
481          *  This is a templated method where the template can be either real or SimdReal.
482          *
483          *  \tparam     numFuncInTable   Number of separate functions in table, default is 3
484          *  \tparam     funcIndex0       Index of 1st function to evaluate in table, default is 0
485          *  \tparam     funcIndex1       Index of 2nd function to evaluate in table, default is 1
486          *  \tparam     funcIndex2       Index of 3rd function to evaluate in table, default is 2
487          *  \tparam     T                Type (SimdReal or real) of lookup and result
488          *  \param      r                Points for which to evaluate function and derivative
489          *  \param[out] functionValue0   Interpolated value for first function
490          *  \param[out] derivativeValue0 Interpolated derivative for first function
491          *  \param[out] functionValue1   Interpolated value for second function
492          *  \param[out] derivativeValue1 Interpolated derivative for second function
493          *  \param[out] functionValue2   Interpolated value for third function
494          *  \param[out] derivativeValue2 Interpolated derivative for third function
495          *
496          *  For debug builds we assert that the input values fall in the range
497          *  specified when constructing the table.
498          */
499         template <int numFuncInTable = 3, int funcIndex0 = 0, int funcIndex1 = 1, int funcIndex2 = 2, typename T>
500         void
501         evaluateFunctionAndDerivative(T     r,
502                                       T *   functionValue0,
503                                       T *   derivativeValue0,
504                                       T *   functionValue1,
505                                       T *   derivativeValue1,
506                                       T *   functionValue2,
507                                       T *   derivativeValue2) const
508         {
509             rangeCheck(r);
510             GMX_ASSERT(numFuncInTable == numFuncInTable_, "Evaluation method not matching number of functions in table");
511             GMX_ASSERT(funcIndex0 < numFuncInTable && funcIndex1 < numFuncInTable && funcIndex2 < numFuncInTable, "Function index not in range of the number of tables");
512
513             T     rTable   = r * T(tableScale_);
514             auto  tabIndex = cvttR2I(rTable); // type is either std::int32_t or SimdInt32
515             T     eps      = rTable - trunc(rTable);
516             T     Y, F, G, H;
517
518             // Load Derivative, Delta, Function, and Zero values for each table point.
519             // The 4 refers to these four values - not any SIMD width.
520             gatherLoadBySimdIntTranspose<4*numFuncInTable>(yfghMultiTableData_.data() + 4*funcIndex0, tabIndex, &Y, &F, &G, &H);
521             *functionValue0   = fma(fma(fma(H, eps, G), eps, F), eps, Y);
522             *derivativeValue0 = tableScale_ * fma(fma(T(3.0)*H, eps, T(2.0)*G), eps, F);
523
524             gatherLoadBySimdIntTranspose<4*numFuncInTable>(yfghMultiTableData_.data() + 4*funcIndex1, tabIndex, &Y, &F, &G, &H);
525             *functionValue1   = fma(fma(fma(H, eps, G), eps, F), eps, Y);
526             *derivativeValue1 = tableScale_ * fma(fma(T(3.0)*H, eps, T(2.0)*G), eps, F);
527
528             gatherLoadBySimdIntTranspose<4*numFuncInTable>(yfghMultiTableData_.data() + 4*funcIndex2, tabIndex, &Y, &F, &G, &H);
529             *functionValue2   = fma(fma(fma(H, eps, G), eps, F), eps, Y);
530             *derivativeValue2 = tableScale_ * fma(fma(T(3.0)*H, eps, T(2.0)*G), eps, F);
531         }
532
533         /*! \brief Evaluate function value only, three table functions
534          *
535          *  This is a templated method where the template can be either real or SimdReal.
536          *
537          *  \tparam     numFuncInTable   Number of separate functions in table, default is 3
538          *  \tparam     funcIndex0       Index of 1st function to evaluate in table, default is 0
539          *  \tparam     funcIndex1       Index of 2nd function to evaluate in table, default is 1
540          *  \tparam     funcIndex2       Index of 3rd function to evaluate in table, default is 2
541          *  \tparam     T                Type (SimdReal or real) of lookup and result
542          *  \param      r                Points for which to evaluate function value
543          *  \param[out] functionValue0   Interpolated value for first function
544          *  \param[out] functionValue1   Interpolated value for second function
545          *  \param[out] functionValue2   Interpolated value for third function
546          *
547          *  For debug builds we assert that the input values fall in the range
548          *  specified when constructing the table.
549          */
550         template <int numFuncInTable = 3, int funcIndex0 = 0, int funcIndex1 = 1, int funcIndex2 = 2, typename T>
551         void
552         evaluateFunction(T     r,
553                          T *   functionValue0,
554                          T *   functionValue1,
555                          T *   functionValue2) const
556         {
557             T     der0 gmx_unused;
558             T     der1 gmx_unused;
559             T     der2 gmx_unused;
560
561             evaluateFunctionAndDerivative<numFuncInTable, funcIndex0, funcIndex1, funcIndex2>(r, functionValue0, &der0, functionValue1, &der1, functionValue2, &der2);
562         }
563
564         /*! \brief Evaluate function derivative only, three table functions
565          *
566          *  This is a templated method where the template can be either real or SimdReal.
567          *
568          *  \tparam     numFuncInTable   Number of separate functions in table, default is 3
569          *  \tparam     funcIndex0       Index of 1st function to evaluate in table, default is 0
570          *  \tparam     funcIndex1       Index of 2nd function to evaluate in table, default is 1
571          *  \tparam     funcIndex2       Index of 3rd function to evaluate in table, default is 2
572          *  \tparam     T                Type (SimdReal or real) of lookup and result
573          *  \param      r                Points for which to evaluate function derivative
574          *  \param[out] derivativeValue0 Interpolated derivative for first function
575          *  \param[out] derivativeValue1 Interpolated derivative for second function
576          *  \param[out] derivativeValue2 Interpolated derivative for third function
577          *
578          *  For debug builds we assert that the input values fall in the range
579          *  specified when constructing the table.
580          */
581         template <int numFuncInTable = 3, int funcIndex0 = 0, int funcIndex1 = 1, int funcIndex2 = 2, typename T>
582         void
583         evaluateDerivative(T     r,
584                            T *   derivativeValue0,
585                            T *   derivativeValue1,
586                            T *   derivativeValue2) const
587         {
588             rangeCheck(r);
589             GMX_ASSERT(numFuncInTable == numFuncInTable_, "Evaluation method not matching number of functions in table");
590             GMX_ASSERT(funcIndex0 < numFuncInTable && funcIndex1 < numFuncInTable && funcIndex2 < numFuncInTable, "Function index not in range of the number of tables");
591
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);
595             T     Y, F, G, H;
596
597             // Load Derivative, Delta, Function, and Zero values for each table point.
598             // The 4 refers to these four values - not any SIMD width.
599             gatherLoadBySimdIntTranspose<4*numFuncInTable>(yfghMultiTableData_.data() + 4 * funcIndex0, tabIndex, &Y, &F, &G, &H);
600             *derivativeValue0 = tableScale_ * fma(fma(T(3.0)*H, eps, T(2.0)*G), eps, F);
601
602             gatherLoadBySimdIntTranspose<4*numFuncInTable>(yfghMultiTableData_.data() + 4 * funcIndex1, tabIndex, &Y, &F, &G, &H);
603             *derivativeValue1 = tableScale_ * fma(fma(T(3.0)*H, eps, T(2.0)*G), eps, F);
604
605             gatherLoadBySimdIntTranspose<4*numFuncInTable>(yfghMultiTableData_.data() + 4 * funcIndex2, tabIndex, &Y, &F, &G, &H);
606             *derivativeValue2 = tableScale_ * fma(fma(T(3.0)*H, eps, T(2.0)*G), eps, F);
607         }
608
609         /*! \brief Return the table spacing (distance between points)
610          *
611          *  You should never have to use this for normal code, but due to the
612          *  way tables are constructed internally we need this in the unit tests
613          *  to check relative tolerances over each interval.
614          *
615          *  \return table spacing.
616          */
617         real
618         tableSpacing() const { return 1.0 / tableScale_; }
619
620     private:
621
622         std::size_t             numFuncInTable_; //!< Number of separate tabluated functions
623         std::pair<real, real>   range_;          //!< Range for which table evaluation is allowed
624         real                    tableScale_;     //!< Table scale (inverse of spacing between points)
625
626         /*! \brief Vector with combined table data to save calculations after lookup.
627          *
628          *  For table point i, this vector contains the four coefficients
629          *  Y,F,G,H that we use to express the function value as
630          *  V(x)  = Y + F e + G e^2 + H e^3, where e is the epsilon offset from
631          *  the nearest table point.
632          *
633          *  To allow aligned SIMD loads we need to use an aligned allocator for
634          *  this container.
635          */
636         std::vector<real, AlignedAllocator<real> >  yfghMultiTableData_;
637
638         // There should never be any reason to copy the table since it is read-only
639         GMX_DISALLOW_COPY_AND_ASSIGN(CubicSplineTable);
640 };
641
642
643 }      // namespace gmx
644
645 #endif // GMX_TABLES_CUBICSPLINETABLE_H