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