laura is hosted by Hepforge, IPPP Durham
Laura++  v3r2
A maximum likelihood fitting package for performing Dalitz-plot analysis.
Lau1DCubicSpline.cc
Go to the documentation of this file.
1 
2 // Copyright University of Warwick 2004 - 2015.
3 // Distributed under the Boost Software License, Version 1.0.
4 // (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5 
6 // Authors:
7 // Thomas Latham
8 // John Back
9 // Paul Harrison
10 
15 #include <cmath>
16 #include <cstdlib>
17 #include <iostream>
18 
19 #include <TH2.h>
20 #include <TSystem.h>
21 
22 #include "Lau1DCubicSpline.hh"
23 
24 Lau1DCubicSpline::Lau1DCubicSpline(const std::vector<Double_t>& xs, const std::vector<Double_t>& ys,
25  LauSplineType type,
26  LauSplineBoundaryType leftBound,
27  LauSplineBoundaryType rightBound,
28  Double_t dydx0, Double_t dydxn) :
29  nKnots_(xs.size()),
30  x_(xs),
31  y_(ys),
32  type_(type),
33  leftBound_(leftBound),
34  rightBound_(rightBound),
35  dydx0_(dydx0),
36  dydxn_(dydxn)
37 {
38  init();
39 }
40 
42 {
43 }
44 
45 Double_t Lau1DCubicSpline::evaluate(Double_t x) const
46 {
47  // do not attempt to extrapolate the spline
48  if( x<x_[0] || x>x_[nKnots_-1] ) {
49  std::cout << "WARNING in Lau1DCubicSpline::evaluate : function is only defined between " << x_[0] << " and " << x_[nKnots_-1] << std::endl;
50  std::cout << " value at " << x << " returned as 0" << std::endl;
51  return 0.;
52  }
53 
54  // first determine which 'cell' of the spline x is in
55  // cell i runs from knot i to knot i+1
56  Int_t cell(0);
57 
58  while( x > x_[cell+1] ) {
59  ++cell;
60  }
61 
62  // obtain x- and y-values of the neighbouring knots
63  Double_t xLow = x_[cell];
64  Double_t xHigh = x_[cell+1];
65  Double_t yLow = y_[cell];
66  Double_t yHigh = y_[cell+1];
67 
69  return yHigh*(x-xLow)/(xHigh-xLow) + yLow*(xHigh-x)/(xHigh-xLow);
70  }
71 
72  // obtain t, the normalised x-coordinate within the cell,
73  // and the coefficients a and b, which are defined in cell i as:
74  //
75  // a_i = k_i *(x_i+1 - x_i) - (y_i+1 - y_i),
76  // b_i = -k_i+1*(x_i+1 - x_i) + (y_i+1 - y_i)
77  //
78  // where k_i is (by construction) the first derivative at knot i
79  Double_t t = (x - xLow) / (xHigh - xLow);
80  Double_t a = dydx_[cell] * (xHigh - xLow) - (yHigh - yLow);
81  Double_t b = -1.*dydx_[cell+1] * (xHigh - xLow) + (yHigh - yLow);
82 
83  Double_t retVal = (1 - t) * yLow + t * yHigh + t * (1 - t) * ( a * (1 - t) + b * t );
84 
85  return retVal;
86 }
87 
88 void Lau1DCubicSpline::updateYValues(const std::vector<Double_t>& ys)
89 {
90  y_ = ys;
91  this->calcDerivatives();
92 }
93 
95 {
96  if(type_ != type) {
97  type_ = type;
98  this->calcDerivatives();
99  }
100 }
101 
103  LauSplineBoundaryType rightBound,
104  Double_t dydx0, Double_t dydxn)
105 {
106  Bool_t updateDerivatives(kFALSE);
107 
108  if(leftBound_ != leftBound || rightBound_ != rightBound) {
109  leftBound_ = leftBound;
110  rightBound_ = rightBound;
111  updateDerivatives = kTRUE;
112  }
113 
114  if(dydx0_ != dydx0) {
115  dydx0_ = dydx0;
116  if(leftBound_ == Lau1DCubicSpline::Clamped) updateDerivatives = kTRUE;
117  }
118 
119  if(dydxn_ != dydxn) {
120  dydxn_ = dydxn;
121  if(rightBound_ == Lau1DCubicSpline::Clamped) updateDerivatives = kTRUE;
122  }
123 
124  if(updateDerivatives) {
125  this->calcDerivatives();
126  }
127 }
128 
130 {
131  if( y_.size() != x_.size()) {
132  std::cout << "ERROR in Lau1DCubicSpline::init : The number of y-values given does not match the number of x-values" << std::endl;
133  std::cout << " Found " << y_.size() << ", expected " << x_.size() << std::endl;
134  gSystem->Exit(EXIT_FAILURE);
135  }
136 
137  dydx_.insert(dydx_.begin(),nKnots_,0.);
138 
139  a_.insert(a_.begin(),nKnots_,0.);
140  b_.insert(b_.begin(),nKnots_,0.);
141  c_.insert(c_.begin(),nKnots_,0.);
142  d_.insert(d_.begin(),nKnots_,0.);
143 
144  this->calcDerivatives();
145 }
146 
148 {
149  switch ( type_ ) {
151  this->calcDerivativesStandard();
152  break;
154  this->calcDerivativesAkima();
155  break;
157  //derivatives not needed for linear interpolation
158  break;
159  }
160 }
161 
163 {
164  // derivatives are determined such that the second derivative is continuous at internal knots
165 
166  // derivatives, k_i, are the solutions to a set of linear equations of the form:
167  // a_i * k_i-1 + b_i * k+i + c_i * k_i+1 = d_i with a_0 = 0, c_n-1 = 0
168 
169  // this is solved using the tridiagonal matrix algorithm as on en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm
170 
171  // first and last equations give boundary conditions
172  // - for natural boundary, require f''(x) = 0 at end knot
173  // - for 'not a knot' boundary, require f'''(x) continuous at second knot
174  // - for clamped boundary, require predefined value of f'(x) at end knot
175 
176  // non-zero values of a_0 and c_n-1 would give cyclic boundary conditions
177  a_[0] = 0.;
178  c_[nKnots_-1] = 0.;
179 
180  // set left boundary condition
182  b_[0] = 2./(x_[1]-x_[0]);
183  c_[0] = 1./(x_[1]-x_[0]);
184  d_[0] = 3.*(y_[1]-y_[0])/((x_[1]-x_[0])*(x_[1]-x_[0]));
185 
186  } else if(leftBound_ == Lau1DCubicSpline::NotAKnot) {
187  // define the width, h, and the 'slope', delta, of the first cell
188  Double_t h1(x_[1]-x_[0]), h2(x_[2]-x_[0]);
189  Double_t delta1((y_[1]-y_[0])/h1), delta2((y_[2]-y_[1])/h2);
190 
191  // these coefficients can be determined by requiring f'''_0(x_1) = f'''_1(x_1)
192  // the requirement f''_0(x_1) = f''_1(x_1) has been used to remove the dependence on k_2
193  b_[0] = h2;
194  c_[0] = h1+h2;
195  d_[0] = delta1*(2.*h2*h2 + 3.*h1*h2)/(h1+h2) + delta2*5.*h1*h1/(h1+h2);
196 
197  } else { //Clamped
198  b_[0] = 1.;
199  c_[0] = 0.;
200  d_[0] = dydx0_;
201  }
202 
203  // set right boundary condition
205  a_[nKnots_-1] = 1./(x_[nKnots_-1]-x_[nKnots_-2]);
206  b_[nKnots_-1] = 2./(x_[nKnots_-1]-x_[nKnots_-2]);
207  d_[nKnots_-1] = 3.*(y_[nKnots_-1]-y_[nKnots_-2])/((x_[nKnots_-1]-x_[nKnots_-2])*(x_[nKnots_-1]-x_[nKnots_-2]));
208 
210  // define the width, h, and the 'slope', delta, of the last cell
211  Double_t hnm1(x_[nKnots_-1]-x_[nKnots_-2]), hnm2(x_[nKnots_-2]-x_[nKnots_-3]);
212  Double_t deltanm1((y_[nKnots_-1]-y_[nKnots_-2])/hnm1), deltanm2((y_[nKnots_-2]-y_[nKnots_-3])/hnm2);
213 
214  // these coefficients can be determined by requiring f'''_n-3(x_n-2) = f'''_n-2(x_n-2)
215  // the requirement f''_n-3(x_n-2) = f''_n-2(x_n-2) has been used to remove
216  // the dependence on k_n-3
217  a_[nKnots_-1] = hnm2 + hnm1;
218  b_[nKnots_-1] = hnm1;
219  d_[nKnots_-1] = deltanm2*hnm1*hnm1/(hnm2+hnm1) + deltanm1*(2.*hnm2*hnm2 + 3.*hnm2*hnm1)/(hnm2+hnm1);
220 
221  } else { //Clamped
222  a_[nKnots_-1] = 0.;
223  b_[nKnots_-1] = 1.;
224  d_[nKnots_-1] = dydxn_;
225  }
226 
227  // the remaining equations ensure that f_i-1''(x_i) = f''_i(x_i) for all internal knots
228  for(UInt_t i=1; i<nKnots_-1; ++i) {
229  a_[i] = 1./(x_[i]-x_[i-1]);
230  b_[i] = 2./(x_[i]-x_[i-1]) + 2./(x_[i+1]-x_[i]);
231  c_[i] = 1./(x_[i+1]-x_[i]);
232  d_[i] = 3.*(y_[i]-y_[i-1])/((x_[i]-x_[i-1])*(x_[i]-x_[i-1])) + 3.*(y_[i+1]-y_[i])/((x_[i+1]-x_[i])*(x_[i+1]-x_[i]));
233  }
234 
235  // forward sweep - replace c_i and d_i with
236  //
237  // c'_i = c_i / b_i for i = 0
238  // c_i / (b_i - a_i * c_i-1) otherwise
239  //
240  // and
241  //
242  // d'_i = d_i / b_i for i = 0
243  // (d_i - a_i * d_i-1) / (b_i - a_i * c_i-1) otherwise
244 
245  c_[0] /= b_[0];
246  d_[0] /= b_[0];
247 
248  for(UInt_t i=1; i<nKnots_-1; ++i) {
249  c_[i] = c_[i] / (b_[i] - a_[i]*c_[i-1]);
250  d_[i] = (d_[i] - a_[i]*d_[i-1]) / (b_[i] - a_[i]*c_[i-1]);
251  }
252 
253  d_[nKnots_-1] = (d_[nKnots_-1] - a_[nKnots_-1]*d_[nKnots_-2]) / (b_[nKnots_-1] - a_[nKnots_-1]*c_[nKnots_-2]);
254 
255  // back substitution - calculate k_i = dy/dx at each knot
256  //
257  // k_i = d'_i for i = n-1
258  // d'_i - c'_i * k_i+1 otherwise
259 
260  dydx_[nKnots_-1] = d_[nKnots_-1];
261 
262  for(Int_t i=nKnots_-2; i>=0; --i) {
263  dydx_[i] = d_[i] - c_[i]*dydx_[i+1];
264  }
265 }
266 
268  //derivatives are calculated according to the Akima method
269  // J.ACM vol. 17 no. 4 pp 589-602
270 
271  Double_t am1(0.), an(0.), anp1(0.);
272 
273  // a[i] is the slope of the segment from point i-1 to point i
274  //
275  // n.b. segment 0 is before point 0 and segment n is after point n-1
276  // internal segments are numbered 1 - n-1
277  for(UInt_t i=1; i<nKnots_; ++i) {
278  a_[i] = (y_[i]-y_[i-1])/(x_[i]-x_[i-1]);
279  }
280 
281  // calculate slopes between additional points on each end according to the Akima method
282  // method assumes that the additional points follow a quadratic defined by the last three points
283  // this leads to the relations a[2] - a[1] = a[1] - a[0] = a[0] - a[-1] and a[n-1] - a[n-2] = a[n] - a[n-1] = a[n+1] - a[n]
284  a_[0] = 2*a_[1] - a_[2];
285  am1 = 2*a_[0] - a_[1];
286 
287  an = 2*a_[nKnots_-1] - a_[nKnots_-2];
288  anp1 = 2*an - a_[nKnots_-1];
289 
290  // b[i] is the weight of a[i] towards dydx[i]
291  // c[i] is the weight of a[i+1] towards dydx[i]
292  // See Appendix A of J.ACM vol. 17 no. 4 pp 589-602 for a justification of these weights
293  b_[0] = TMath::Abs(a_[1] - a_[0]);
294  b_[1] = TMath::Abs(a_[2] - a_[1]);
295 
296  c_[0] = TMath::Abs(a_[0] - am1 );
297  c_[1] = TMath::Abs(a_[1] - a_[0]);
298 
299  for(UInt_t i=2; i<nKnots_-2; ++i) {
300  b_[i] = TMath::Abs(a_[i+2] - a_[i+1]);
301  c_[i] = TMath::Abs(a_[i] - a_[i-1]);
302  }
303 
304  b_[nKnots_-2] = TMath::Abs(an - a_[nKnots_-1]);
305  b_[nKnots_-1] = TMath::Abs(anp1 - an );
306 
307  c_[nKnots_-2] = TMath::Abs(a_[nKnots_-2] - a_[nKnots_-3]);
308  c_[nKnots_-1] = TMath::Abs(a_[nKnots_-1] - a_[nKnots_-2]);
309 
310  // dy/dx calculated as the weighted average of a[i] and a[i+1]:
311  // dy/dx_i = ( | a_i+2 - a_i+1 | a_i + | a_i - a_i-1 | a_i+1 ) / ( | a_i+2 - a_i+1 | + | a_i - a_i-1 | )
312  // in the special case a_i-1 == a_i != a_i+1 == a_i+2 this function is undefined so dy/dx is then defined as (a_i + a_i+1) / 2
313  for(UInt_t i=0; i<nKnots_-2; ++i) {
314  if(b_[i]==0 && c_[i]==0) {
315  dydx_[i] = ( a_[i] + a_[i+1] ) / 2.;
316  } else {
317  dydx_[i] = ( b_[i] * a_[i] + c_[i] * a_[i+1] ) / ( b_[i] + c_[i] );
318  }
319  }
320 
321  if(b_[nKnots_-1]==0 && c_[nKnots_-1]==0) {
322  dydx_[nKnots_-1] = ( a_[nKnots_-1] + an ) / 2.;
323  } else {
324  dydx_[nKnots_-1] = ( b_[nKnots_-1] * a_[nKnots_-1] + c_[nKnots_-1] * an ) / ( b_[nKnots_-1] + c_[nKnots_-1] );
325  }
326 }
std::vector< Double_t > c_
The &#39;c&#39; coefficients used to determine the derivatives.
LauSplineBoundaryType
Define the allowed boundary condition types.
std::vector< Double_t > b_
The &#39;b&#39; coefficients used to determine the derivatives.
Double_t dydxn_
The gradient at the right boundary for a clamped spline.
Double_t evaluate(Double_t x) const
Evaluate the function at given point.
void updateType(LauSplineType type)
Update the type of interpolation to perform.
void calcDerivatives()
Calculate the first derivative at each knot.
void init()
Initialise the class.
std::vector< Double_t > d_
The &#39;d&#39; coefficients used to determine the derivatives.
void calcDerivativesAkima()
Calculate the first derivatives according to the Akima method.
LauSplineType type_
The type of interpolation to be performed.
std::vector< Double_t > dydx_
The first derivative at each knot.
std::vector< Double_t > a_
The &#39;a&#39; coefficients used to determine the derivatives.
File containing declaration of Lau1DCubicSpline class.
LauSplineBoundaryType leftBound_
The left-hand boundary condition on the spline.
void updateYValues(const std::vector< Double_t > &ys)
Update the y-values of the knots.
const UInt_t nKnots_
The number of knots in the spline.
Lau1DCubicSpline(const std::vector< Double_t > &xs, const std::vector< Double_t > &ys, LauSplineType type=Lau1DCubicSpline::StandardSpline, LauSplineBoundaryType leftBound=Lau1DCubicSpline::NotAKnot, LauSplineBoundaryType rightBound=Lau1DCubicSpline::NotAKnot, Double_t dydx0=0.0, Double_t dydxn=0.0)
Constructor.
Double_t dydx0_
The gradient at the left boundary for a clamped spline.
LauSplineBoundaryType rightBound_
The right-hand boundary condition on the spline.
void updateBoundaryConditions(LauSplineBoundaryType leftBound, LauSplineBoundaryType rightBound, Double_t dydx0=0.0, Double_t dydxn=0.0)
Update the boundary conditions for the spline.
std::vector< Double_t > y_
The y-value at each knot.
LauSplineType
Define the allowed interpolation types.
void calcDerivativesStandard()
Calculate the first derivatives according to the standard method.
std::vector< Double_t > x_
The x-value at each knot.
virtual ~Lau1DCubicSpline()
Destructor.