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