Update instructions in containers.rst
[gromacs.git] / src / gromacs / tables / cubicsplinetable.cpp
blob9f0bf2b451aba1bd28df6a029a1c024d72f8f209
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2016,2017,2018,2019, 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.
36 /*! \internal \file
37 * \brief
38 * Implements classes for cubic spline table functions
40 * \author Erik Lindahl <erik.lindahl@gmail.com>
41 * \ingroup module_tables
43 #include "gmxpre.h"
45 #include "cubicsplinetable.h"
47 #include <cmath>
49 #include <algorithm>
50 #include <functional>
51 #include <initializer_list>
52 #include <utility>
53 #include <vector>
55 #include "gromacs/tables/tableinput.h"
56 #include "gromacs/utility/alignedallocator.h"
57 #include "gromacs/utility/arrayref.h"
58 #include "gromacs/utility/exceptions.h"
59 #include "gromacs/utility/real.h"
61 #include "splineutil.h"
63 namespace gmx
66 namespace
69 /*! \brief Calculate table elements from function/derivative data
71 * \param functionValue0 Function value for the present table index
72 * \param functionValue1 Function value for the next table index
73 * \param derivativeValue0 Derivative value for the present table index
74 * \param derivativeValue1 Derivative value for the next table index
75 * \param spacing Distance between table points
76 * \param Y Function value for table index
77 * \param F Component to multiply with offset eps
78 * \param G Component to multiply with eps^2
79 * \param H Component to multiply with eps^3
81 void calculateCubicSplineCoefficients(double functionValue0,
82 double functionValue1,
83 double derivativeValue0,
84 double derivativeValue1,
85 double spacing,
86 double* Y,
87 double* F,
88 double* G,
89 double* H)
91 *Y = functionValue0;
92 *F = spacing * derivativeValue0;
93 *G = 3.0 * (functionValue1 - functionValue0) - spacing * (derivativeValue1 + 2.0 * derivativeValue0);
94 *H = -2.0 * (functionValue1 - functionValue0) + spacing * (derivativeValue1 + derivativeValue0);
97 /*! \brief Perform cubic spline interpolation in interval from function/derivative
99 * \param functionValue0 Function value for the present table index
100 * \param functionValue1 Function value for the next table index
101 * \param derivativeValue0 Derivative value for the present table index
102 * \param derivativeValue1 Derivative value for the next table index
103 * \param spacing Distance between table points
104 * \param eps Offset from lower table point for evaluation
105 * \param[out] interpolatedFunctionValue Output function value
106 * \param[out] interpolatedDerivativeValue Output derivative value
108 void cubicSplineInterpolationFromFunctionAndDerivative(double functionValue0,
109 double functionValue1,
110 double derivativeValue0,
111 double derivativeValue1,
112 double spacing,
113 double eps,
114 double* interpolatedFunctionValue,
115 double* interpolatedDerivativeValue)
117 double Y, F, G, H;
119 calculateCubicSplineCoefficients(functionValue0, functionValue1, derivativeValue0,
120 derivativeValue1, spacing, &Y, &F, &G, &H);
122 double Fp = fma(fma(H, eps, G), eps, F);
124 *interpolatedFunctionValue = fma(Fp, eps, Y);
125 *interpolatedDerivativeValue = fma(eps, fma(2.0 * eps, H, G), Fp) / spacing;
129 /*! \brief Construct the data for a single cubic table from analytical functions
131 * \param[in] function Analytical functiojn
132 * \param[in] derivative Analytical derivative
133 * \param[in] range Upper/lower limit of region to tabulate
134 * \param[in] spacing Distance between table points
135 * \param[out] yfghTableData Output cubic spline table with Y,F,G,H entries
137 void fillSingleCubicSplineTableData(const std::function<double(double)>& function,
138 const std::function<double(double)>& derivative,
139 const std::pair<real, real>& range,
140 double spacing,
141 std::vector<real>* yfghTableData)
143 int endIndex = static_cast<int>(range.second / spacing + 2);
145 yfghTableData->resize(4 * endIndex);
147 double maxMagnitude = 0.0001 * GMX_REAL_MAX;
148 bool functionIsInRange = true;
149 std::size_t lastIndexInRange = endIndex - 1;
151 for (int i = endIndex - 1; i >= 0; i--)
153 double x = i * spacing;
154 double tmpFunctionValue;
155 double tmpDerivativeValue;
156 double nextHigherFunction;
157 double nextHigherDerivative;
158 double Y, F, G, H;
160 if (range.first > 0 && i == 0)
162 // Avoid x==0 if it is not in the range, since it can lead to
163 // singularities even if the value for i==1 was within or required magnitude
164 functionIsInRange = false;
167 if (functionIsInRange)
169 tmpFunctionValue = function(x);
170 tmpDerivativeValue = derivative(x);
171 nextHigherFunction = ((i + 1) < endIndex) ? function(x + spacing) : 0.0;
172 nextHigherDerivative = ((i + 1) < endIndex) ? derivative(x + spacing) : 0.0;
174 if (std::abs(tmpFunctionValue) > maxMagnitude || std::abs(tmpDerivativeValue) > maxMagnitude)
176 functionIsInRange = false; // Once this happens, it never resets to true again
180 if (functionIsInRange)
182 calculateCubicSplineCoefficients(tmpFunctionValue, nextHigherFunction, tmpDerivativeValue,
183 nextHigherDerivative, spacing, &Y, &F, &G, &H);
184 lastIndexInRange--;
186 else
188 double lastIndexY = (*yfghTableData)[4 * lastIndexInRange];
189 double lastIndexF = (*yfghTableData)[4 * lastIndexInRange + 1];
191 Y = lastIndexY + lastIndexF * (i - lastIndexInRange);
192 F = lastIndexF;
193 G = 0.0;
194 H = 0.0;
197 (*yfghTableData)[4 * i] = Y;
198 (*yfghTableData)[4 * i + 1] = F;
199 (*yfghTableData)[4 * i + 2] = G;
200 (*yfghTableData)[4 * i + 3] = H;
205 /*! \brief Construct the data for a single cubic table from vector data
207 * \param[in] function Input vector with function data
208 * \param[in] derivative Input vector with derivative data
209 * \param[in] inputSpacing Distance between points in input vectors
210 * \param[in] range Upper/lower limit of region to tabulate
211 * \param[in] spacing Distance between table points
212 * \param[out] yfghTableData Output cubic spline table with Y,F,G,H entries
214 void fillSingleCubicSplineTableData(ArrayRef<const double> function,
215 ArrayRef<const double> derivative,
216 double inputSpacing,
217 const std::pair<real, real>& range,
218 double spacing,
219 std::vector<real>* yfghTableData)
221 int endIndex = static_cast<int>(range.second / spacing + 2);
223 std::vector<double> tmpFunction(endIndex);
224 std::vector<double> tmpDerivative(endIndex);
226 double maxMagnitude = 0.0001 * GMX_REAL_MAX;
227 bool functionIsInRange = true;
228 std::size_t lastIndexInRange = endIndex - 1;
230 // Interpolate function and derivative values in positions needed for output
231 for (int i = endIndex - 1; i >= 0; i--)
233 double x = i * spacing;
234 double xtab = x / inputSpacing;
235 int index = static_cast<int>(xtab);
236 double eps = xtab - index;
238 if (range.first > 0 && i == 0)
240 // Avoid x==0 if it is not in the range, since it can lead to
241 // singularities even if the value for i==1 was within or required magnitude
242 functionIsInRange = false;
245 if (functionIsInRange
246 && (std::abs(function[index]) > maxMagnitude || std::abs(derivative[index]) > maxMagnitude))
248 functionIsInRange = false; // Once this happens, it never resets to true again
251 if (functionIsInRange)
253 cubicSplineInterpolationFromFunctionAndDerivative(
254 function[index], function[index + 1], derivative[index], derivative[index + 1],
255 inputSpacing, eps, &(tmpFunction[i]), &(tmpDerivative[i]));
256 lastIndexInRange--;
258 else
260 double lastIndexFunction = tmpFunction[lastIndexInRange];
261 double lastIndexDerivative = tmpDerivative[lastIndexInRange];
262 tmpFunction[i] = lastIndexFunction + lastIndexDerivative * (i - lastIndexInRange) * spacing;
263 tmpDerivative[i] = lastIndexDerivative;
267 yfghTableData->resize(4 * endIndex);
269 for (int i = 0; i < endIndex; i++)
271 double Y, F, G, H;
273 double nextFunction = ((i + 1) < endIndex) ? tmpFunction[i + 1] : 0.0;
274 double nextDerivative = ((i + 1) < endIndex) ? tmpDerivative[i + 1] : 0.0;
276 calculateCubicSplineCoefficients(tmpFunction[i], nextFunction, tmpDerivative[i],
277 nextDerivative, spacing, &Y, &F, &G, &H);
278 (*yfghTableData)[4 * i] = Y;
279 (*yfghTableData)[4 * i + 1] = F;
280 (*yfghTableData)[4 * i + 2] = G;
281 (*yfghTableData)[4 * i + 3] = H;
285 } // namespace
288 #if GMX_DOUBLE
289 const real CubicSplineTable::defaultTolerance = 1e-10;
290 #else
291 const real CubicSplineTable::defaultTolerance = 10.0 * GMX_FLOAT_EPS;
292 #endif
294 CubicSplineTable::CubicSplineTable(std::initializer_list<AnalyticalSplineTableInput> analyticalInputList,
295 const std::pair<real, real>& range,
296 real tolerance) :
297 numFuncInTable_(analyticalInputList.size()),
298 range_(range)
300 // Sanity check on input values
301 if (range_.first < 0.0 || (range_.second - range_.first) < 0.001)
303 GMX_THROW(InvalidInputError(
304 "Range to tabulate cannot include negative values and must span at least 0.001"));
307 if (tolerance < GMX_REAL_EPS)
309 GMX_THROW(ToleranceError("Table tolerance cannot be smaller than GMX_REAL_EPS"));
312 double minQuotient = GMX_REAL_MAX;
314 // loop over all functions to find smallest spacing
315 for (const auto& thisFuncInput : analyticalInputList)
319 internal::throwUnlessDerivativeIsConsistentWithFunction(
320 thisFuncInput.function, thisFuncInput.derivative, range_);
322 catch (gmx::GromacsException& ex)
324 ex.prependContext("Error generating cubic spline table for function '"
325 + thisFuncInput.desc + "'");
326 throw;
328 // Calculate the required table spacing h. The error we make with a third order polynomial
329 // (second order for derivative) will be described by the fourth-derivative correction term.
331 // This means we can compute the required spacing as h =
332 // 0.5*cbrt(72*sqrt(3)*tolerance**min(f'/f'''')), where f'/f'''' is the first and fourth
333 // derivative of the function, respectively. Since we already have an analytical form of the
334 // derivative, we reduce the numerical errors by calculating the quotient of the function
335 // and third derivative of the input-derivative-analytical function instead.
337 double thisMinQuotient = internal::findSmallestQuotientOfFunctionAndThirdDerivative(
338 thisFuncInput.derivative, range_);
340 minQuotient = std::min(minQuotient, thisMinQuotient);
343 double spacing = 0.5 * std::cbrt(72.0 * std::sqrt(3.0) * tolerance * minQuotient);
345 tableScale_ = 1.0 / spacing;
347 if (range_.second * tableScale_ > 2e6)
349 GMX_THROW(
350 ToleranceError("Over a million points would be required for table; decrease range "
351 "or increase tolerance"));
354 // Loop over all tables again.
355 // Here we create the actual table for each function, and then
356 // combine them into a multiplexed table function.
357 std::size_t funcIndex = 0;
359 for (const auto& thisFuncInput : analyticalInputList)
363 std::vector<real> tmpYfghTableData;
365 fillSingleCubicSplineTableData(thisFuncInput.function, thisFuncInput.derivative, range_,
366 spacing, &tmpYfghTableData);
368 internal::fillMultiplexedTableData(tmpYfghTableData, &yfghMultiTableData_, 4,
369 numFuncInTable_, funcIndex);
371 funcIndex++;
373 catch (gmx::GromacsException& ex)
375 ex.prependContext("Error generating cubic spline table for function '"
376 + thisFuncInput.desc + "'");
377 throw;
383 CubicSplineTable::CubicSplineTable(std::initializer_list<NumericalSplineTableInput> numericalInputList,
384 const std::pair<real, real>& range,
385 real tolerance) :
386 numFuncInTable_(numericalInputList.size()),
387 range_(range)
389 // Sanity check on input values
390 if (range.first < 0.0 || (range.second - range.first) < 0.001)
392 GMX_THROW(InvalidInputError(
393 "Range to tabulate cannot include negative values and must span at least 0.001"));
396 if (tolerance < GMX_REAL_EPS)
398 GMX_THROW(ToleranceError("Table tolerance cannot be smaller than GMX_REAL_EPS"));
401 double minQuotient = GMX_REAL_MAX;
403 // loop over all functions to find smallest spacing
404 for (auto thisFuncInput : numericalInputList)
408 // We do not yet know what the margin is, but we need to test that we at least cover
409 // the requested range before starting to calculate derivatives
410 if (thisFuncInput.function.size() < range_.second / thisFuncInput.spacing + 1)
412 GMX_THROW(
413 InconsistentInputError("Table input vectors must cover requested range, "
414 "and a margin beyond the upper endpoint"));
417 if (thisFuncInput.function.size() != thisFuncInput.derivative.size())
419 GMX_THROW(InconsistentInputError(
420 "Function and derivative vectors have different lengths"));
423 internal::throwUnlessDerivativeIsConsistentWithFunction(
424 thisFuncInput.function, thisFuncInput.derivative, thisFuncInput.spacing, range_);
426 catch (gmx::GromacsException& ex)
428 ex.prependContext("Error generating cubic spline table for function '"
429 + thisFuncInput.desc + "'");
430 throw;
432 // Calculate the required table spacing h. The error we make with linear interpolation
433 // of the derivative will be described by the third-derivative correction term.
434 // This means we can compute the required spacing as h = sqrt(12*tolerance*min(f'/f''')),
435 // where f'/f''' is the first and third derivative of the function, respectively.
437 double thisMinQuotient = internal::findSmallestQuotientOfFunctionAndThirdDerivative(
438 thisFuncInput.derivative, thisFuncInput.spacing, range_);
440 minQuotient = std::min(minQuotient, thisMinQuotient);
443 double spacing = std::cbrt(72.0 * std::sqrt(3.0) * tolerance * minQuotient);
445 tableScale_ = 1.0 / spacing;
447 if (range_.second * tableScale_ > 1e6)
449 GMX_THROW(
450 ToleranceError("Requested tolerance would require over a million points in table"));
453 // Loop over all tables again.
454 // Here we create the actual table for each function, and then
455 // combine them into a multiplexed table function.
456 std::size_t funcIndex = 0;
458 for (auto thisFuncInput : numericalInputList)
462 if (spacing < thisFuncInput.spacing)
464 GMX_THROW(
465 ToleranceError("Input vector spacing cannot achieve tolerance requested"));
468 std::vector<real> tmpYfghTableData;
470 fillSingleCubicSplineTableData(thisFuncInput.function, thisFuncInput.derivative,
471 thisFuncInput.spacing, range, spacing, &tmpYfghTableData);
473 internal::fillMultiplexedTableData(tmpYfghTableData, &yfghMultiTableData_, 4,
474 numFuncInTable_, funcIndex);
476 funcIndex++;
478 catch (gmx::GromacsException& ex)
480 ex.prependContext("Error generating cubic spline table for function '"
481 + thisFuncInput.desc + "'");
482 throw;
487 } // namespace gmx