MaCh3  2.6.0
Reference Guide
SplineStructs.h
Go to the documentation of this file.
1 #pragma once
2 
3 // MaCh3 includes
6 
7 #include <cmath>
8 
9 #pragma GCC diagnostic push
10 #pragma GCC diagnostic ignored "-Wuseless-cast"
11 #pragma GCC diagnostic ignored "-Wfloat-conversion"
12 #pragma GCC diagnostic ignored "-Wnull-dereference"
13 
19 
20 // *******************
23 // *******************
26  nPts = -999;
27  CurrSegment = 0;
28  splineParsPointer = nullptr;
29  }
30 
32  virtual ~FastSplineInfo() = default;
33 
36 
38  std::vector<M3::float_t> xPts;
39 
42 
45 };
46 
47 // ***************************************************************************
49 inline void ApplyKnotWeightCap(TGraph* SplineGraph, const int splineParsIndex, ParameterHandlerGeneric* ParHandler) {
50 // ***************************************************************************
51  if(SplineGraph == nullptr){
52  MACH3LOG_ERROR("hmmm looks like you're trying to apply capping for spline parameter {} but it hasn't been set in SplineGraph yet",
53  ParHandler->GetParFancyName(splineParsIndex));
54  throw MaCh3Exception(__FILE__ , __LINE__ );
55  }
56 
57  // EM: cap the weights of the knots if specified in the config
58  if(
59  (ParHandler->GetParSplineKnotUpperBound(splineParsIndex) != M3::DefSplineKnotUpBound)
60  || (ParHandler->GetParSplineKnotLowerBound(splineParsIndex) != M3::DefSplineKnotLowBound))
61  {
62  for(int knotId = 0; knotId < SplineGraph->GetN(); knotId++){
63  double x,y;
64 
65  // EM: get the x and y of the point. Also double check that the requested point was valid just to be super safe
66  if(SplineGraph->GetPoint(knotId, x, y) == -1) {
67  MACH3LOG_ERROR("Invalid knot requested: {}", knotId);
68  throw MaCh3Exception(__FILE__ , __LINE__ );
69  }
70 
71  y = std::min(y, ParHandler->GetParSplineKnotUpperBound(splineParsIndex));
72  y = std::max(y, ParHandler->GetParSplineKnotLowerBound(splineParsIndex));
73 
74  SplineGraph->SetPoint(knotId, x, y);
75  }
76 
77  // EM: check if our cap made the spline flat, if so we set to 1 knot to avoid problems later on
78  bool isFlat = true;
79  for(int knotId = 0; knotId < SplineGraph->GetN(); knotId++){
80  double x,y;
81 
82  // EM: get the x and y of the point. Also double check that the requested point was valid just to be super safe
83  if(SplineGraph->GetPoint(knotId, x, y) == -1) {
84  MACH3LOG_ERROR("Invalid knot requested: {}", knotId);
85  throw MaCh3Exception(__FILE__ , __LINE__ );
86  }
87  if(std::abs(y - 1.0) > 1e-5) isFlat = false;
88  }
89 
90  if(isFlat){
91  SplineGraph->Set(1);
92  SplineGraph->SetPoint(0, 0.0, 1.0);
93  }
94  }
95 }
96 
97 // ***************************************************************************
99 inline void ApplyKnotWeightCapTSpline3(TSpline3* &Spline, const int splineParsIndex, ParameterHandlerGeneric* ParHandler) {
100 // ***************************************************************************
101  if(Spline == nullptr) {
102  MACH3LOG_ERROR("hmmm looks like you're trying to apply capping for spline parameter {} but it hasn't been set in Spline yet",
103  ParHandler->GetParFancyName(splineParsIndex));
104  throw MaCh3Exception(__FILE__ , __LINE__ );
105  }
106 
107  std::string oldName = Spline->GetName();
108  // EM: cap the weights of the knots if specified in the config
109  if((ParHandler->GetParSplineKnotUpperBound(splineParsIndex) != M3::DefSplineKnotUpBound)
110  || (ParHandler->GetParSplineKnotLowerBound(splineParsIndex) != M3::DefSplineKnotLowBound))
111  {
112  const int NValues = Spline->GetNp();
113  std::vector<double> XVals(NValues);
114  std::vector<double> YVals(NValues);
115  for(int knotId = 0; knotId < NValues; knotId++){
116  double x,y;
117  // Extract X and Y
118  Spline->GetKnot(knotId, x, y);
119 
120  y = std::min(y, ParHandler->GetParSplineKnotUpperBound(splineParsIndex));
121  y = std::max(y, ParHandler->GetParSplineKnotLowerBound(splineParsIndex));
122 
123  XVals[knotId] = x;
124  YVals[knotId] = y;
125  }
126  delete Spline;
127  // If we capped we have to make new TSpline3 to recalculate coefficients
128  Spline = new TSpline3(oldName.c_str(), XVals.data(), YVals.data(), NValues);
129  }
130 }
131 
132 // ************************
135 // ************************
136 public:
142  virtual double Eval(const double var)const =0;
144  virtual void Print()const =0;
146  virtual M3::int_t GetNp()const =0;
147 };
148 
149 // ************************
152 // ************************
153 public:
156  length = 0;
157  Par = nullptr;
158  }
159 
161  virtual ~TF1_red() {
162  if (Par != nullptr) {
163  delete[] Par;
164  Par = nullptr;
165  }
166  }
167 
170  length = nSize;
171  for (int i = 0; i < length; ++i) {
172  Par[i] = Array[i];
173  }
174  }
175 
177  TF1_red(TF1* &Function) : TResponseFunction_red() {
178  Par = nullptr;
179  SetFunc(Function);
180  }
181 
183  inline void SetFunc(TF1* &Func) {
184  length = M3::int_t(Func->GetNpar());
185  if (Par != nullptr) delete[] Par;
186  Par = new M3::float_t[length];
187  for (int i = 0; i < length; ++i) {
188  Par[i] = M3::float_t(Func->GetParameter(i));
189  }
190  delete Func;
191  Func = nullptr;
192  }
193 
195  inline double Eval(const double var) const override {
196  return Par[1]+Par[0]*var;
197 
198  /* FIXME in future we might introduce more TF1
199  //If we have 5 parameters we're using a fifth order polynomial
200  if (Type == kFifthOrderPolynomial) {
201  return 1+Par[0]*var+Par[1]*var*var+Par[2]*var*var*var+Par[3]*var*var*var*var+Par[4]*var*var*var*var*var;
202  } else if (Type == kTwoLinears) {
203  return (var <= 0)*(Par[2]+Par[0]*var)+(var > 0)*(Par[2]+Par[1]*var);
204  } else if (Type == kLinear) {
205  return (Par[1]+Par[0]*var);
206  } else if (Type == kPseudoHeaviside) {
207  return (var <= 0)*(1+Par[0]*var) + (1 >= var)*(var > 0)*(1+Par[1]*var) + (var > 1)*(Par[3]+Par[2]*var);
208  }else {
209  MACH3LOG_ERROR(" Class only knows about 5th order polynomial, two superposed linear functions, linear function, or pseudo Heaviside.");
210  MACH3LOG_ERROR(" You have tried something else than this, which remains unimplemented.");
211  MACH3LOG_ERROR("{}: {}", __FILE__, __LINE__);
212  throw MaCh3Exception(__FILE__ , __LINE__ );
213  }
214  */
215  }
216 
218  inline void SetParameter(M3::int_t Parameter, M3::float_t Value) {
219  Par[Parameter] = Value;
220  }
221 
223  double GetParameter(M3::int_t Parameter) const {
224  if (Parameter > length) {
225  MACH3LOG_ERROR("You requested parameter number {} but length is {} parameters", Parameter, length);
226  throw MaCh3Exception(__FILE__ , __LINE__ );
227  return -999.999;
228  }
229  return Par[Parameter];
230  }
231 
233  inline void SetSize(M3::int_t nSpline) {
234  length = nSpline;
235  Par = new M3::float_t[length];
236  }
238  inline int GetSize() const { return length; }
240  inline void Print() const override {
241  MACH3LOG_INFO("Printing TF1_red:");
242  MACH3LOG_INFO(" Length = {}", length);
243  for (int i = 0; i < length; i++) {
244  MACH3LOG_INFO(" Coeff {} = {}", i, Par[i]);
245  }
246  }
247 
249  inline TF1* ConstructTF1(const std::string& function, const int xmin, const int xmax) {
250  TF1 *func = new TF1("TF1", function.c_str(), xmin, xmax);
251  for(int i = 0; i < length; ++i) {
252  func->SetParameter(i, Par[i]);
253  }
254  return func;
255  }
256 
258  inline M3::int_t GetNp() const override { return length; }
259 
260 private:
264 };
265 
266 // ************************
269 // ************************
270 public:
273  nPoints = 0;
274  Par = nullptr;
275  XPos = nullptr;
276  YResp = nullptr;
277  }
278 
280  TSpline3_red(TSpline3* &spline, SplineInterpolation InterPolation = kTSpline3) : TResponseFunction_red() {
281  Par = nullptr;
282  XPos = nullptr;
283  YResp = nullptr;
284  SetFunc(spline, InterPolation);
285  }
286 
289  nPoints = N;
290  // Save the parameters for each knot
291  Par = new M3::float_t*[nPoints];
292  // Save the positions of the knots
293  XPos = new M3::float_t[nPoints];
294  // Save the y response at each knot
295  YResp = new M3::float_t[nPoints];
296  for(int j = 0; j < N; ++j){
297  Par[j] = new M3::float_t[3];
298  Par[j][0] = P[j][0];
299  Par[j][1] = P[j][1];
300  Par[j][2] = P[j][2];
301  XPos[j] = X[j];
302  YResp[j] = Y[j];
303 
304  if((Par[j][0] == -999) | (Par[j][1] ==-999) | (Par[j][2] ==-999) | (XPos[j] ==-999) | (YResp[j] ==-999)){
305  MACH3LOG_ERROR("******************* Bad parameter values when constructing TSpline3_red *********************");
306  MACH3LOG_ERROR("Passed values (i, x, y, b, c, d): {}, {}, {}, {}, {}, {}", j, X[j], Y[j], P[j][0], P[j][1], P[j][2]);
307  MACH3LOG_ERROR("Set values (i, x, y, b, c, d): {}, {}, {}, {}, {}, {}", j, XPos[j], YResp[j], Par[j][0], Par[j][1], Par[j][2]);
308  MACH3LOG_ERROR("*********************************************************************************************");
309  }
310  }
311  }
314  inline void SetFunc(TSpline3* &spline, SplineInterpolation InterPolation = kTSpline3) {
315  nPoints = M3::int_t(spline->GetNp());
316  if (Par != nullptr) {
317  for (int i = 0; i < nPoints; ++i) {
318  delete[] Par[i];
319  Par[i] = nullptr;
320  }
321  delete[] Par;
322  Par = nullptr;
323  }
324  if (XPos != nullptr) delete[] XPos;
325  if (YResp != nullptr) delete[] YResp;
326  // Save the parameters for each knot
327  Par = new M3::float_t*[nPoints];
328  // Save the positions of the knots
329  XPos = new M3::float_t[nPoints];
330  // Save the y response at each knot
331  YResp = new M3::float_t[nPoints];
332 
333  //KS: Default TSpline3 ROOT implementation
334  if(InterPolation == kTSpline3)
335  {
336  for (int i = 0; i < nPoints; ++i) {
337  // 3 is the size of the TSpline3 coefficients
338  Par[i] = new M3::float_t[3];
339  double x = -999.99, y = -999.99, b = -999.99, c = -999.99, d = -999.99;
340  spline->GetCoeff(i, x, y, b, c, d);
341  XPos[i] = M3::float_t(x);
342  YResp[i] = M3::float_t(y);
343  Par[i][0] = M3::float_t(b);
344  Par[i][1] = M3::float_t(c);
345  Par[i][2] = M3::float_t(d);
346  }
347  }
348  //CW: Reduce to use linear spline interpolation for certain parameters
349  // Not the most elegant way: use TSpline3 object but set coefficients to zero and recalculate spline points; the smart way (but more human intensive) would be to save memory here and simply not store the zeros at all
350  // Get which parameters should be linear from the fit manager
351  // Convert the spline number to global parameter
352  // Loop over the splines points
353  // KS: kLinearFunc should be used with TF1, this is just as safety
354  else if(InterPolation == kLinear || InterPolation == kLinearFunc)
355  {
356  for (int k = 0; k < nPoints; ++k) {
357  Par[k] = new M3::float_t[3];
358 
359  Double_t x1, y1, b1, c1, d1, x2, y2, b2, c2, d2 = 0;
360  spline->GetCoeff(k, x1, y1, b1, c1, d1);
361 
362  double tempb = 0;
363  if (k == nPoints - 1) {
364  tempb = Par[k-1][0];
365  } else {
366  spline->GetCoeff(k + 1, x2, y2, b2, c2, d2);
367  tempb = (y2-y1)/(x2-x1);
368  }
369  XPos[k] = M3::float_t(x1);
370  YResp[k] = M3::float_t(y1);
371  Par[k][0] = M3::float_t(tempb); // linear slope
372  Par[k][1] = M3::float_t(0);
373  Par[k][2] = M3::float_t(0);
374  }
375  }
376  //EM: Akima spline is similar to regular cubic spline but is allowed to be discontinuous in 2nd derivative and coefficients in any segment
377  // only depend on th 2 nearest points on either side
378  else if(InterPolation == kAkima)
379  {
380  // get the knot values for the spline
381  for (int i = 0; i < nPoints; ++i) {
382  // 3 is the size of the TSpline3 coefficients
383  Par[i] = new M3::float_t[3];
384 
385  double x = -999.99, y = -999.99;
386  spline->GetKnot(i, x, y);
387 
388  XPos[i] = M3::float_t(x);
389  YResp[i] = M3::float_t(y);
390  }
391 
392  M3::float_t* mvals = new M3::float_t[nPoints + 3];
393  M3::float_t* svals = new M3::float_t[nPoints + 1];
394 
395  for (int i = -2; i <= nPoints; ++i) {
396  // if segment is first or last or 2nd to first or last, needs to be dealt with slightly differently;
397  // need to estimate the values for additional points which would lie outside of the spline
398  if(i ==-2){
399  mvals[i+2] = M3::float_t(3.0 * (YResp[1] - YResp[0]) / (XPos[1] - XPos[0]) - 2.0*(YResp[2] - YResp[1]) / (XPos[2] - XPos[1]));
400  }
401  else if(i==-1){
402  mvals[i+2] = M3::float_t(2.0 * (YResp[1] - YResp[0]) / (XPos[1] - XPos[0]) - (YResp[2] - YResp[1]) / (XPos[2] - XPos[1]));
403  }
404  else if(i==nPoints){
405  mvals[i+2] = M3::float_t(3.0 * (YResp[nPoints-1] - YResp[nPoints-2]) / (XPos[nPoints-1] - XPos[nPoints-2]) - 2.0*(YResp[nPoints-2] - YResp[nPoints-3]) / (XPos[nPoints-2] - XPos[nPoints-3]));
406  }
407  else if(i == nPoints - 1){
408  mvals[i+2] = M3::float_t(2.0 * (YResp[nPoints-1] - YResp[nPoints-2]) / (XPos[nPoints-1] - XPos[nPoints-2]) - (YResp[nPoints-2] - YResp[nPoints-3]) / (XPos[nPoints-2] - XPos[nPoints-3]));
409  }
410  //standard internal segment
411  else{
412  mvals[i+2] = (YResp[i+1] - YResp[i])/ (XPos[i+1] - XPos[i]);
413  }
414  }
415 
416  for(int i = 2; i<=nPoints+2; i++){
417  if (std::abs(mvals[i+1] - mvals[i]) + std::abs(mvals[i-1] - mvals[i-2]) != 0.0){
418  svals[i-2] = (std::abs(mvals[i+1] - mvals[i]) * mvals[i-1] + std::abs(mvals[i-1] - mvals[i-2]) *mvals[i]) / (std::abs(mvals[i+1] - mvals[i]) + std::abs(mvals[i-1] - mvals[i-2]));
419  }
420  else{svals[i-2] = mvals[i];}
421  }
422 
423  // calculate the coefficients for the spline
424  for(int i = 0; i <nPoints; i++){
425  M3::float_t b, c, d = M3::float_t(-999.999);
426 
427  b = svals[i];
428  c = M3::float_t(3.0* (YResp[i+1] - YResp[i]) / (XPos[i+1] - XPos[i]) -2.0 *svals[i] - svals[i +1]) /(XPos[i+1] - XPos[i]);
429  d = M3::float_t((svals[i + 1] +svals[i]) - 2.0*(YResp[i+1] - YResp[i]) / (XPos[i+1] - XPos[i])) / ((XPos[i+1] - XPos[i]) * (XPos[i+1] - XPos[i]));
430 
431  Par[i][0] = b;
432  Par[i][1] = c;
433  Par[i][2] = d;
434  }
435 
436  // check the input spline for linear segments, if there are any then overwrite the calculated coefficients
437  // this will pretty much only ever be the case if they are set to be linear in SampleHandlerBase i.e. the user wants it to be linear
438  for(int i = 0; i <nPoints-1; i++){
439  double x = -999.99, y = -999.99, b = -999.99, c = -999.99, d = -999.99;
440  spline->GetCoeff(i, x, y, b, c, d);
441 
442  if((c == 0.0 && d == 0.0)){
443  Par[i][0] = M3::float_t(b);
444  Par[i][1] = M3::float_t(0.0);
445  Par[i][2] = M3::float_t(0.0);
446  }
447  }
448  delete[] mvals;
449  delete[] svals;
450  }
451  //EM: Monotone spline is similar to regular cubic spline but enforce the condition that the interpolated value at any point
452  // must be between its two nearest knots, DOES NOT make the entire spline monotonic, only the segments
453  else if(InterPolation == kMonotonic)
454  {
455  // values of the secants at each point (for calculating monotone spline)
456  M3::float_t * Secants = new M3::float_t[nPoints -1];
457  // values of the tangents at each point (for calculating monotone spline)
458  M3::float_t * Tangents = new M3::float_t[nPoints];
459 
460  // get the knot values for the spline
461  for (int i = 0; i < nPoints; ++i) {
462  // 3 is the size of the TSpline3 coefficients
463  Par[i] = new M3::float_t[3];
464 
465  double x = -999.99, y = -999.99;
466  spline->GetKnot(i, x, y);
467 
468  XPos[i] = M3::float_t(x);
469  YResp[i] = M3::float_t(y);
470 
471  Tangents[i] = 0.0;
472  }
473 
474  // deal with the case of two points (just do linear interpolation between them)
475  if (nPoints == 2){
476  Par[0][0] = (YResp[1] - YResp[0]) / (XPos[1] - XPos[0]);
477  Par[0][1] = 0.0;
478  Par[0][2] = 0.0;
479  // extra "virtual" segment at end to make Par array shape fit with knot arrays shapes
480  Par[1][1] = 0.0;
481  Par[1][2] = 0.0;
482 
483  return;
484  } // if nPoints !=2 do full monotonic spline treatment:
485  else
486  {
487  // first pass over knots to calculate the secants
488  for (int i = 0; i < nPoints-1; ++i) {
489  Secants[i] = (YResp[i+1] - YResp[i]) / (XPos[i+1] - XPos[i]);
490  MACH3LOG_TRACE("Secant {}: {}", i, Secants[i]);
491  }
492 
493  Tangents[0] = Secants[0];
494  Tangents[nPoints-1] = Secants[nPoints -2];
495 
496  M3::float_t alpha;
497  M3::float_t beta;
498 
499  // second pass over knots to calculate tangents
500  for (int i = 1; i < nPoints-1; ++i) {
501  if ((Secants[i-1] >= 0.0 && Secants[i] >= 0.0) | (Secants[i-1] < 0.0 && Secants[i] < 0.0)){ //check for same sign
502  Tangents[i] = M3::float_t((Secants[i-1] + Secants[i]) /2.0);
503  }
504  }
505 
506  // third pass over knots to rescale tangents
507  for (int i = 0; i < nPoints-1; ++i) {
508  if (Secants[i] == 0.0){
509  Tangents[i] = 0.0;
510  Tangents[i+1] = 0.0;
511  }
512  else{
513  alpha = Tangents[i] / Secants[i];
514  beta = Tangents[i+1] / Secants[i];
515 
516  if (alpha <0.0){
517  Tangents[i] = 0.0;
518  }
519  if (beta < 0.0){
520  Tangents[i+1] = 0.0;
521  }
522 
523  if (alpha * alpha + beta * beta >9.0){
524  M3::float_t tau = M3::float_t(3.0 / std::sqrt(alpha * alpha + beta * beta));
525  Tangents[i] = tau * alpha * Secants[i];
526  Tangents[i+1] = tau * beta * Secants[i];
527  }
528  }
529  } // finished rescaling tangents
530  // fourth pass over knots to calculate the coefficients for the spline
531  M3::float_t dx;
532  for(int i = 0; i <nPoints-1; i++){
533  M3::float_t b, c, d = M3::float_t(-999.999);
534  dx = XPos[i+1] - XPos[i];
535 
536  b = Tangents[i] * dx;
537  c = M3::float_t(3.0* (YResp[i+1] - YResp[i]) -2.0 *dx * Tangents[i] - dx * Tangents[i +1]);
538  d = M3::float_t(2.0* (YResp[i] - YResp[i+1]) + dx * (Tangents[i] + Tangents[i+1]));
539 
540  Par[i][0] = b / dx;
541  Par[i][1] = c / (dx * dx);
542  Par[i][2] = d / (dx * dx * dx);
543 
544  if((Par[i][0] == -999) | (Par[i][1] == -999) | (Par[i][2] ==-999) | (Par[i][0] == -999.999) | (Par[i][1] == -999.999) | (Par[i][2] ==-999.999)){
545  MACH3LOG_INFO("Bad spline parameters for segment {}: (b, c, d) = {}, {}, {}. This will cause problems with GPU.",
546  i, Par[i][0], Par[i][1], Par[i][2]);
547  }
548  MACH3LOG_TRACE("b: {}", b);
549  MACH3LOG_TRACE("dx: {}, x_0: {}, x_1: {}", dx, XPos[i], XPos[i+1]);
550  MACH3LOG_TRACE(" y_0: {}, y_1: {}", YResp[i], YResp[i+1]);
551  }
552 
553  // include params for final "segment" outside of the spline so that par array fits with x and y arrays,
554  // should never actually get used but if not set then the GPU code gets very angry
555  Par[nPoints-1][0] = 0.0;
556  Par[nPoints-1][1] = 0.0;
557  Par[nPoints-1][2] = 0.0;
558 
559  // check the input spline for linear segments, if there are any then overwrite the calculated coefficients
560  // this will pretty much only ever be the case if they are set to be linear in samplePDFND i.e. the user wants it to be linear
561  for(int i = 0; i <nPoints-1; i++){
562  double x = -999.99, y = -999.99, b = -999.99, c = -999.99, d = -999.99;
563  spline->GetCoeff(i, x, y, b, c, d);
564 
565  if((c == 0.0 && d == 0.0)){
566  Par[i][0] = M3::float_t(b);
567  Par[i][1] = 0.0;
568  Par[i][2] = 0.0;
569  }
570  }
571  delete[] Secants;
572  delete[] Tangents;
573  } // end of if(nPoints !=2)
574  }
575  else if (InterPolation == kKochanekBartels)
576  {
577  // Allocate memory for tangents and coefficients
578  M3::float_t* Tangents = new M3::float_t[nPoints];
579  for (int i = 0; i < nPoints; ++i)
580  {
581  Par[i] = new M3::float_t[3];
582  double x = -999.99, y = -999.99;
583  spline->GetKnot(i, x, y);
584  XPos[i] = M3::float_t(x);
585  YResp[i] = M3::float_t(y);
586  Tangents[i] = 0.0;
587  }
588 
589  // KS: Setting all parameters to 0 gives a Catmull-Rom spline.
590  // Tension (T):
591  // - T = 0.0: Default smooth interpolation (Catmull-Rom).
592  // - T = 1.0: Maximum tension; curve becomes linear between knots (no curvature).
593  // - T = -1.0: Overshooting; creates loops or "bouncy" effects around knots.
594  constexpr M3::float_t T = 0.0;
595 
596  // Continuity (C):
597  // - C = 0.0: Default smooth transition (continuous first derivative).
598  // - C = 1.0: Sharp corner/crease at knot (discontinuous first derivative).
599  // - C = -1.0: Inverted curve; creates loops or kinks at knot.
600  constexpr M3::float_t C = 0.0;
601 
602  // Bias (B):
603  // - B = 0.0: Default symmetric curve around knot.
604  // - B = 1.0: Curve is "pulled" toward the next knot (leading effect).
605  // - B = -1.0: Curve is "pulled" toward the previous knot (lagging effect).
606  constexpr M3::float_t B = 0.0;
607 
608  // Calculate tangents for internal knots
609  for (int i = 1; i < nPoints - 1; ++i)
610  {
611  M3::float_t d0 = (YResp[i] - YResp[i-1]) / (XPos[i] - XPos[i-1]);
612  M3::float_t d1 = (YResp[i+1] - YResp[i]) / (XPos[i+1] - XPos[i]);
613 
614  M3::float_t term1 = (1.0 - T) * (1.0 + B) * (1.0 + C) * 0.5 * d0;
615  M3::float_t term2 = (1.0 - T) * (1.0 - B) * (1.0 - C) * 0.5 * d1;
616 
617  Tangents[i] = term1 + term2;
618  }
619 
620  // Boundary conditions (simple choice: secant slopes)
621  Tangents[0] = (YResp[1] - YResp[0]) / (XPos[1] - XPos[0]);
622  Tangents[nPoints-1]= (YResp[nPoints-1] - YResp[nPoints-2]) / (XPos[nPoints-1] - XPos[nPoints-2]);
623 
624  // Compute cubic coefficients for each segment
625  for (int i = 0; i < nPoints - 1; ++i)
626  {
627  M3::float_t dx = XPos[i+1] - XPos[i];
628  M3::float_t dy = YResp[i+1] - YResp[i];
629  M3::float_t m0 = Tangents[i];
630  M3::float_t m1 = Tangents[i+1];
631 
632  Par[i][0] = m0; // b
633  Par[i][1] = (3*dy/(dx*dx)) - (2*m0 + m1)/dx; // c
634  Par[i][2] = (m0 + m1 - 2*dy/dx) / (dx*dx); // d
635 
636  MACH3LOG_TRACE("KB segment {}: dx={}, dy={}, b={}, c={}, d={}",
637  i, dx, dy, Par[i][0], Par[i][1], Par[i][2]);
638  }
639  // Last "virtual" segment
640  Par[nPoints-1][0] = 0.0;
641  Par[nPoints-1][1] = 0.0;
642  Par[nPoints-1][2] = 0.0;
643 
644  delete[] Tangents;
645  }
646  else
647  {
648  MACH3LOG_ERROR("Unsupported interpolation type {}", static_cast<int>(InterPolation));
649  throw MaCh3Exception(__FILE__ , __LINE__ );
650  }
651 
652  delete spline;
653  spline = nullptr;
654  }
655 
657  virtual ~TSpline3_red() {
658  if(Par != nullptr) {
659  for (int i = 0; i < nPoints; ++i) {
660  if (Par[i] != nullptr) {
661  delete[] Par[i];
662  }
663  }
664  delete[] Par;
665  }
666  if(XPos != nullptr) delete[] XPos;
667  if(YResp != nullptr) delete[] YResp;
668  Par = nullptr;
669  XPos = YResp = nullptr;
670  }
671 
675  inline int FindX(double x) const {
676  // The segment we're interested in (klow in ROOT code)
677  int segment = 0;
678  int kHigh = nPoints-1;
679  // If the variation is below the lowest saved spline point
680  if (x <= XPos[0]){
681  segment = 0;
682  // If the variation is above the highest saved spline point
683  } else if (x >= XPos[nPoints-1]) {
684  // Yes, the -2 is indeed correct, see TSpline.cxx:814 and //see: https://savannah.cern.ch/bugs/?71651
685  segment = kHigh;
686  // If the variation is between the maximum and minimum, perform a binary search
687  } else {
688  // The top point we've got
689  int kHalf = 0;
690  // While there is still a difference in the points (we haven't yet found the segment)
691  // This is a binary search, incrementing segment and decrementing kHalf until we've found the segment
692  while (kHigh - segment > 1) {
693  // Increment the half-step
694  kHalf = (segment + kHigh)/2;
695  // If our variation is above the kHalf, set the segment to kHalf
696  if (x > XPos[kHalf]) {
697  segment = kHalf;
698  // Else move kHigh down
699  } else {
700  kHigh = kHalf;
701  }
702  } // End the while: we've now done our binary search
703  } // End the else: we've now found our point
704  if (segment >= nPoints-1 && nPoints > 1) segment = nPoints-2;
705  return segment;
706  }
707 
709  inline double Eval(const double var) const override {
710  // Get the segment for this variation
711  int segment = FindX(var);
712  // The get the coefficients for this variation
713  M3::float_t x = M3::float_t(-999.99), y = M3::float_t(-999.99), b = M3::float_t(-999.99), c = M3::float_t(-999.99), d = M3::float_t(-999.99);
714  GetCoeff(segment, x, y, b, c, d);
715  double dx = var - x;
716  // Evaluate the third order polynomial
717  double weight = y+dx*(b+dx*(c+d*dx));
718  return weight;
719  }
720 
722  inline M3::int_t GetNp() const override { return nPoints; }
723  // Get the ith knot's x and y position
724  inline void GetKnot(int i, M3::float_t &xtmp, M3::float_t &ytmp) const {
725  xtmp = XPos[i];
726  ytmp = YResp[i];
727  }
728 
730  inline void GetCoeff(int segment, M3::float_t &x, M3::float_t &y,
731  M3::float_t &b, M3::float_t &c, M3::float_t &d) const {
732  b = Par[segment][0];
733  c = Par[segment][1];
734  d = Par[segment][2];
735  x = XPos[segment];
736  y = YResp[segment];
737  }
738 
740  inline TSpline3* ConstructTSpline3() {
741  // KS: Sadly ROOT only accepts double...
742  #ifdef _LOW_MEMORY_STRUCTS_
743  std::vector<Double_t> xPosDoubles(nPoints);
744  std::vector<Double_t> yPosDoubles(nPoints);
745  for (Int_t i = 0; i < nPoints; ++i) {
746  xPosDoubles[i] = static_cast<Double_t>(XPos[i]); // Convert float to double
747  yPosDoubles[i] = static_cast<Double_t>(YResp[i]); // Convert float to double
748  }
749  TSpline3 *spline = new TSpline3("Spline", xPosDoubles.data(), yPosDoubles.data(), static_cast<int>(nPoints));
750  #else
751  TSpline3 *spline = new TSpline3("Spline", XPos, YResp, nPoints);
752  #endif
753  for (Int_t i = 0; i < nPoints; ++i) {
754  spline->SetPointCoeff(i, Par[i][0], Par[i][1], Par[i][2]);
755  }
756 
757  return spline;
758  }
759 
761  inline void Print() const override {
762  MACH3LOG_INFO("Printing TSpline_red:");
763  MACH3LOG_INFO(" Nknots = {}", nPoints);
764  for (int i = 0; i < nPoints; ++i) {
765  MACH3LOG_INFO(" i = {} x = {} y = {} b = {} c = {} d = {}",
766  i, XPos[i], YResp[i], Par[i][0], Par[i][1], Par[i][2]);
767  }
768  }
769 
770  protected: //changed to protected from private so can be accessed by derived classes
779 };
780 
781 // *****************************************
784 inline bool isFlat(TSpline3_red* &spl) {
785 // *****************************************
786  int Np = spl->GetNp();
787  M3::float_t x, y, b, c, d;
788  // Go through spline segment parameters,
789  // Get y values for each spline knot,
790  // Every knot must evaluate to 1.0 to create a flat spline
791  for(int i = 0; i < Np; i++) {
792  spl->GetCoeff(i, x, y, b, c, d);
793  if (y != 1) {
794  return false;
795  }
796  }
797  return true;
798 }
799 
800 // *********************************
803 inline std::vector<std::vector<TSpline3_red*> > ReduceTSpline3(std::vector<std::vector<TSpline3*> > &MasterSpline) {
804 // *********************************
805  std::vector<std::vector<TSpline3*> >::iterator OuterIt;
806  std::vector<TSpline3*>::iterator InnerIt;
807 
808  // The return vector
809  std::vector<std::vector<TSpline3_red*> > ReducedVector;
810  ReducedVector.reserve(MasterSpline.size());
811 
812  // Loop over each parameter
813  for (OuterIt = MasterSpline.begin(); OuterIt != MasterSpline.end(); ++OuterIt) {
814  // Make the temp vector
815  std::vector<TSpline3_red*> TempVector;
816  TempVector.reserve(OuterIt->size());
817  // Loop over each TSpline3 pointer
818  for (InnerIt = OuterIt->begin(); InnerIt != OuterIt->end(); ++InnerIt) {
819  // Here's our delicious TSpline3 object
820  TSpline3 *spline = (*InnerIt);
821  // Now make the reduced TSpline3 pointer
822  TSpline3_red *red = nullptr;
823  if (spline != nullptr) {
824  red = new TSpline3_red(spline);
825  (*InnerIt) = spline;
826  }
827  // Push back onto new vector
828  TempVector.push_back(red);
829  } // End inner for loop
830  ReducedVector.push_back(TempVector);
831  } // End outer for loop
832  // Now have the reduced vector
833  return ReducedVector;
834 }
835 
836 // *********************************
839 inline std::vector<std::vector<TF1_red*> > ReduceTF1(std::vector<std::vector<TF1*> > &MasterSpline) {
840 // *********************************
841  std::vector<std::vector<TF1*> >::iterator OuterIt;
842  std::vector<TF1*>::iterator InnerIt;
843 
844  // The return vector
845  std::vector<std::vector<TF1_red*> > ReducedVector;
846  ReducedVector.reserve(MasterSpline.size());
847 
848  // Loop over each parameter
849  for (OuterIt = MasterSpline.begin(); OuterIt != MasterSpline.end(); ++OuterIt) {
850  // Make the temp vector
851  std::vector<TF1_red*> TempVector;
852  TempVector.reserve(OuterIt->size());
853  // Loop over each TSpline3 pointer
854  for (InnerIt = OuterIt->begin(); InnerIt != OuterIt->end(); ++InnerIt) {
855  // Here's our delicious TSpline3 object
856  TF1* spline = (*InnerIt);
857  // Now make the reduced TSpline3 pointer (which deleted TSpline3)
858  TF1_red* red = nullptr;
859  if (spline != nullptr) {
860  red = new TF1_red(spline);
861  (*InnerIt) = spline;
862  }
863  // Push back onto new vector
864  TempVector.push_back(red);
865  } // End inner for loop
866  ReducedVector.push_back(TempVector);
867  } // End outer for loop
868  // Now have the reduced vector
869  return ReducedVector;
870 }
871 
872 // *********************************
879  const RespFuncType SplineRespFuncType,
880  const SplineInterpolation SplineInterpolationType,
881  const std::string& Title) {
882 // *********************************
883  TResponseFunction_red* RespFunc = nullptr;
884 
885  if (graph && graph->GetN() > 1)
886  {
887  if(SplineRespFuncType == kTSpline3_red)
888  {
889  // Here's the TSpline3
890  TSpline3* spline = nullptr;
891  TSpline3_red *spline_red = nullptr;
892 
893  // Create the TSpline3* from the TGraph* and build the coefficients
894  spline = new TSpline3(Title.c_str(), graph);
895  spline->SetNameTitle(Title.c_str(), Title.c_str());
896 
897  // Make the reduced TSpline3 format and delete the old spline
898  spline_red = new TSpline3_red(spline, SplineInterpolationType);
899 
900  RespFunc = spline_red;
901  }
902  else if(SplineRespFuncType == kTF1_red)
903  {
904  // The TF1 object we build from fitting the TGraph
905  TF1 *Fitter = nullptr;
906  TF1_red *tf1_red = nullptr;
907 
908  if(graph->GetN() != 2) {
909  MACH3LOG_ERROR("Trying to make TF1 from more than 2 knots. Knots = {}", graph->GetN());
910  MACH3LOG_ERROR("Currently support only linear with 2 knots :(");
911  throw MaCh3Exception(__FILE__ , __LINE__ );
912  }
913 
914  // Try simple linear function
915  Fitter = new TF1(Title.c_str(), "([1]+[0]*x)", graph->GetX()[0], graph->GetX()[graph->GetN()-1]);
916  //CW: For 2p2h shape C and O we can't fit a polynomial: try a linear combination of two linear functions around 0
917  //Fitter = new TF1(Title.c_str(), "(x<=0)*(1+[0]*x)+(x>0)*([1]*x+1)", graph->GetX()[0], graph->GetX()[graph->GetN()-1]);
918  // Fit 5hd order polynomial for all other parameters
919  //Fitter = new TF1(Title.c_str(), "1+[0]*x+[1]*x*x+[2]*x*x*x+[3]*x*x*x*x+[4]*x*x*x*x*x", graph->GetX()[0], graph->GetX()[graph->GetN()-1]);
920  //Pseudo Heaviside for Pauli Blocking
921  //Fitter = new TF1(Title.c_str(), "(x <= 0)*(1+[0]*x) + (1 >= x)*(x > 0)*(1+[1]*x) + (x > 1)*([3]+[2]*x)", graph->GetX()[0], graph->GetX()[graph->GetN()-1]);
922 
923  // Fit the TF1 to the graph
924  graph->Fit(Fitter, "Q0");
925  // Make the reduced TF1 if we want
926  tf1_red = new TF1_red(Fitter);
927 
928  RespFunc = tf1_red;
929  }
930  else
931  {
932  MACH3LOG_ERROR("Unsupported response function type");
933  throw MaCh3Exception(__FILE__ , __LINE__ );
934  }
935  }
936  else
937  {
938  RespFunc = nullptr;
939  }
940  return RespFunc;
941 }
942 
943 #pragma GCC diagnostic pop
944 
#define MACH3LOG_ERROR
Definition: MaCh3Logger.h:37
#define MACH3LOG_INFO
Definition: MaCh3Logger.h:35
#define MACH3LOG_TRACE
Definition: MaCh3Logger.h:33
Definitions of generic parameter structs and utility templates for MaCh3.
SplineInterpolation
Make an enum of the spline interpolation type.
@ kTSpline3
Default TSpline3 interpolation.
@ kMonotonic
EM: DOES NOT make the entire spline monotonic, only the segments.
@ kKochanekBartels
KS: Kochanek-Bartels spline: allows local control of tension, continuity, and bias.
@ kLinear
Linear interpolation between knots.
@ kLinearFunc
Liner interpolation using TF1 not spline.
@ kAkima
EM: Akima spline iis allowed to be discontinuous in 2nd derivative and coefficients in any segment.
RespFuncType
Make an enum of the spline interpolation type.
@ kTF1_red
Uses TF1_red for interpolation.
@ kTSpline3_red
Uses TSpline3_red for interpolation.
TResponseFunction_red * CreateResponseFunction(TGraph *&graph, const RespFuncType SplineRespFuncType, const SplineInterpolation SplineInterpolationType, const std::string &Title)
KS: Create Response Function using TGraph.
std::vector< std::vector< TF1_red * > > ReduceTF1(std::vector< std::vector< TF1 * > > &MasterSpline)
CW: Reduced the TF1 to TF1_red.
void ApplyKnotWeightCap(TGraph *SplineGraph, const int splineParsIndex, ParameterHandlerGeneric *ParHandler)
EM: Apply capping to knot weight for specified spline parameter. SplineGraph needs to have been set i...
Definition: SplineStructs.h:49
std::vector< std::vector< TSpline3_red * > > ReduceTSpline3(std::vector< std::vector< TSpline3 * > > &MasterSpline)
CW: Reduced the TSpline3 to TSpline3_red.
void ApplyKnotWeightCapTSpline3(TSpline3 *&Spline, const int splineParsIndex, ParameterHandlerGeneric *ParHandler)
EM: Apply capping to knot weight for specified spline parameter. param graph needs to have been set i...
Definition: SplineStructs.h:99
bool isFlat(TSpline3_red *&spl)
CW: Helper function used in the constructor, tests to see if the spline is flat.
Custom exception class used throughout MaCh3.
std::string GetParFancyName(const int i) const
Get fancy name of the Parameter.
Class responsible for handling of systematic error parameters with different types defined in the con...
double GetParSplineKnotUpperBound(const int i) const
EM: value at which we cap spline knot weight.
double GetParSplineKnotLowerBound(const int i) const
EM: value at which we cap spline knot weight.
CW: A reduced TF1 class only. Only saves parameters for each TF1 and how many parameters each paramet...
double Eval(const double var) const override
Evaluate a variation.
double GetParameter(M3::int_t Parameter) const
Get a parameter value.
void SetSize(M3::int_t nSpline)
Set the size.
M3::int_t GetNp() const override
DL: Get number of points.
M3::int_t length
void SetFunc(TF1 *&Func)
Set the function.
TF1_red()
Empty constructor.
TF1 * ConstructTF1(const std::string &function, const int xmin, const int xmax)
KS: Make a TF1 from the reduced TF1.
TF1_red(TF1 *&Function)
The TF1 constructor with deep copy.
M3::float_t * Par
The parameters.
virtual ~TF1_red()
Empty destructor.
int GetSize() const
Get the size.
TF1_red(M3::int_t nSize, M3::float_t *Array)
The useful constructor with deep copy.
void SetParameter(M3::int_t Parameter, M3::float_t Value)
Set a parameter to a value.
void Print() const override
Print detailed info.
KS: A reduced ResponseFunction Generic function used for evaluating weight.
virtual double Eval(const double var) const =0
Evaluate a variation.
virtual ~TResponseFunction_red()
Empty destructor.
virtual void Print() const =0
KS: Printer.
TResponseFunction_red()
Empty constructor.
virtual M3::int_t GetNp() const =0
DL: Get number of points.
CW: Reduced TSpline3 class.
void GetKnot(int i, M3::float_t &xtmp, M3::float_t &ytmp) const
int FindX(double x) const
Find the segment relevant to this variation in x.
virtual ~TSpline3_red()
Empty destructor.
void Print() const override
Print detailed info.
M3::float_t ** Par
Always uses a third order polynomial, so hard-code the number of coefficients in implementation.
TSpline3_red(M3::float_t *X, M3::float_t *Y, M3::int_t N, M3::float_t **P)
constructor taking parameters
M3::int_t nPoints
Number of points/knot in TSpline3.
TSpline3_red(TSpline3 *&spline, SplineInterpolation InterPolation=kTSpline3)
The constructor that takes a TSpline3 pointer and copies in to memory.
TSpline3 * ConstructTSpline3()
CW: Make a TSpline3 from the reduced splines.
void SetFunc(TSpline3 *&spline, SplineInterpolation InterPolation=kTSpline3)
Set the function .
M3::float_t * XPos
Positions of each x for each knot.
void GetCoeff(int segment, M3::float_t &x, M3::float_t &y, M3::float_t &b, M3::float_t &c, M3::float_t &d) const
CW: Get the coefficient of a given segment.
M3::int_t GetNp() const override
CW: Get the number of points.
TSpline3_red()
Empty constructor.
double Eval(const double var) const override
CW: Evaluate the weight from a variation.
M3::float_t * YResp
y-value for each knot
constexpr static const double DefSplineKnotUpBound
Default value for spline knot capping, default mean not capping is being applied.
Definition: Core.h:86
double float_t
Definition: Core.h:37
constexpr static const double DefSplineKnotLowBound
Default value for spline knot capping, default mean not capping is being applied.
Definition: Core.h:88
int int_t
Definition: Core.h:38
CW: Add a struct to hold info about the splinified parameters and help with FindSplineSegment.
Definition: SplineStructs.h:22
virtual ~FastSplineInfo()=default
Destructor.
const M3::float_t * splineParsPointer
Array of the knots positions.
Definition: SplineStructs.h:44
std::vector< M3::float_t > xPts
Array of the knots positions.
Definition: SplineStructs.h:38
M3::int_t CurrSegment
Array of what segment of spline we're currently interested in. Gets updated once per MCMC iteration.
Definition: SplineStructs.h:41
FastSplineInfo()
Constructor.
Definition: SplineStructs.h:25
M3::int_t nPts
Number of points in spline.
Definition: SplineStructs.h:35