LArSoft  v10_06_00
Liquid Argon Software toolkit - https://larsoft.org/
TrackMomentumCalculator.cxx
Go to the documentation of this file.
1 // \file TrackMomentumCalculator.cxx
2 //
3 // \author sowjanyag@phys.ksu.edu
4 
6 #include "cetlib/pow.h"
8 
9 #include <array>
10 #include <cassert>
11 #include <cmath>
12 #include <iostream>
13 #include <limits>
14 #include <string>
15 #include <tuple>
16 
17 #include "Math/Functor.h"
18 #include "Math/GenVector/PositionVector3D.h"
19 #include "Minuit2/Minuit2Minimizer.h"
20 #include "Rtypes.h"
21 #include "TAxis.h"
22 #include "TGraphErrors.h"
23 #include "TMath.h"
24 #include "TMatrixDSymEigen.h"
25 #include "TMatrixDSymfwd.h"
26 #include "TMatrixDfwd.h"
27 #include "TMatrixT.h"
28 #include "TMatrixTSym.h"
29 #include "TPolyLine3D.h"
30 #include "TSpline.h"
31 #include "TVectorDfwd.h"
32 #include "TVectorT.h"
35 
36 using std::cout;
37 using std::endl;
38 
39 namespace {
40 
41  //TODO: Replace for some global variable present in the art file
42  constexpr double LAr_density{1.396};
43  constexpr double rad_length{14.0};
44  constexpr double m_muon{0.1057}; // muon mass
45  constexpr auto range_gramper_cm()
46  {
47  std::array<double, 73> Range_grampercm{
48  {0.9833, 1.36, 1.786, 2.507, 3.321, 4.859, 6.598, 8.512, 10.58, 12.78,
49  15.1, 17.52, 20.04, 25.31, 30.84, 36.59, 42.5, 54.73, 67.32, 86.66,
50  106.3, 139.4, 172.5, 205.6, 238.5, 271.1, 303.5, 335.7, 367.7, 431.0,
51  493.4, 555.2, 616.3, 736.8, 855.2, 1030.0, 1202.0, 1482.0, 1758.0, 2029.0,
52  2297.0, 2562.0, 2825.0, 3085.0, 3343.0, 3854.0, 4359.0, 4859.0, 5354.0, 6333.0,
53  7298.0, 8726.0, 10130.0, 12430.0, 14690.0, 16920.0, 19100.0, 21260.0, 23380.0, 25480.0,
54  27550.0, 31610.0, 35580.0, 39460.0, 43260.0, 50620.0, 57680.0, 67780.0, 77340.0, 92220.0,
55  1.06e+05, 1.188e+05, 1.307e+05}};
56  for (double& value : Range_grampercm) {
57  value /= LAr_density; // convert to cm
58  }
59  return Range_grampercm;
60  }
61 
62  constexpr auto Range_grampercm = range_gramper_cm();
63  constexpr std::array<double, 73> KE_MeV{
64  {10.0, 12.0, 14.0, 17.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0,
65  50.0, 55.0, 60.0, 70.0, 80.0, 90.0, 100.0, 120.0, 140.0, 170.0,
66  200.0, 250.0, 300.0, 350.0, 400.0, 450.0, 500.0, 550.0, 600.0, 700.0,
67  800.0, 900.0, 1000.0, 1200.0, 1400.0, 1700.0, 2000.0, 2500.0, 3000.0, 3500.0,
68  4000.0, 4500.0, 5000.0, 5500.0, 6000.0, 7000.0, 8000.0, 9000.0, 10000.0, 12000.0,
69  14000.0, 17000.0, 20000.0, 25000.0, 30000.0, 35000.0, 40000.0, 45000.0, 50000.0, 55000.0,
70  60000.0, 70000.0, 80000.0, 90000.0, 1e+05, 1.2e+05, 1.4e+05, 1.7e+05, 2e+05, 2.5e+05,
71  3e+05, 3.5e+05, 4e+05}};
72  TGraph const KEvsR{73, Range_grampercm.data(), KE_MeV.data()};
73  TSpline3 const KEvsR_spline3{"KEvsRS", &KEvsR};
74 
75  constexpr std::array<double, 73> KE_MeV_Pi{
76  {11.1928, 13.4567, 15.6946, 19.0487, 22.3945, 27.9408, 33.4579, 38.9494, 44.4198,
77  49.8612, 55.2838, 60.6757, 66.0632, 76.7631, 87.3839, 97.9617, 108.461, 129.341,
78  150.023, 180.793, 211.275, 261.704, 311.585, 361.284, 410.705, 459.809, 508.805,
79  557.727, 606.592, 704.011, 801.047, 898.111, 994.983, 1188.51, 1381.61, 1671.44,
80  1961.46, 2442.32, 2925.25, 3406.69, 3888.88, 4370.85, 4853.71, 5335.02, 5816.14,
81  6778.21, 7739.41, 8699.89, 9658.32, 11572.9, 13480.8, 16335.1, 19171.0, 23866.9,
82  28529.9, 33169.1, 37734.7, 42284.0, 46770.4, 51233.2, 55648.5, 64349.8, 72904.4,
83  81303.4, 89561.4, 1.06e+05, 1.21e+05, 1.43e+05, 1.65e+05, 1.98e+05, 2.28e+05, 2.55e+05,
84  2.77e+05}};
85  TGraph const KEvsRPi{73, Range_grampercm.data(), KE_MeV_Pi.data()};
86  TSpline3 const KEvsRPi_spline3{"KEvsRS_pion", &KEvsRPi};
87 
88  // Stopping power data from pdg, to be used with MCS
89  std::vector<double> dedx_GeV_per_cm()
90  {
91  // original table (in MeV cm2/g)
92  std::vector<double> dEdx{
93  {5.687, 4.979, 4.461, 3.902, 3.502, 3.042, 2.731, 2.508, 2.34, 2.21, 2.107, 2.023, 1.954,
94  1.848, 1.771, 1.713, 1.67, 1.609, 1.57, 1.536, 1.519, 1.508, 1.51, 1.517, 1.526, 1.537,
95  1.548, 1.559, 1.57, 1.591, 1.61, 1.628, 1.645, 1.675, 1.7, 1.733, 1.761, 1.799, 1.829,
96  1.855, 1.877, 1.897, 1.914, 1.93, 1.944, 1.969, 1.991, 2.01, 2.028, 2.058, 2.084, 2.119,
97  2.149, 2.193, 2.232, 2.269, 2.304, 2.337, 2.369, 2.4, 2.43, 2.49, 2.548, 2.606, 2.663,
98  2.776, 2.888, 3.055, 3.224, 3.498, 3.774, 4.052, 4.332}};
99  for (double& value : dEdx) {
100  value *= LAr_density * 1e-3; // convert to GeV/cm
101  }
102  return dEdx;
103  }
104 
105  const int ndedx = 73;
106  const std::vector<double> dEdx_GeV_per_cm = dedx_GeV_per_cm();
107  const std::vector<double> E_GeV{
108  {0.115695, 0.117697, 0.119693, 0.122694, 0.125695, 0.13069, 0.135694, 0.14069, 0.145714,
109  0.150689, 0.155682, 0.160666, 0.165693, 0.17566, 0.185714, 0.1957, 0.205644, 0.225683,
110  0.245698, 0.275669, 0.305658, 0.355669, 0.405711, 0.45563, 0.505671, 0.555646, 0.605694,
111  0.655676, 0.705661, 0.805664, 0.905689, 1.00557, 1.10606, 1.30529, 1.50571, 1.8061,
112  2.10565, 2.60614, 3.1058, 3.60555, 4.10536, 4.60521, 5.10609, 5.606, 6.10591,
113  7.10579, 8.10569, 9.10561, 10.1106, 12.1105, 14.1104, 17.1103, 20.1103, 25.1102,
114  30.1102, 35.1102, 40.1101, 45.1101, 50.1101, 55.1101, 60.1101, 70.1101, 80.1101,
115  90.1101, 100.1, 120.1, 140.1, 170.1, 200.1, 250.1, 300.1, 350.1,
116  400.1}};
117  TGraph const dEdx_vs_E{ndedx, E_GeV.data(), dEdx_GeV_per_cm.data()};
118  TSpline3 const dEdx_vs_E_spline3{"dEdx_vs_E", &dEdx_vs_E};
119 
120  TVector3 const basex{1, 0, 0};
121  TVector3 const basey{0, 1, 0};
122  TVector3 const basez{0, 0, 1};
123  constexpr double kcal{0.0024}; // Approximation of dE/dx for mip muon in LAr
124 
125  constexpr double MomentumDependentConstant(const double p)
126  {
127  // values measured with MC: https://indico.fnal.gov/event/62283/contributions/299163/attachments/181289/248600/20240910_SimReco_Update_CM.pdf
128  double a = 0.079;
129  double c = 10.435;
130  return (a / (p * p)) + c;
131  }
132  double ComputeExpetecteRMS(const double p, const double red_length)
133  {
134  double beta = std::sqrt(1 - ((m_muon * m_muon) / (p * p + m_muon * m_muon)));
135  return (MomentumDependentConstant(p) / (p * beta)) *
136  (1.0 + 0.038 * TMath::Log(red_length / cet::square(beta))) * std::sqrt(red_length);
137  }
138  class FcnWrapper {
139  public:
140  explicit FcnWrapper(std::vector<double>&& xmeas,
141  std::vector<double>&& ymeas,
142  std::vector<double>&& eymeas,
143  double correction)
144  : xmeas_{std::move(xmeas)}
145  , ymeas_{std::move(ymeas)}
146  , eymeas_{std::move(eymeas)}
147  , correction_{correction}
148  {}
149 
150  double my_mcs_chi2(double const* x) const
151  {
152  double result = 0.0;
153 
154  double p = x[0];
155  double theta0 = x[1];
156 
157  auto const n = xmeas_.size();
158  assert(n == ymeas_.size());
159  assert(n == eymeas_.size());
160 
161  for (std::size_t i = 0; i < n; ++i) {
162  double const xx = xmeas_[i];
163  double const yy = ymeas_[i];
164  double const ey = eymeas_[i];
165 
166  if (std::abs(ey) < std::numeric_limits<double>::epsilon()) {
167  std::cout << " Zero denominator in my_mcs_chi2 ! " << std::endl;
168  return -1;
169  }
170 
171  double const l0 = xx / rad_length;
172  double res1 = 0.0;
173  // Highland formula
174  // Parameters given at Particle Data Group https://pdg.lbl.gov/2023/web/viewer.html?file=../reviews/rpp2022-rev-passage-particles-matter.pdf
175  double beta = std::sqrt(1 - ((m_muon * m_muon) / (p * p + m_muon * m_muon)));
176  if (xx > 0 && p > 0)
177  res1 = (13.6 / (p * beta)) * (1.0 + 0.038 * TMath::Log(l0 / cet::square(beta))) *
178  std::sqrt(l0);
179  res1 *= correction_;
180 
181  res1 = std::hypot(res1, theta0);
182 
183  double const diff = yy - res1;
184  result += cet::square(diff / ey);
185  }
186 
187  if (std::isnan(result) || std::isinf(result)) {
188  MF_LOG_DEBUG("TrackMomentumCalculator") << " Is nan in my_mcs_chi2 ! ";
189  return -1;
190  }
191 
192  return result;
193  }
194 
195  private:
196  std::vector<double> const xmeas_;
197  std::vector<double> const ymeas_;
198  std::vector<double> const eymeas_;
199  double const correction_;
200  };
201 
202  class FcnWrapperLLHD {
203  public:
204  explicit FcnWrapperLLHD(std::vector<double>&& dEi,
205  std::vector<double>&& dEj,
206  std::vector<double>&& dthij,
207  std::vector<double>&& ind,
208  std::vector<bool>&& dthij_valid,
209  double stepsize,
210  double correction)
211  : dEi_{std::move(dEi)}
212  , dEj_{std::move(dEj)}
213  , dthij_{std::move(dthij)}
214  , ind_{std::move(ind)}
215  , dthij_valid_{std::move(dthij_valid)}
216  , stepsize_{stepsize}
217  , correction_{correction}
218  {}
219 
220  double my_mcs_llhd(double const* x) const
221  {
222  double result = 0.0;
223 
224  double red_length = stepsize_ / rad_length;
225 
226  double p = x[0];
227  double theta0 = x[1];
228 
229  // Total initial energy of the muon (converting the input "p" into energy with muon mass)
230  double Etot = std::hypot(p, m_muon);
231 
232  double Ei{Etot};
233 
234  double dEi{0};
235  auto const n = dEi_.size(); // number of segments of energy
236 
237  bool addpenality = false;
238  for (std::size_t i = 0; i < n; ++i) {
239  Ei -= dEi;
240  if (Ei >= E_GeV[0]) { //otherwise keep dEi the same (In any case, this is close to p = 0)
241  dEi = dEdx_vs_E_spline3.Eval(Ei) * stepsize_;
242  }
243  else {
244  dEi = dEdx_GeV_per_cm[0] * stepsize_;
245  }
246  if (Ei <= m_muon) {
247  Ei =
248  m_muon +
249  0.010; // Reached zero energy, keep this value constant to not evaluate `nan` in pij nor tH0
250  addpenality = true;
251  }
252 
253  if (dthij_valid_.at(i) == false) continue;
254 
255  // Total momentum of the muon including momentum lost upstream of this segment (converting Eij to momentum)
256  double pij = std::sqrt(Ei * Ei - m_muon * m_muon);
257 
258  // Highland formula
259  // Parameters given at Particle Data Group https://pdg.lbl.gov/2023/web/viewer.html?file=../reviews/rpp2022-rev-passage-particles-matter.pdf
260  // Modified with uboone studies: https://iopscience.iop.org/article/10.1088/1748-0221/12/10/P10010
261  double tH0 = ComputeExpetecteRMS(pij, red_length);
262 
263  // The tH0 (theta rms) is calculated for projected angles
264  // If space angles are used instead, tH0 needs to be multiplied by sqrt(2)
265  tH0 *= correction_;
266 
267  double rms_square = -1.0;
268 
269  double prob = 0;
270  double DT = 0;
271  // Computes the rms of angle (no need to evaluate sqrt)
272  rms_square = cet::sum_of_squares(tH0, theta0);
273 
274  DT = dthij_.at(i);
275 
276  // Formula is modified so we don't compute sqrt(rms), use factor in log instead
277  prob = -0.5 * std::log(2.0 * TMath::Pi()) - 0.5 * std::log(rms_square) -
278  0.5 * DT * DT / rms_square;
279 
280  if (addpenality) { prob -= 2 * rms_square; }
281 
282  result = result - 2.0 * prob; // Adds for each segment
283  }
284 
285  return result;
286  }
287 
288  private:
289  std::vector<double> const dEi_;
290  std::vector<double> const dEj_;
291  std::vector<double> const dthij_;
292  std::vector<double> const ind_;
293  std::vector<bool> const dthij_valid_;
294  double const stepsize_;
295  double const correction_;
296  };
297 
298 }
299 
300 namespace trkf {
301 
302  TrackMomentumCalculator::TrackMomentumCalculator(double const min,
303  double const max,
304  double const stepsize,
305  int const angleMethod,
306  int const nsteps)
307  : minLength{min}
308  , maxLength{max}
309  , steps_size{stepsize}
310  , fMCSAngleMethod{static_cast<ScatterAngleMethods>(angleMethod)}
311  , n_steps{nsteps}
312  {
313  for (int i = 1; i <= n_steps; i++) {
314  steps.push_back(steps_size * i);
315  }
316  }
317 
318  double TrackMomentumCalculator::GetTrackMomentum(double trkrange, int pdg) const
319  {
320  /* Muon range-momentum tables from CSDA (Argon density = 1.4 g/cm^3)
321  website:
322  http://pdg.lbl.gov/2012/AtomicNuclearProperties/MUON_ELOSS_TABLES/muonloss_289.pdf
323 
324  CSDA table values:
325  double Range_grampercm[30] = {9.833E-1, 1.786E0, 3.321E0,
326  6.598E0, 1.058E1, 3.084E1, 4.250E1, 6.732E1, 1.063E2, 1.725E2,
327  2.385E2, 4.934E2, 6.163E2, 8.552E2, 1.202E3, 1.758E3, 2.297E3,
328  4.359E3, 5.354E3, 7.298E3, 1.013E4, 1.469E4, 1.910E4, 3.558E4,
329  4.326E4, 5.768E4, 7.734E4, 1.060E5, 1.307E5}; double KE_MeV[30] = {10, 14,
330  20, 30, 40, 80, 100, 140, 200, 300, 400, 800, 1000, 1400, 2000, 3000,
331  4000, 8000, 10000, 14000, 20000, 30000, 40000, 80000, 100000, 140000,
332  200000, 300000, 400000};
333 
334  Functions below are obtained by fitting polynomial fits to KE_MeV vs
335  Range (cm) graph. A better fit was obtained by splitting the graph into
336  two: Below range<=200cm,a polynomial of power 4 was a good fit; above
337  200cm, a polynomial of power 6 was a good fit
338 
339  Fit errors for future purposes:
340  Below 200cm, Forpoly4 fit: p0 err=1.38533;p1 err=0.209626; p2
341  err=0.00650077; p3 err=6.42207E-5; p4 err=1.94893E-7; Above 200cm,
342  Forpoly6 fit: p0 err=5.24743;p1 err=0.0176229; p2 err=1.6263E-5; p3
343  err=5.9155E-9; p4 err=9.71709E-13; p5 err=7.22381E-17;p6
344  err=1.9709E-21;*/
345 
347  //*********For muon, the calculations are valid up to 1.91E4 cm range
348  //corresponding to a Muon KE of 40 GeV**********//
350 
351  /*Proton range-momentum tables from CSDA (Argon density = 1.4 g/cm^3):
352  website: https://physics.nist.gov/PhysRefData/Star/Text/PSTAR.html
353 
354  CSDA values:
355  double KE_MeV_P_Nist[31]={10, 15, 20, 30, 40, 80, 100, 150, 200, 250, 300,
356  350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000,
357  1500, 2000, 2500, 3000, 4000, 5000};
358 
359  double Range_gpercm_P_Nist[31]={1.887E-1,3.823E-1, 6.335E-1, 1.296,
360  2.159, 7.375, 1.092E1, 2.215E1, 3.627E1, 5.282E1, 7.144E1,
361  9.184E1, 1.138E2, 1.370E2, 1.614E2, 1.869E2, 2.132E2, 2.403E2,
362  2.681E2, 2.965E2, 3.254E2, 3.548E2, 3.846E2, 4.148E2, 4.454E2,
363  7.626E2, 1.090E3, 1.418E3, 1.745E3, 2.391E3, 3.022E3};
364 
365  Functions below are obtained by fitting power and polynomial fits to
366  KE_MeV vs Range (cm) graph. A better fit was obtained by splitting the
367  graph into two: Below range<=80cm,a a*(x^b) was a good fit; above 80cm, a
368  polynomial of power 6 was a good fit
369 
370  Fit errors for future purposes:
371  For power function fit: a=0.388873; and b=0.00347075
372  Forpoly6 fit: p0 err=3.49729;p1 err=0.0487859; p2 err=0.000225834; p3
373  err=4.45542E-7; p4 err=4.16428E-10; p5 err=1.81679E-13;p6
374  err=2.96958E-17;*/
375 
377  //*********For proton, the calculations are valid up to 3.022E3 cm range
378  //corresponding to a Muon KE of 5 GeV**********//
380 
381  /* Pion range-momentum extracted from Bethe-Bloch equation. The
382  computation are all one using
383  https://github.com/sungbinoh/PDSPTreeAnalyzer.git (last commit 4a3d1d2)
384 
385  The computation is done with the spline of the Range_grampercm and
386  KE_MeV_Pion.*/
387 
389  //*********For pion, the calculations are valid only when there is no
390  //inelastic scattering present. So this method should be used
391  //carefully.**********//
393 
394  if (trkrange < 0 || std::isnan(trkrange)) {
395  mf::LogError("TrackMomentumCalculator") << "Invalid track range " << trkrange << " return -1";
396  return -1.;
397  }
398 
399  double KE, Momentum, M;
400  constexpr double Muon_M = m_muon * 1e3, Proton_M = 938.272, Pion_M = 139.57039;
401 
402  if (abs(pdg) == 13) {
403  M = Muon_M;
404  KE = KEvsR_spline3.Eval(trkrange);
405  }
406  else if (abs(pdg) == 2212) {
407  M = Proton_M;
408  if (trkrange > 0 && trkrange <= 80)
409  KE = 29.9317 * std::pow(trkrange, 0.586304);
410  else if (trkrange > 80 && trkrange <= 3.022E3)
411  KE = 149.904 + (3.34146 * trkrange) + (-0.00318856 * trkrange * trkrange) +
412  (4.34587E-6 * trkrange * trkrange * trkrange) +
413  (-3.18146E-9 * trkrange * trkrange * trkrange * trkrange) +
414  (1.17854E-12 * trkrange * trkrange * trkrange * trkrange * trkrange) +
415  (-1.71763E-16 * trkrange * trkrange * trkrange * trkrange * trkrange * trkrange);
416  else
417  KE = -999;
418  }
419  else if (abs(pdg) == 211) {
420  M = Pion_M;
421  KE = KEvsRPi_spline3.Eval(trkrange);
422  }
423  else
424  KE = -999;
425 
426  if (KE < 0)
427  Momentum = -999;
428  else
429  Momentum = std::sqrt((KE * KE) + (2 * M * KE));
430 
431  Momentum = Momentum / 1000;
432 
433  return Momentum;
434  }
435 
436  // Momentum measurement via Multiple Coulomb Scattering (MCS)
437 
438  // author: Leonidas N. Kalousis (July 2015)
439 
440  // email: kalousis@vt.edu
441 
442  // Updated by: Henrique Vieira de Souza (June 2024)
443  // email: hvsouza@apc.in2p3.fr
444 
446  const bool checkValidPoints,
447  const int maxMomentum_MeV,
448  const double min_resolution,
449  const double max_resolution,
450  const bool check_valid_scattered,
451  const double angle_correction)
452  {
453 
454  std::vector<double> recoX;
455  std::vector<double> recoY;
456  std::vector<double> recoZ;
457 
458  int n_points = trk->NumberTrajectoryPoints();
459 
460  for (int i = 0; i < n_points; i++) {
461  if (checkValidPoints && !trk->HasValidPoint(i)) continue;
462  auto const& pos = trk->LocationAtPoint(i);
463  recoX.push_back(pos.X());
464  recoY.push_back(pos.Y());
465  recoZ.push_back(pos.Z());
466  }
467 
468  if (recoX.size() < 2) return -1.0;
469 
470  if (!plotRecoTracks_(recoX, recoY, recoZ)) return -1.0;
471 
472  double const seg_size{steps_size};
473 
474  auto const segments = getSegTracks_(recoX, recoY, recoZ, seg_size, check_valid_scattered);
475  if (!segments.has_value()) return -1.0;
476 
477  auto const seg_steps = segments->x.size();
478  if (seg_steps < 2) return -1;
479 
480  double const recoSegmentLength = segments->L.at(seg_steps - 1);
481  if (recoSegmentLength < minLength || recoSegmentLength > maxLength) return -1;
482 
483  std::vector<double> dEi;
484  std::vector<double> dEj;
485  std::vector<double> dthij;
486  std::vector<double> ind;
487  std::vector<bool> dthij_valid = segments->nvalid;
488  if (getDeltaThetaij_(dEi, dEj, dthij, ind, *segments, seg_size, angle_correction) != 0)
489  return -1;
490 
491  auto const ndEi = dEi.size();
492  if (ndEi < 1) return -1;
493 
494  double correction = 1.;
495  if (fMCSAngleMethod == kAngleCombined) { correction = std::sqrt(2.); }
496 
497  // Gets energy evaluated by range
498  auto recoL = trk->Length();
499  double const minP = this->GetTrackMomentum(recoL, 13);
500 
501  ROOT::Minuit2::Minuit2Minimizer mP{};
502  FcnWrapperLLHD const wrapper{std::move(dEi),
503  std::move(dEj),
504  std::move(dthij),
505  std::move(ind),
506  std::move(dthij_valid),
507  seg_size,
508  correction};
509  ROOT::Math::Functor FCA([&wrapper](double const* xs) { return wrapper.my_mcs_llhd(xs); }, 2);
510 
511  mP.SetFunction(FCA);
512 
513  // Start point for resolution
514  // If max_resolution is zero, startpoint is equal min_resolution
515  double startpoint = 2;
516  if (startpoint < min_resolution)
517  startpoint = (max_resolution - min_resolution) / 2.; // prevent wrong values
518  bool fixresolution = false;
519  double maxres = max_resolution;
520  if (max_resolution == 0 || max_resolution <= min_resolution) {
521  fixresolution = true;
522  startpoint = min_resolution;
523  maxres += 1;
524  }
525 
526  // Starting energy as double of the energy by range
527  // Assumes that the smallet possible energy is given by 60% with CSDA
528  // Step as 10 %
529  mP.SetLimitedVariable(0, "p_{MCS}", minP * 2, minP * 0.1, minP * 0.6, maxMomentum_MeV / 1.e3);
530  mP.SetLimitedVariable(1, "#delta#theta", startpoint, startpoint / 2., min_resolution, maxres);
531  if (fixresolution) { mP.FixVariable(1); }
532  mP.SetMaxFunctionCalls(1.E9);
533  mP.SetMaxIterations(1.E9);
534  mP.SetTolerance(0.01);
535  mP.SetStrategy(2);
536  mP.SetErrorDef(1);
537 
538  bool const mstatus = mP.Minimize();
539 
540  mP.Hesse();
541 
542  const double* pars = mP.X();
543 
544  double const p_mcs = pars[0];
545 
546  return mstatus ? p_mcs : -1.0;
547  }
548 
550  {
551  double const LLHDp = GetMuMultiScatterLLHD3(trk, true);
552  double const LLHDm = GetMuMultiScatterLLHD3(trk, false);
553 
554  if (LLHDp != -1 && LLHDm != -1 && LLHDp > LLHDm) {
555  int const n_points = trk->NumberTrajectoryPoints();
556  return trk->LocationAtPoint<TVector3>(n_points - 1);
557  }
558  else {
559  return trk->LocationAtPoint<TVector3>(0);
560  }
561 
562  // Should never get here
563  return TVector3{};
564  }
565 
567  bool const dir)
568  {
569  std::vector<double> recoX;
570  std::vector<double> recoY;
571  std::vector<double> recoZ;
572 
573  int const n_points = trk->NumberTrajectoryPoints();
574  for (int i = 0; i < n_points; ++i) {
575  auto const index = dir ? i : n_points - 1 - i;
576  auto const& pos = trk->LocationAtPoint(index);
577  recoX.push_back(pos.X());
578  recoY.push_back(pos.Y());
579  recoZ.push_back(pos.Z());
580  }
581 
582  if (recoX.size() < 2) return -1.0;
583 
584  if (!plotRecoTracks_(recoX, recoY, recoZ)) return -1.0;
585 
586  constexpr double seg_size{5.0};
587  auto const segments = getSegTracks_(recoX, recoY, recoZ, seg_size);
588  if (!segments.has_value()) return -1.0;
589 
590  auto const seg_steps = segments->x.size();
591  if (seg_steps < 2) return -1;
592 
593  double const recoSegmentLength = segments->L.at(seg_steps - 1);
594  if (recoSegmentLength < 15.0 || recoSegmentLength > maxLength) return -1;
595 
596  std::vector<double> dEi;
597  std::vector<double> dEj;
598  std::vector<double> dthij;
599  std::vector<double> ind;
600  double angle_correction = 1; // never tested in this function
601  if (getDeltaThetaij_(dEi, dEj, dthij, ind, *segments, seg_size, angle_correction) != 0)
602  return -1;
603 
604  auto const recoL = trk->Length();
605  double const p_range = recoL * kcal;
606  double const logL = my_mcs_llhd(dEi, dEj, dthij, ind, p_range, 5.65);
607 
608  return logL;
609  }
610 
611  int TrackMomentumCalculator::getDeltaThetaij_(std::vector<double>& ei,
612  std::vector<double>& ej,
613  std::vector<double>& th,
614  std::vector<double>& ind,
615  Segments const& segments,
616  double const thick,
617  double const angle_correction) const
618  {
619  auto const& segnx = segments.nx;
620  auto const& segny = segments.ny;
621  auto const& segnz = segments.nz;
622  auto const& segL = segments.L;
623 
624  int const a1 = segnx.size();
625  int const a2 = segny.size();
626  int const a3 = segnz.size();
627 
628  if (a1 != a2 || a1 != a3) {
629  std::cout << " ( Get thij ) Error ! " << std::endl;
630  return -1.0;
631  }
632 
633  int tot = a1;
634 
635  for (int i = 0; i < tot - 1; i++) {
636  double const dx = segnx.at(i);
637  double const dy = segny.at(i);
638  double const dz = segnz.at(i);
639 
640  // Assumes z as propagation angle
641  TVector3 const vec_z{dx, dy, dz};
642  TVector3 vec_x;
643  TVector3 vec_y;
644 
645  double const switcher = basex.Dot(vec_z);
646  if (std::abs(switcher) <= 0.995) {
647  vec_y = vec_z.Cross(basex).Unit();
648  vec_x = vec_y.Cross(vec_z);
649  }
650  else {
651  // cout << " It switched ! Isn't this lovely !!! " << endl; //
652  vec_y = basez.Cross(vec_z).Unit();
653  vec_x = vec_y.Cross(vec_z);
654  }
655 
656  TVector3 const Rx{vec_x.Dot(basex), vec_x.Dot(basey), vec_x.Dot(basez)};
657  TVector3 const Ry{vec_y.Dot(basex), vec_y.Dot(basey), vec_y.Dot(basez)};
658  TVector3 const Rz{vec_z.Dot(basex), vec_z.Dot(basey), vec_z.Dot(basez)};
659 
660  double const here_dx = segnx.at(i + 1);
661  double const here_dy = segny.at(i + 1);
662  double const here_dz = segnz.at(i + 1);
663 
664  TVector3 const here_vec{here_dx, here_dy, here_dz};
665  TVector3 const rot_here{Rx.Dot(here_vec), Ry.Dot(here_vec), Rz.Dot(here_vec)};
666 
667  double const scx = rot_here.X();
668  double const scy = rot_here.Y();
669  double const scz = rot_here.Z();
670 
671  double const azy = find_angle(scz, scy);
672  double const azx = find_angle(scz, scx);
673 
674  constexpr double ULim = 10000.0; // Avoid huge (wrong) angles
675  constexpr double LLim = -10000.0;
676 
677  double const Li = segL.at(i);
678  double const Lj = segL.at(i + 1);
679 
680  if (azy <= ULim && azy >= LLim) { // safe scatter in the yz plane
681 
682  if (azx <= ULim && azx >= LLim) { // safe scatter in the za plane
683  ei.push_back(Li); // Energy deposited at i
684  ej.push_back(Lj); // Energy deposited at i+1
685  if (fMCSAngleMethod == kAnglezx) {
686  th.push_back(azx); // scattered angle z-x
687  }
688  else if (fMCSAngleMethod == kAnglezy) {
689  th.push_back(azy);
690  }
691  else if (fMCSAngleMethod == kAngleCombined) {
692  th.push_back(std::hypot(azx, azy) /
693  angle_correction); // space angle (applying correction)
694  }
695  }
696  }
697  }
698 
699  return 0;
700  }
701 
703  const bool checkValidPoints,
704  const int maxMomentum_MeV,
705  const double min_resolution,
706  const double max_resolution)
707  {
708  std::vector<double> recoX;
709  std::vector<double> recoY;
710  std::vector<double> recoZ;
711 
712  int n_points = trk->NumberTrajectoryPoints();
713 
714  for (int i = 0; i < n_points; i++) {
715  if (checkValidPoints && !trk->HasValidPoint(i)) continue;
716  auto const& pos = trk->LocationAtPoint(i);
717  recoX.push_back(pos.X());
718  recoY.push_back(pos.Y());
719  recoZ.push_back(pos.Z());
720  }
721 
722  if (recoX.size() < 2) return -1.0;
723 
724  if (!plotRecoTracks_(recoX, recoY, recoZ)) return -1.0;
725 
726  double const seg_size{steps_size};
727  auto const segments = getSegTracks_(recoX, recoY, recoZ, seg_size);
728  if (!segments.has_value()) return -1.0;
729 
730  auto const seg_steps = segments->x.size();
731  if (seg_steps < 2) return -1;
732 
733  double const recoSegmentLength = segments->L.at(seg_steps - 1);
734  if (recoSegmentLength < minLength || recoSegmentLength > maxLength) return -1;
735 
736  double ymax = -999.0;
737  double ymin = +999.0;
738 
739  std::vector<double> xmeas;
740  std::vector<double> ymeas;
741  std::vector<double> eymeas;
742  xmeas.reserve(n_steps);
743  ymeas.reserve(n_steps);
744  eymeas.reserve(n_steps);
745  for (int j = 0; j < n_steps; j++) {
746  double const trial = steps.at(j);
747  // computes the rms by groups of trial, if seg_size was chosen as 10, trials will be 10, 20, etc.. until 10 * n_steps
748  auto const [mean, rms, rmse] = getDeltaThetaRMS_(*segments, trial);
749 
750  if (std::isnan(mean) || std::isinf(mean)) {
751  mf::LogDebug("TrackMomentumCalculator") << "Returned mean is either nan or infinity.";
752  continue;
753  }
754  if (std::isnan(rms) || std::isinf(rms)) {
755  mf::LogDebug("TrackMomentumCalculator") << "Returned rms is either nan or infinity.";
756  continue;
757  }
758  if (std::isnan(rmse) || std::isinf(rmse)) {
759  mf::LogDebug("TrackMomentumCalculator") << "Returned rmse is either nan or infinity.";
760  continue;
761  }
762 
763  if (mean == -1 && rms == -1 && rmse == -1) continue;
764  xmeas.push_back(trial); // x values are different steps length, ex: 10, 20, 30 cm
765  ymeas.push_back(rms); // y values are the RMS of the scattered angle for each step defined
766  eymeas.push_back(
767  std::hypot(rmse, 0.05 * rms)); // <--- conservative syst. error to fix chi^{2} behaviour !!!
768 
769  if (ymin > rms) ymin = rms;
770  if (ymax < rms) ymax = rms;
771  }
772 
773  assert(xmeas.size() == ymeas.size());
774  assert(xmeas.size() == eymeas.size());
775  if (xmeas.empty()) { return -1.0; }
776 
777  TGraphErrors gr_meas{n_steps, xmeas.data(), ymeas.data(), nullptr, eymeas.data()};
778 
779  gr_meas.SetTitle("(#Delta#theta)_{rms} versus material thickness; Material "
780  "thickness in cm; (#Delta#theta)_{rms} in mrad");
781 
782  gr_meas.SetLineColor(kBlack);
783  gr_meas.SetMarkerColor(kBlack);
784  gr_meas.SetMarkerStyle(20);
785  gr_meas.SetMarkerSize(1.2);
786 
787  gr_meas.GetXaxis()->SetLimits(steps.at(0) - steps.at(0), steps.at(n_steps - 1) + steps.at(0));
788  gr_meas.SetMinimum(0.0);
789  gr_meas.SetMaximum(1.80 * ymax);
790 
791  double correction = 1.;
792  if (fMCSAngleMethod == kAngleCombined) { correction = std::sqrt(2.); }
793 
794  ROOT::Minuit2::Minuit2Minimizer mP{};
795  FcnWrapper const wrapper{std::move(xmeas), std::move(ymeas), std::move(eymeas), correction};
796  ROOT::Math::Functor FCA([&wrapper](double const* xs) { return wrapper.my_mcs_chi2(xs); }, 2);
797 
798  // Start point for resolution
799  double startpoint = 2;
800  if (startpoint < min_resolution) startpoint = (max_resolution - min_resolution) / 2.;
801  bool fixresolution = false;
802  double maxres = max_resolution;
803  if (max_resolution == 0 || max_resolution <= min_resolution) {
804  fixresolution = true;
805  startpoint = min_resolution;
806  maxres += 1;
807  }
808 
809  mP.SetFunction(FCA);
810  mP.SetLimitedVariable(0, "p_{MCS}", 1.0, 0.01, 0.001, maxMomentum_MeV / 1.e3);
811  mP.SetLimitedVariable(1, "#delta#theta", startpoint, startpoint / 2., min_resolution, maxres);
812  if (fixresolution) { mP.FixVariable(1); }
813  mP.SetMaxFunctionCalls(1.E9);
814  mP.SetMaxIterations(1.E9);
815  mP.SetTolerance(0.01);
816  mP.SetStrategy(2);
817  mP.SetErrorDef(1);
818 
819  bool const mstatus = mP.Minimize();
820 
821  mP.Hesse();
822 
823  const double* pars = mP.X();
824 
825  auto const recoL = trk->Length();
826  double const deltap = (recoL * kcal) / 2.0;
827 
828  double const p_mcs = pars[0] + deltap;
829 
830  return mstatus ? p_mcs : -1.0;
831  }
832 
833  bool TrackMomentumCalculator::plotRecoTracks_(std::vector<double> const& xxx,
834  std::vector<double> const& yyy,
835  std::vector<double> const& zzz)
836  {
837  auto const n = xxx.size();
838  auto const y_size = yyy.size();
839  auto const z_size = zzz.size();
840 
841  if (n != y_size || n != z_size) {
842  cout << " ( Get reco tacks ) Error ! " << endl;
843  return false;
844  }
845 
846  // Here, we perform a const-cast to double* because, sadly,
847  // TPolyLine3D requires a pointer to a non-const object. We will
848  // trust that ROOT does not mess around with the underlying data.
849  auto xs = const_cast<double*>(xxx.data());
850  auto ys = const_cast<double*>(yyy.data());
851  auto zs = const_cast<double*>(zzz.data());
852 
853  auto const narrowed_size = static_cast<int>(n); // ROOT requires a signed integral type
854  delete gr_reco_xyz;
855  gr_reco_xyz = new TPolyLine3D{narrowed_size, zs, xs, ys};
856  gr_reco_yz = TGraph{narrowed_size, zzz.data(), yyy.data()};
857  gr_reco_xz = TGraph{narrowed_size, zzz.data(), xxx.data()};
858  gr_reco_xy = TGraph{narrowed_size, xxx.data(), yyy.data()};
859 
860  return true;
861  }
862 
863  // Compute the deviation in `segx, ...` of the segment stores at `segnx, ...`
864  // vx, vy, vz are used and cleared afterwards
865  // The `maximum fluctuation` is given by the direction of the give by the Principal Component Analysis (PCA)
866  void TrackMomentumCalculator::compute_max_fluctuation_vector(const std::vector<double> segx,
867  const std::vector<double> segy,
868  const std::vector<double> segz,
869  std::vector<double>& segnx,
870  std::vector<double>& segny,
871  std::vector<double>& segnz,
872  std::vector<bool>& segn_isvalid,
873  std::vector<double>& vx,
874  std::vector<double>& vy,
875  std::vector<double>& vz,
876  bool check_valid_scattered)
877  {
878  auto const na = vx.size();
879 
880  double sumx = 0.0;
881  double sumy = 0.0;
882  double sumz = 0.0;
883 
884  bool isvalid = true;
885  // In case vx, vy, etc have 3 points, probably two are just "linear"
886  // interpolations. In this case, the angle of scattering will be zero.
887  if (check_valid_scattered && na <= 2) { isvalid = false; }
888 
889  // computes the average in x, y, z
890  for (std::size_t i = 0; i < na; ++i) {
891  sumx += vx.at(i);
892  sumy += vy.at(i);
893  sumz += vz.at(i);
894  }
895 
896  sumx /= na;
897  sumy /= na;
898  sumz /= na;
899 
900  std::vector<double> mx;
901  std::vector<double> my;
902  std::vector<double> mz;
903 
904  TMatrixDSym m(3);
905 
906  // Computes the Covariance matrix (Principal Component Analysis (PCA)
907  for (std::size_t i = 0; i < na; ++i) {
908  double const xxw1 = vx.at(i);
909  double const yyw1 = vy.at(i);
910  double const zzw1 = vz.at(i);
911 
912  mx.push_back(xxw1 - sumx);
913  my.push_back(yyw1 - sumy);
914  mz.push_back(zzw1 - sumz);
915 
916  double const xxw0 = mx.at(i);
917  double const yyw0 = my.at(i);
918  double const zzw0 = mz.at(i);
919 
920  m(0, 0) += xxw0 * xxw0 / na;
921  m(0, 1) += xxw0 * yyw0 / na;
922  m(0, 2) += xxw0 * zzw0 / na;
923 
924  m(1, 0) += yyw0 * xxw0 / na;
925  m(1, 1) += yyw0 * yyw0 / na;
926  m(1, 2) += yyw0 * zzw0 / na;
927 
928  m(2, 0) += zzw0 * xxw0 / na;
929  m(2, 1) += zzw0 * yyw0 / na;
930  m(2, 2) += zzw0 * zzw0 / na;
931  }
932 
933  TMatrixDSymEigen me(m);
934 
935  // retrieve eigenvalues and vectors
936  TVectorD eigenval = me.GetEigenValues();
937  TMatrixD eigenvec = me.GetEigenVectors();
938 
939  double max1 = -666.0;
940 
941  double ind1 = 0;
942 
943  // get maximum eingevalue
944  for (int i = 0; i < 3; ++i) {
945  double const p1 = eigenval(i);
946 
947  if (p1 > max1) {
948  max1 = p1;
949  ind1 = i;
950  }
951  }
952 
953  // set the `direction` vector that points to the maximum fluctuation
954  double ax = eigenvec(0, ind1);
955  double ay = eigenvec(1, ind1);
956  double az = eigenvec(2, ind1);
957 
958  if (n_seg > 1) {
959  // for x, y and z, check if the last point is bigger then the previous point.
960  // Ensures that the computed fluctation follows the trend of the track
961  if (segx.at(n_seg - 1) - segx.at(n_seg - 2) > 0)
962  ax = std::abs(ax);
963  else
964  ax = -1.0 * std::abs(ax);
965 
966  if (segy.at(n_seg - 1) - segy.at(n_seg - 2) > 0)
967  ay = std::abs(ay);
968  else
969  ay = -1.0 * std::abs(ay);
970 
971  if (segz.at(n_seg - 1) - segz.at(n_seg - 2) > 0)
972  az = std::abs(az);
973  else
974  az = -1.0 * std::abs(az);
975 
976  segnx.push_back(ax);
977  segny.push_back(ay);
978  segnz.push_back(az);
979  segn_isvalid.push_back(isvalid);
980  }
981 
982  // clear the vectors
983  vx.clear();
984  vy.clear();
985  vz.clear();
986  }
987 
988  /* This function will group each point of the track inside segments with
989  * fixed size `seg_size`.
990  * It returns computed new points `segx, segy, segz` separated by `seg_size`,
991  * the deviation `segnx, ...` between this points and the distance `segL` in
992  * steps of `seg_size` between these points
993  */
994  std::optional<TrackMomentumCalculator::Segments> TrackMomentumCalculator::getSegTracks_(
995  std::vector<double> const& xxx,
996  std::vector<double> const& yyy,
997  std::vector<double> const& zzz,
998  double const seg_size,
999  bool const check_valid_scattered)
1000  {
1001  double stag = 0.0;
1002 
1003  int a1 = xxx.size();
1004  int a2 = yyy.size();
1005  int a3 = zzz.size();
1006 
1007  if ((a1 != a2) || (a1 != a3) || (a2 != a3)) {
1008  cout << " ( Digitize reco tacks ) Error ! " << endl;
1009  return std::nullopt;
1010  }
1011 
1012  int const stopper = seg_stop / seg_size;
1013 
1014  // values to be filled and returned
1015  std::vector<double> segx, segnx;
1016  std::vector<double> segy, segny;
1017  std::vector<double> segz, segnz;
1018  std::vector<bool> segn_isvalid;
1019  std::vector<double> segL;
1020 
1021  n_seg = 0;
1022 
1023  double x0{};
1024  double y0{};
1025  double z0{};
1026 
1027  double x00 = xxx.at(0);
1028  double y00 = yyy.at(0);
1029  double z00 = zzz.at(0);
1030 
1031  int indC = 0;
1032 
1033  // These vectors will keep the points inside reach segments
1034  // They are cleared inside the function `compute_max_fluctuation_vector`
1035  // each time it finishes with a segment
1036  std::vector<double> vx;
1037  std::vector<double> vy;
1038  std::vector<double> vz;
1039 
1040  for (int i = 0; i < a1; i++) {
1041  x0 = xxx.at(i);
1042  y0 = yyy.at(i);
1043  z0 = zzz.at(i);
1044 
1045  double const RR0 = std::hypot(x00 - x0, y00 - y0, z00 - z0);
1046 
1047  if (RR0 >= stag) { // stag is aways set to zero, this is always true
1048 
1049  segx.push_back(x0);
1050  segy.push_back(y0);
1051  segz.push_back(z0);
1052 
1053  segL.push_back(0);
1054 
1055  // TGraph
1056  x_seg[n_seg] = x0;
1057  y_seg[n_seg] = y0;
1058  z_seg[n_seg] = z0;
1059 
1060  n_seg++;
1061 
1062  vx.push_back(x0);
1063  vy.push_back(y0);
1064  vz.push_back(z0);
1065 
1066  indC = i + 1;
1067 
1068  break;
1069  }
1070  }
1071 
1072  for (int i = indC; i < a1 - 1; i++) { // starting at second point (i=1) if stag set to zero
1073  // current point
1074  double const x1 = xxx.at(i);
1075  double const y1 = yyy.at(i);
1076  double const z1 = zzz.at(i);
1077 
1078  // distante from previous point
1079  double const dr1 = std::hypot(x1 - x0, y1 - y0, z1 - z0);
1080 
1081  // next point
1082  double const x2 = xxx.at(i + 1);
1083  double const y2 = yyy.at(i + 1);
1084  double const z2 = zzz.at(i + 1);
1085 
1086  // distant of next point to previous point
1087  double const dr2 = std::hypot(x2 - x0, y2 - y0, z2 - z0);
1088 
1089  if (dr1 < seg_size) {
1090  vx.push_back(x1);
1091  vy.push_back(y1);
1092  vz.push_back(z1);
1093  }
1094 
1095  /* If current point is inside segment length w.r.t. the first point of
1096  * the segment (x0,y0,z0), but the next point is outsize: create a new
1097  * point in between (x1,y1,z1) and (x2,y2,z2) in which will be exacly at
1098  * the segment length (w.r.t. to the first point) This is done using the
1099  * cosines law for a given factor `t` times dr (x2-x1, ...), and so:
1100  *
1101  * (ds)^2 = (dr1)^2 + (t*dr)^2 + 2*dot_product(dr1, t*dr)
1102  * Using cos(180-theta) = -cos(theta))
1103  *
1104  * Solve for `t` the second degree equation: t^2 + beta*t + gamma = 0
1105  */
1106  if (dr1 <= seg_size && dr2 > seg_size) {
1107  double const dx = x2 - x1;
1108  double const dy = y2 - y1;
1109  double const dz = z2 - z1;
1110  double const dr = std::hypot(dx, dy, dz);
1111 
1112  if (dr == 0) {
1113  cout << " ( Zero ) Error ! " << endl;
1114  return std::nullopt;
1115  }
1116 
1117  double const beta = 2.0 * ((x1 - x0) * dx + (y1 - y0) * dy + (z1 - z0) * dz) / (dr * dr);
1118 
1119  double const gamma = (dr1 * dr1 - seg_size * seg_size) / (dr * dr);
1120  double const delta = beta * beta - 4.0 * gamma;
1121 
1122  if (delta < 0.0) {
1123  cout << " ( Discriminant ) Error ! " << endl;
1124  return std::nullopt;
1125  }
1126 
1127  // solves for t
1128  double const lysi1 = (-beta + std::sqrt(delta)) / 2.0;
1129  double const t = lysi1;
1130 
1131  // find next points in that will exactly at the segment length from x0
1132  double const xp = x1 + t * dx;
1133  double const yp = y1 + t * dy;
1134  double const zp = z1 + t * dz;
1135 
1136  // Add points to be returned
1137  segx.push_back(xp);
1138  segy.push_back(yp);
1139  segz.push_back(zp);
1140 
1141  segL.push_back(n_seg * seg_size);
1142 
1143  // for TGraph
1144  x_seg[n_seg] = xp;
1145  y_seg[n_seg] = yp;
1146  z_seg[n_seg] = zp;
1147 
1148  n_seg++;
1149 
1150  // This are the new `x0` points (for next segment)
1151  x0 = xp;
1152  y0 = yp;
1153  z0 = zp;
1154 
1155  // Add points to segment
1156  vx.push_back(x0);
1157  vy.push_back(y0);
1158  vz.push_back(z0);
1159 
1160  if (n_seg <= 1) // This should never happen
1161  return std::nullopt;
1162 
1163  // Now, compute the deviation in `segx, ...` of the segment
1164  // vx, vy, vz are used and cleared afterwards
1166  segx, segy, segz, segnx, segny, segnz, segn_isvalid, vx, vy, vz, check_valid_scattered);
1167 
1168  // Starting over
1169  vx.push_back(x0);
1170  vy.push_back(y0);
1171  vz.push_back(z0);
1172  }
1173  else if (dr1 > seg_size) { // in this case, just interpolate until reach `seg_size`
1174 
1175  // Rolling `i` back for the next iteration.
1176  // Because the current point does not belong to this segment
1177  i = (i - 1);
1178 
1179  double const dx = x1 - x0;
1180  double const dy = y1 - y0;
1181  double const dz = z1 - z0;
1182  double const dr = std::hypot(dx, dy, dz);
1183 
1184  if (dr == 0) {
1185  cout << " ( Zero ) Error ! " << endl;
1186  return std::nullopt;
1187  }
1188 
1189  // computes the point by simple interpolation
1190  double const t = seg_size / dr;
1191  double const xp = x0 + t * dx;
1192  double const yp = y0 + t * dy;
1193  double const zp = z0 + t * dz;
1194 
1195  segx.push_back(xp);
1196  segy.push_back(yp);
1197  segz.push_back(zp);
1198  segL.push_back(1.0 * n_seg * 1.0 * seg_size + stag);
1199 
1200  // for TGraph
1201  x_seg[n_seg] = xp;
1202  y_seg[n_seg] = yp;
1203  z_seg[n_seg] = zp;
1204 
1205  n_seg++;
1206 
1207  x0 = xp;
1208  y0 = yp;
1209  z0 = zp;
1210 
1211  vx.push_back(x0);
1212  vy.push_back(y0);
1213  vz.push_back(z0);
1214 
1215  if (n_seg <= 1) // This should never happen
1216  return std::nullopt;
1217 
1218  // Now, compute the deviation in `segx, ...` of the segment
1219  // vx, vy, vz are used and cleared afterwards
1221  segx, segy, segz, segnx, segny, segnz, segn_isvalid, vx, vy, vz, check_valid_scattered);
1222 
1223  // vectors are cleared in previous step
1224  vx.push_back(x0);
1225  vy.push_back(y0);
1226  vz.push_back(z0);
1227  }
1228 
1229  if (n_seg >= (stopper + 1.0) && seg_stop != -1) break;
1230  }
1231 
1232  delete gr_seg_xyz;
1233  gr_seg_xyz = new TPolyLine3D{n_seg, z_seg, x_seg, y_seg};
1234  gr_seg_yz = TGraph{n_seg, z_seg, y_seg};
1235  gr_seg_xz = TGraph{n_seg, z_seg, x_seg};
1236  gr_seg_xy = TGraph{n_seg, x_seg, y_seg};
1237 
1238  return std::make_optional<Segments>(
1239  Segments{segx, segnx, segy, segny, segz, segnz, segL, segn_isvalid});
1240  }
1241 
1242  /* Computes the rms by groups of `thick`
1243  *
1244  */
1245  std::tuple<double, double, double> TrackMomentumCalculator::getDeltaThetaRMS_(
1246  Segments const& segments,
1247  double const thick) const
1248  {
1249  auto const& segnx = segments.nx;
1250  auto const& segny = segments.ny;
1251  auto const& segnz = segments.nz;
1252  auto const& segL = segments.L;
1253 
1254  int const a1 = segnx.size();
1255  int const a2 = segny.size();
1256  int const a3 = segnz.size();
1257 
1258  if (a1 != a2 || a1 != a3) {
1259  cout << " ( Get RMS ) Error ! " << endl;
1260  return std::make_tuple(0., 0., 0.);
1261  }
1262 
1263  int const tot = a1;
1264 
1265  std::vector<double> buf0;
1266 
1267  for (int i = 0; i < tot; i++) {
1268  double const dx = segnx.at(i);
1269  double const dy = segny.at(i);
1270  double const dz = segnz.at(i);
1271 
1272  TVector3 const vec_z{dx, dy, dz};
1273  TVector3 vec_x;
1274  TVector3 vec_y;
1275 
1276  double const switcher = basex.Dot(vec_z);
1277 
1278  if (switcher <= 0.995) {
1279  vec_y = vec_z.Cross(basex).Unit();
1280  vec_x = vec_y.Cross(vec_z);
1281  }
1282  else {
1283  // cout << " It switched ! Isn't this lovely !!! " << endl;
1284  vec_y = basez.Cross(vec_z).Unit();
1285  vec_x = vec_y.Cross(vec_z);
1286  }
1287 
1288  TVector3 const Rx{vec_x.Dot(basex), vec_x.Dot(basey), vec_x.Dot(basez)};
1289  TVector3 const Ry{vec_y.Dot(basex), vec_y.Dot(basey), vec_y.Dot(basez)};
1290  TVector3 const Rz{vec_z.Dot(basex), vec_z.Dot(basey), vec_z.Dot(basez)};
1291 
1292  double const refL = segL.at(i);
1293 
1294  for (int j = i; j < tot; j++) {
1295  double const L1 = segL.at(j);
1296 
1297  double const dz1 = L1 - refL;
1298 
1299  if (dz1 >= thick) {
1300  double const here_dx = segnx.at(j);
1301  double const here_dy = segny.at(j);
1302  double const here_dz = segnz.at(j);
1303 
1304  TVector3 const here_vec{here_dx, here_dy, here_dz};
1305  TVector3 const rot_here{Rx.Dot(here_vec), Ry.Dot(here_vec), Rz.Dot(here_vec)};
1306 
1307  double const scx = rot_here.X();
1308  double const scy = rot_here.Y();
1309  double const scz = rot_here.Z();
1310 
1311  double const azy = find_angle(scz, scy);
1312  double const azx = find_angle(scz, scx);
1313 
1314  constexpr double ULim = 10000.0; // Avoid huge (wrong) angles
1315  constexpr double LLim = -10000.0;
1316 
1317  if (azy <= ULim && azy >= LLim) { // safe scatter in the yz plane
1318 
1319  if (azx <= ULim && azx >= LLim) { // safe scatter in the za plane
1320  if (fMCSAngleMethod == kAnglezx) {
1321  buf0.push_back(azx); // scattered angle z-x
1322  }
1323  else if (fMCSAngleMethod == kAnglezy) {
1324  buf0.push_back(azy);
1325  }
1326  else if (fMCSAngleMethod == kAngleCombined) {
1327  buf0.push_back(std::hypot(azx, azy));
1328  }
1329  }
1330  }
1331  break; // of course !
1332  }
1333  }
1334  }
1335 
1336  int const nmeas = buf0.size();
1337  double nnn = 0.0;
1338 
1339  double mean = 0.0;
1340  double rms = 0.0;
1341  double rmse = 0.0;
1342 
1343  for (int i = 0; i < nmeas; i++) {
1344  mean += buf0.at(i);
1345  nnn++;
1346  }
1347 
1348  mean = mean / nnn;
1349 
1350  for (int i = 0; i < nmeas; i++)
1351  rms += ((buf0.at(i)) * (buf0.at(i)));
1352 
1353  rms = rms / (nnn);
1354  rms = std::sqrt(rms);
1355  rmse = rms / std::sqrt(2.0 * tot);
1356 
1357  double rms1 = rms;
1358 
1359  rms = 0.0;
1360 
1361  double ntot1 = 0.0;
1362  double const lev1 = 2.50;
1363 
1364  for (int i = 0; i < nmeas; i++) {
1365  double const amp = buf0.at(i);
1366  if (amp < (mean + lev1 * rms1) && amp > (mean - lev1 * rms1)) {
1367  ++ntot1;
1368  rms += amp * amp;
1369  }
1370  }
1371 
1372  rms = rms / (ntot1);
1373  rms = std::sqrt(rms);
1374  rmse = rms / std::sqrt(2.0 * ntot1);
1375  return std::make_tuple(mean, rms, rmse);
1376  }
1377 
1378  double TrackMomentumCalculator::find_angle(double vz, double vy) const
1379  {
1380  double thetayz = -999.0;
1381 
1382  if (vz > 0 && vy > 0) {
1383  double ratio = std::abs(vy / vz);
1384  thetayz = std::atan(ratio);
1385  }
1386 
1387  else if (vz < 0 && vy > 0) {
1388  double ratio = std::abs(vy / vz);
1389  thetayz = std::atan(ratio);
1390  thetayz = TMath::Pi() - thetayz;
1391  }
1392 
1393  else if (vz < 0 && vy < 0) {
1394  double ratio = std::abs(vy / vz);
1395  thetayz = std::atan(ratio);
1396  thetayz = thetayz + TMath::Pi();
1397  }
1398 
1399  else if (vz > 0 && vy < 0) {
1400  double ratio = std::abs(vy / vz);
1401  thetayz = std::atan(ratio);
1402  thetayz = 2.0 * TMath::Pi() - thetayz;
1403  }
1404 
1405  else if (vz == 0 && vy > 0) {
1406  thetayz = TMath::Pi() / 2.0;
1407  }
1408 
1409  else if (vz == 0 && vy < 0) {
1410  thetayz = 3.0 * TMath::Pi() / 2.0;
1411  }
1412  else if (vz > 0 && vy == 0) {
1413  thetayz = 0;
1414  }
1415  else if (vz < 0 && vy == 0) {
1416  thetayz = TMath::Pi();
1417  }
1418 
1419  if (thetayz > TMath::Pi()) { thetayz = thetayz - 2.0 * TMath::Pi(); }
1420 
1421  return 1000.0 * thetayz;
1422  }
1423 
1424  double TrackMomentumCalculator::my_g(double xx, double Q, double s) const
1425  {
1426  if (s == 0.) {
1427  cout << " Error : The code tries to divide by zero ! " << endl;
1428  return 0.;
1429  }
1430 
1431  double const arg = (xx - Q) / s;
1432  double const result = -0.5 * std::log(2.0 * TMath::Pi()) - std::log(s) - 0.5 * arg * arg;
1433 
1434  if (std::isnan(result) || std::isinf(result)) {
1435  cout << " Is nan ! my_g ! " << -std::log(s) << ", " << s << endl;
1436  }
1437 
1438  return result;
1439  }
1440 
1441  double TrackMomentumCalculator::my_mcs_llhd(std::vector<double> const& dEi,
1442  std::vector<double> const& dEj,
1443  std::vector<double> const& dthij,
1444  std::vector<double> const& ind,
1445  double const x0,
1446  double const x1) const
1447  {
1448  double p = x0;
1449  double theta0x = x1;
1450 
1451  double result = 0.0;
1452  double nnn1 = dEi.size(); // number of segments of energy
1453 
1454  double red_length = (steps_size) / rad_length;
1455  double addth = 0;
1456 
1457  for (int i = 0; i < nnn1; i++) {
1458  double Ei = p - dEi.at(i); // Estimated energy at point i
1459  double Ej = p - dEj.at(i); // Estimated enery at point j
1460 
1461  // If the momentum p choosen allows that the muon stopped inside, add 1 rad to the change in scatter angle (as the particle stops)
1462  if (Ei > 0 && Ej < 0) addth = 3.14 * 1000.0;
1463 
1464  Ei = std::abs(Ei);
1465  Ej = std::abs(Ej);
1466 
1467  // Highland formula
1468  // Parameters given at Particle Data Group https://pdg.lbl.gov/2023/web/viewer.html?file=../reviews/rpp2022-rev-passage-particles-matter.pdf
1469  double tH0 =
1470  (13.6 / std::sqrt(Ei * Ej)) * (1.0 + 0.038 * std::log(red_length)) * std::sqrt(red_length);
1471 
1472  double rms = -1.0;
1473 
1474  if (ind.at(i) == 1) {
1475  // Computes the rms of angle
1476  rms = std::hypot(tH0, theta0x);
1477 
1478  double const DT = dthij.at(i) + addth;
1479  double const prob = my_g(DT, 0.0, rms); // Computes log likelihood
1480 
1481  result = result - 2.0 * prob; // Adds for each segment
1482  }
1483  }
1484 
1485  if (std::isnan(result) || std::isinf(result)) {
1486  std::cout << " Is nan ! my_mcs_llhd ( 1 ) ! " << std::endl;
1487  }
1488  return result;
1489  }
1490 
1491 } // namespace track
Float_t x
Definition: compare.C:6
Provides recob::Track data product.
double GetMomentumMultiScatterLLHD(art::Ptr< recob::Track > const &trk, const bool checkValidPoints=false, const int maxMomentum_MeV=7500, const double min_resolution=0.001, const double max_resolution=800, const bool check_valid_scattered=false, const double angle_correction=0.757)
Calculate muon momentum (GeV) using multiple coulomb scattering by log likelihood.
Double_t xx
Definition: macro.C:12
double GetMomentumMultiScatterChi2(art::Ptr< recob::Track > const &trk, const bool checkValidPoints=false, const int maxMomentum_MeV=7500, const double min_resolution=0, const double max_resolution=45)
Calculate muon momentum (GeV) using multiple coulomb scattering. Chi2 minimization of the Highland fo...
double my_g(double xx, double Q, double s) const
chi square minizer using Minuit2, it will minize (xx-Q)/s
Float_t y1[n_points_granero]
Definition: compare.C:5
Float_t x1[n_points_granero]
Definition: compare.C:5
Point_t const & LocationAtPoint(size_t i) const
Access to track position at different points.
Definition: Track.h:160
bool HasValidPoint(size_t i) const
Various functions related to the presence and the number of (valid) points.
Definition: Track.h:145
size_t NumberTrajectoryPoints() const
Various functions related to the presence and the number of (valid) points.
Definition: Track.h:136
constexpr auto abs(T v)
Returns the absolute value of the argument.
MaybeLogger_< ELseverityLevel::ELsev_error, false > LogError
std::optional< Segments > getSegTracks_(std::vector< double > const &xxx, std::vector< double > const &yyy, std::vector< double > const &zzz, double seg_size, bool check_valid_scattered=false)
Split tracks into segments to calculate the scattered angle later. Check DOI 10.1088/1748-0221/12/10/...
Float_t y2[n_points_geant4]
Definition: compare.C:26
int getDeltaThetaij_(std::vector< double > &ei, std::vector< double > &ej, std::vector< double > &th, std::vector< double > &ind, Segments const &segments, double thick, double const angle_correction) const
Gets the scatterd angle for all the segments.
#define a2
#define a3
Use scattered angle z-x (z is along the particle&#39;s direction)
double Length(size_t p=0) const
Access to various track properties.
Definition: Track.h:207
double find_angle(double vz, double vy) const
Gets angle between two vy and vz.
Float_t E
Definition: plot.C:20
Float_t thick
Definition: plot.C:23
double my_mcs_llhd(std::vector< double > const &dEi, std::vector< double > const &dEj, std::vector< double > const &dthij, std::vector< double > const &ind, double x0, double x1) const
Minimizer of log likelihood for scattered angle.
bool plotRecoTracks_(std::vector< double > const &xxx, std::vector< double > const &yyy, std::vector< double > const &zzz)
Struct to store segments. x, y and z are the 3D points of the segment nx, ny, nz forms the vector tha...
std::tuple< double, double, double > getDeltaThetaRMS_(Segments const &segments, double thick) const
Gets the scattered angle RMS for a all segments.
Use space angle: sqrt( zx^2 + zy^2 )/sqrt(2)
float dEdx(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp, const TCSlice &slc, TP3D &tp3d)
Definition: PFPUtils.cxx:2671
double value
Definition: spectrum.C:18
double mean(const std::vector< short > &wf, size_t start, size_t nsample)
Definition: UtilFunc.cxx:13
void compute_max_fluctuation_vector(const std::vector< double > segx, const std::vector< double > segy, const std::vector< double > segz, std::vector< double > &segnx, std::vector< double > &segny, std::vector< double > &segnz, std::vector< bool > &segn_isvalid, std::vector< double > &vx, std::vector< double > &vy, std::vector< double > &vz, bool check_valid_scattered)
Computes the vector with most scattering inside a segment with size steps_size.
double GetMuMultiScatterLLHD3(art::Ptr< recob::Track > const &trk, bool dir)
TDirectory * dir
Definition: macro.C:5
MaybeLogger_< ELseverityLevel::ELsev_success, false > LogDebug
#define MF_LOG_DEBUG(id)
TVector3 GetMultiScatterStartingPoint(art::Ptr< recob::Track > const &trk)
Char_t n[5]
Float_t x2[n_points_geant4]
Definition: compare.C:26
Float_t e
Definition: plot.C:35
double GetTrackMomentum(double trkrange, int pdg) const
#define a1