QMCPACK
OneDimCubicSpline.h
Go to the documentation of this file.
1 //////////////////////////////////////////////////////////////////////////////////////
2 // This file is distributed under the University of Illinois/NCSA Open Source License.
3 // See LICENSE file in top directory for details.
4 //
5 // Copyright (c) 2016 Jeongnim Kim and QMCPACK developers.
6 //
7 // File developed by: Ken Esler, kpesler@gmail.com, University of Illinois at Urbana-Champaign
8 // Jeongnim Kim, jeongnim.kim@gmail.com, University of Illinois at Urbana-Champaign
9 // Miguel Morales, moralessilva2@llnl.gov, Lawrence Livermore National Laboratory
10 // Jeremy McMinnis, jmcminis@gmail.com, University of Illinois at Urbana-Champaign
11 // Mark A. Berrill, berrillma@ornl.gov, Oak Ridge National Laboratory
12 //
13 // File created by: Jeongnim Kim, jeongnim.kim@gmail.com, University of Illinois at Urbana-Champaign
14 //////////////////////////////////////////////////////////////////////////////////////
15 
16 
17 #ifndef QMCPLUSPLUS_GRID_FUNCTOR_CUBIC_SPLINE_H
18 #define QMCPLUSPLUS_GRID_FUNCTOR_CUBIC_SPLINE_H
19 
21 #include "Numerics/SplineSolvers.h"
22 
23 namespace qmcplusplus
24 {
25 /**Perform One-Dimensional Cubic Spline Interpolation using M-relation.
26  *
27  Given a function evaluated on a grid \f$ \{x_i\},
28  i=1\ldots N, \f$ such that \f$ y_i = y(x_i), \f$ we would like to
29  interpolate for a point \f$ x \f$ in the interval \f$ [x_j,x_{j+1}]. \f$
30  The linear interpolation formula
31  \f[
32  y = Ay_j + By_{j+1}
33  \f]
34  where
35  \f[
36  A = \frac{x_{j+1}-x}{x_{j+1}-x_j} \;\;\;\;\;\;\;\;\;\;\;\;\;
37  B = 1-A = \frac{x-x_{j+1}}{x_{j+1}-x_j}
38  \f]
39  Satisfies the conditions at the endpoints \f$ x_j \mbox{ and } x_{j+1},\f$
40  but suffers from some major drawbacks. The problem with this approach is
41  that over the range of the function \f$ [x_1,x_N] \f$ we have a series of
42  piecewise linear equations with a zero second derivative within each interval
43  and an undefined or infinite second derivative at the interval boundaries,
44  the grid points \f$ \{x_i\}. \f$ Ideally we would like to construct an
45  interpolation function with a smooth first derivative and a continuous second
46  derivative both within the intervals and at the the grid points.
47 
48  By adding a cubic polynomial to the linear interpolation equation within
49  each interval, we can construct an interpolation function that varies
50  linearly in the second derivative. Assume for a moment that we have the
51  values of the second derivative evaluated at each grid point,
52  \f$ y_i'' = d^2y(x_i)/dx^2, i=1\ldots N. \f$ Now we can construct a cubic
53  polynomial that has the correct second derivatives \f$y_j'' \mbox{ and }
54  y_{j+1}''\f$ at the endpoints and also evaluates to zero at the endpoints.
55  The reason the polynomial must be zero at the endpoints is to not spoil
56  the agreement that is already built into the linear function. A function
57  constructed from these principals is given by the equation
58  \f[
59  y = Ay_j + By_{j+1} + Cy_j'' + Dy_{j+1}''
60  \f]
61  where
62  \f[
63  C = \frac{1}{6}(A^3-A)(x_{j+1}-x_j)^2 \;\;\;\;\;\;\;
64  D = \frac{1}{6}(B^3-B)(x_{j+1}-x_j)^2.
65  \f]
66 
67 
68  To explicitly check that this function does indeed satisfy the conditions
69  at the endpoints take the derivatives
70  \f[
71  \frac{dy}{dx} = \frac{y_{j+1}-y_j}{x_{j+1}-x_j}
72  - \frac{3A^2-1}{6}(x_{j+1}-x_j)y_j''
73  + \frac{3B^2-1}{6}(x_{j+1}-x_j)y_{j+1}''
74  \f]
75  and
76  \f[
77  \frac{d^2y}{dx^2} = Ay_j'' + By_{j+1}''.
78  \f]
79  The second derivative is continuous across the boundary between two
80  intervals, e.g. \f$ [x_{j-1},x_j] \f$ and \f$ [x_j,x_{j+1}], \f$ and
81  obeys the conditions at the endpoints since at \f$ x=x_j, (A=1,B=0) \f$
82  and at \f$ x=x_{j+1}, (A=0,B=1). \f$
83 
84 
85  We had made the assumption that the values of the second derivative are
86  known at the grid points, which they are not. By imposing the condition
87  that the first derivative is smooth and continuous across the boundary
88  between two intervals it is possible to derive a set of equations to
89  generate the \f$ y_i''\f$'s. Evaluate the equation for the first
90  derivative at \f$x=x_j\f$ in the inverval \f$ [x_{j-1},x_j] \f$ and set
91  it equal to the same equation evaluated at \f$x=x_j\f$ in the inverval
92  \f$ [x_j,x_{j+1}]; \f$ rearranging the terms
93 
94  \f[
95  \frac{x_j-x_{j+1}}{6}y_{j+1}'' + \frac{x_{j+1}-x_{j-1}}{3}y_j''
96  + \frac{x_{j+1}-x_j}{6}y_{j+1}'' = \frac{y_{j+1}-y_j}{x_{j+1}-x_j}
97  - \frac{y_j-y_{j+1}}{x_j-x_{j+1}},
98  \f]
99  where \f$ j=2\ldots N-1.\f$ To generate a unique solution for the system
100  of \f$N-2\f$ equations we have to impose boundary conditions at \f$x_1
101  \mbox{ and } x_N,\f$ the possibilities being either to set \f$y_1''
102  \mbox{ and } y_N''\f$ to zero, the natural cubic spline, or, if you want
103  to make the first derivative at the boundaries to have a specified value,
104  use \f$y_1' \mbox{ and } y_N'\f$ to calculate the second derivatives at
105  the endpoints using equation.
106  *
107 */
108 
109 template<class T>
111 {
112 private:
113  T dist;
114  T dL;
115  T dLinv;
116  T cL;
117  T cR;
118  T h6;
119  T q1;
120  T q2;
121  T dq1;
122  T dq2;
123 
124 public:
126  {
127  dLinv = 1.0 / dL;
128  cL = dist * dLinv;
129  cR = (dL - dist) * dLinv;
130  h6 = dL / 6.0;
131  q1 = cR * (cR * cR - 1.0) * h6 * dL;
132  q2 = cL * (cL * cL - 1.0) * h6 * dL;
133  dq1 = h6 * (1.0 - 3.0 * cR * cR);
134  dq2 = h6 * (3.0 * cL * cL - 1.0);
135  }
136 
137  template<typename T1>
138  inline T1 cubicInterpolate(T1 y1, T1 y2, T1 d2y1, T1 d2y2) const
139  {
140  return cR * y1 + cL * y2 + q1 * d2y1 + q2 * d2y2;
141  }
142 
143  template<typename T1>
144  inline T1 cubicInterpolateSecondDeriv(T1 y1, T1 y2, T1 d2y1, T1 d2y2, T1& du, T1& d2u) const
145  {
146  du = dLinv * (y2 - y1) + dq1 * d2y1 + dq2 * d2y2;
147  d2u = cR * d2y1 + cL * d2y2;
148  return cR * y1 + cL * y2 + q1 * d2y1 + q2 * d2y2;
149  }
150 };
151 
152 template<class Td, class Tg = Td, class CTd = Vector<Td>, class CTg = Vector<Tg>>
153 class OneDimCubicSpline : public OneDimGridFunctor<Td, Tg, CTd, CTg>
154 {
155 public:
159  using data_type = typename base_type::data_type;
160  using grid_type = typename base_type::grid_type;
161 
162  using base_type::d2Y;
163  using base_type::dY;
164  using base_type::m_grid;
165  using base_type::m_Y;
166  using base_type::Y;
167  //using base_type::FirstAddress;
168 
170 
171  using base_type::NumNodes;
172 
178 
179  //OneDimCubicSpline(const OneDimCubicSpline<Td,Tg,CTd,CTg>& rhs):
180  // base_type(rhs), m_Y2(rhs.m_Y2)
181  // { }
182 
183  OneDimCubicSpline(std::unique_ptr<grid_type> gt = std::unique_ptr<grid_type>()) : base_type(std::move(gt)) {}
184 
185  template<class VV>
186  OneDimCubicSpline(std::unique_ptr<grid_type> gt, const VV& nv)
187  : base_type(std::move(gt)), first_deriv(0.0), last_deriv(0.0)
188  {
189  m_Y.resize(nv.size());
190  m_Y2.resize(nv.size());
191  copy(nv.begin(), nv.end(), m_Y.data());
192  }
193 
194  OneDimCubicSpline* makeClone() const { return new OneDimCubicSpline(*this); }
195 
197  {
198  m_Y2.resize(a.m_Y2.size());
199  m_Y2 = a.m_Y2;
201  r_min = a.r_min;
202  r_max = a.r_max;
205  }
206 
207  inline value_type splint(point_type r) const override
208  {
209  if (r < r_min)
210  {
211  return m_Y[0] + first_deriv * (r - r_min);
212  }
213  else if (r >= r_max)
214  {
215  return ConstValue;
216  }
217 
218  value_type dist;
219  int Loc = m_grid->getIndexAndDistanceFromGridPoint(r, dist);
220  value_type dL = m_grid->dr(Loc);
221  CubicSplineEvaluator<value_type> eval(dist, dL);
222  return eval.cubicInterpolate(m_Y[Loc], m_Y[Loc + 1], m_Y2[Loc], m_Y2[Loc + 1]);
223  }
224 
225  /** Interpolation to evaluate the function and itsderivatives.
226  *@param r the radial distance
227  *@param du return the derivative
228  *@param d2u return the 2nd derivative
229  *@return the value of the function
230  */
231  inline value_type splint(point_type r, value_type& du, value_type& d2u) const override
232  {
233  if (r < r_min)
234  //linear-extrapolation returns y[0]+y'*(r-r[0])
235  {
236  du = first_deriv;
237  d2u = 0.0;
238  return m_Y[0] + first_deriv * (r - r_min);
239  }
240  else if (r >= r_max)
241  {
242  du = 0.0;
243  d2u = 0.0;
244  return ConstValue;
245  }
246  value_type dist;
247  int Loc = m_grid->getIndexAndDistanceFromGridPoint(r, dist);
248  value_type dL = m_grid->dr(Loc);
249  CubicSplineEvaluator<value_type> eval(dist, dL);
250  return eval.cubicInterpolateSecondDeriv(m_Y[Loc], m_Y[Loc + 1], m_Y2[Loc], m_Y2[Loc + 1], du, d2u);
251  }
252 
253  /** Interpolation to evaluate the function and itsderivatives.
254  *@param r the radial distance
255  *@param du return the derivative
256  *@param d2u return the 2nd derivative
257  *@param d3u return the 3nd derivative
258  *@return the value of the function
259  */
261  {
262  if (r < r_min)
263  //linear-extrapolation returns y[0]+y'*(r-r[0])
264  {
265  du = first_deriv;
266  d2u = 0.0;
267  return m_Y[0] + first_deriv * (r - r_min);
268  }
269  else if (r >= r_max)
270  {
271  du = 0.0;
272  d2u = 0.0;
273  return ConstValue;
274  }
275  // no third derivatives yet, only for templating purposes
276  d3u = 0.0;
277 
278  value_type dist;
279  int Loc = m_grid->getIndexAndDistanceFromGridPoint(r, dist);
280  value_type dL = m_grid->dr(Loc);
281  CubicSplineEvaluator<value_type> eval(dist, dL);
282  return eval.cubicInterpolateSecondDeriv(m_Y[Loc], m_Y[Loc + 1], m_Y2[Loc], m_Y2[Loc + 1], du, d2u);
283  }
284  /** Evaluate the 2nd derivative on the grid points
285  *\param imin the index of the first valid data point
286  *\param yp1 the derivative at the imin-th grid point
287  *\param imax the index of the last valid data point
288  *\param ypn the derivative at the imax-th grid point
289  *
290  *In general, a grid is shared by several OneDimCubicSpline objects
291  *and each object can have its own range of valid grid points.
292  *r_min and r_max are used to specify the range.
293  */
294  inline void spline(int imin, value_type yp1, int imax, value_type ypn) override
295  {
296  first_deriv = yp1;
297  last_deriv = ypn;
298  r_min = m_grid->r(imin);
299  r_max = m_grid->r(imax);
300  int npts(this->size());
301  m_Y2.resize(npts);
302  m_Y2 = 0.0;
303  CubicSplineSolve(m_grid->data() + imin, m_Y.data() + imin, npts - imin, yp1, ypn, m_Y2.data() + imin);
304  ConstValue = m_Y[imax];
305  //FirstAddress[0]=m_Y.data()+imin;
306  //FirstAddress[2]=m_Y2.data()+imin;
307  }
308 
309  inline void spline() override { spline(0, 0.0, m_grid->size() - 1, 0.0); }
310 };
311 
312 
313 } // namespace qmcplusplus
314 #endif
void CubicSplineSolve(T const *x, T const *y, int n, T yp0, T ypn, T *y2)
Solve for second derivatives for cubic splines.
Definition: SplineSolvers.h:33
T1 cubicInterpolate(T1 y1, T1 y2, T1 d2y1, T1 d2y2) const
CTd data_type
the type of the containers Y, dY and d2Y
typename base_type::value_type value_type
helper functions for EinsplineSetBuilder
Definition: Configuration.h:43
Implement One-Dimensional function on a radial grid.
OneDimCubicSpline(const OneDimCubicSpline &a)
int NumNodes
the number of nodes
typename base_type::grid_type grid_type
value_type splint(point_type r, value_type &du, value_type &d2u, value_type &d3u) const
Interpolation to evaluate the function and itsderivatives.
typename base_type::point_type point_type
value_type splint(point_type r) const override
void spline(int imin, value_type yp1, int imax, value_type ypn) override
Evaluate the 2nd derivative on the grid points.
void copy(const Array< T1, 3 > &src, Array< T2, 3 > &dest)
Definition: Blitz.h:639
Perform One-Dimensional Cubic Spline Interpolation using M-relation.
Td value_type
the type of the value on a grid
T1 cubicInterpolateSecondDeriv(T1 y1, T1 y2, T1 d2y1, T1 d2y2, T1 &du, T1 &d2u) const
value_type Y
store the value of the function
value_type d2Y
store the second derivative of the function
OneDimGridBase< Tg, CTg > grid_type
the grid type
typename base_type::data_type data_type
int size() const
return the number of data points
value_type dY
store the derivative of the function
value_type splint(point_type r, value_type &du, value_type &d2u) const override
Interpolation to evaluate the function and itsderivatives.
OneDimCubicSpline(std::unique_ptr< grid_type > gt, const VV &nv)
OneDimCubicSpline(std::unique_ptr< grid_type > gt=std::unique_ptr< grid_type >())
Tg point_type
the type of the grid value
point_type r(int i) const
return the grid point at index i
std::unique_ptr< grid_type > m_grid
pointer to the radial grid
data_type m_Y
data for the function on the grid
OneDimCubicSpline * makeClone() const