LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
TrajectoryMCSFitter.cxx
Go to the documentation of this file.
1 #include "TrajectoryMCSFitter.h"
6 
8 
9 #include "TMatrixDSym.h"
10 #include "TMatrixDSymEigen.h"
11 
12 using namespace std;
13 using namespace trkf;
14 using namespace recob::tracking;
15 
16 recob::MCSFitResult TrajectoryMCSFitter::fitMcs(const recob::TrackTrajectory& traj, int pid) const
17 {
18  //
19  // Break the trajectory in segments of length approximately equal to segLen_
20  //
21  vector<size_t> breakpoints;
22  vector<float> segradlengths;
23  vector<float> cumseglens;
24  breakTrajInSegments(traj, breakpoints, segradlengths, cumseglens);
25  //
26  // Fit segment directions, and get 3D angles between them
27  //
28  if (segradlengths.size() < 2) return recob::MCSFitResult();
29  vector<float> dtheta;
30  Vector_t pcdir0;
31  Vector_t pcdir1;
32  for (unsigned int p = 0; p < segradlengths.size(); p++) {
33  linearRegression(traj, breakpoints[p], breakpoints[p + 1], pcdir1);
34  if (p > 0) {
35  if (segradlengths[p] < -100. || segradlengths[p - 1] < -100.) { dtheta.push_back(-999.); }
36  else {
37  const double cosval =
38  pcdir0.X() * pcdir1.X() + pcdir0.Y() * pcdir1.Y() + pcdir0.Z() * pcdir1.Z();
39  //assert(std::abs(cosval)<=1);
40  //units are mrad
41  double dt = 1000. * acos(cosval); //should we try to use expansion for small angles?
42  dtheta.push_back(dt);
43  }
44  }
45  pcdir0 = pcdir1;
46  }
47  //
48  // Perform likelihood scan in forward and backward directions
49  //
50  vector<float> cumLenFwd;
51  vector<float> cumLenBwd;
52  for (unsigned int i = 0; i < cumseglens.size() - 2; i++) {
53  cumLenFwd.push_back(cumseglens[i]);
54  cumLenBwd.push_back(cumseglens.back() - cumseglens[i + 2]);
55  }
56  double detAngResol = DetectorAngularResolution(std::abs(traj.StartDirection().Z()));
57  const ScanResult fwdResult =
58  doLikelihoodScan(dtheta, segradlengths, cumLenFwd, true, pid, detAngResol);
59  const ScanResult bwdResult =
60  doLikelihoodScan(dtheta, segradlengths, cumLenBwd, false, pid, detAngResol);
61  //
62  return recob::MCSFitResult(pid,
63  fwdResult.p,
64  fwdResult.pUnc,
65  fwdResult.logL,
66  bwdResult.p,
67  bwdResult.pUnc,
68  bwdResult.logL,
69  segradlengths,
70  dtheta);
71 }
72 
73 void TrajectoryMCSFitter::breakTrajInSegments(const recob::TrackTrajectory& traj,
74  vector<size_t>& breakpoints,
75  vector<float>& segradlengths,
76  vector<float>& cumseglens) const
77 {
78  //
80  auto const* _SCE = (applySCEcorr_ ? lar::providerFrom<spacecharge::SpaceChargeService>() : NULL);
81  //
82  const double trajlen = traj.Length();
83  const double thisSegLen =
84  (trajlen > (segLen_ * minNSegs_) ? segLen_ : trajlen / double(minNSegs_));
85  // std::cout << "track with length=" << trajlen << " broken in nseg=" << std::max(minNSegs_,int(trajlen/segLen_)) << " of length=" << thisSegLen << " where segLen_=" << segLen_ << std::endl;
86  //
87  constexpr double lar_radl_inv = 1. / 14.0;
88  cumseglens.push_back(0.); //first segment has zero cumulative length from previous segments
89  double thislen = 0.;
90  double totlen = 0.;
91  auto nextValid = traj.FirstValidPoint();
92  breakpoints.push_back(nextValid);
93  auto pos0 = traj.LocationAtPoint(nextValid);
94  if (applySCEcorr_) {
95  geo::TPCID tpcid = geom->FindTPCAtPosition(pos0);
96  geo::Vector_t pos0_offset(0., 0., 0.);
97  if (tpcid.isValid) { pos0_offset = _SCE->GetCalPosOffsets(pos0, tpcid.TPC); }
98  pos0.SetX(pos0.X() - pos0_offset.X());
99  pos0.SetY(pos0.Y() + pos0_offset.Y());
100  pos0.SetZ(pos0.Z() + pos0_offset.Z());
101  }
102  auto dir0 = traj.DirectionAtPoint(nextValid);
103  nextValid = traj.NextValidPoint(nextValid + 1);
104  int npoints = 0;
105  while (nextValid != recob::TrackTrajectory::InvalidIndex) {
106  if (npoints == 0) dir0 = traj.DirectionAtPoint(nextValid);
107  auto pos1 = traj.LocationAtPoint(nextValid);
108  if (applySCEcorr_) {
109  geo::TPCID tpcid = geom->FindTPCAtPosition(pos1);
110  geo::Vector_t pos1_offset(0., 0., 0.);
111  if (tpcid.isValid) { pos1_offset = _SCE->GetCalPosOffsets(pos1, tpcid.TPC); }
112  pos1.SetX(pos1.X() - pos1_offset.X());
113  pos1.SetY(pos1.Y() + pos1_offset.Y());
114  pos1.SetZ(pos1.Z() + pos1_offset.Z());
115  }
116  //increments along the initial direction of the segment
117  auto step = (pos1 - pos0).R();
118  thislen += dir0.Dot(pos1 - pos0);
119  totlen += step;
120  pos0 = pos1;
121  // //fixme: testing alternative approaches here
122  // //test1: increments following scatters
123  // auto step = (pos1-pos0).R();
124  // thislen += step;
125  // totlen += step;
126  // pos0=pos1;
127  // //test2: end-start distance along the initial direction of the segment
128  // thislen = dir0.Dot(pos1-pos0);
129  // totlen = (pos1-pos0).R();
130  //
131  npoints++;
132  if (thislen >= (thisSegLen - segLenTolerance_)) {
133  breakpoints.push_back(nextValid);
134  if (npoints >= minHitsPerSegment_)
135  segradlengths.push_back(thislen * lar_radl_inv);
136  else
137  segradlengths.push_back(-999.);
138  cumseglens.push_back(totlen);
139  thislen = 0.;
140  npoints = 0;
141  }
142  nextValid = traj.NextValidPoint(nextValid + 1);
143  }
144  //then add last segment
145  if (thislen > 0.) {
146  breakpoints.push_back(traj.LastValidPoint() + 1);
147  segradlengths.push_back(thislen * lar_radl_inv);
148  cumseglens.push_back(cumseglens.back() + thislen);
149  }
150  return;
151 }
152 
153 const TrajectoryMCSFitter::ScanResult TrajectoryMCSFitter::doLikelihoodScan(
154  std::vector<float>& dtheta,
155  std::vector<float>& seg_nradlengths,
156  std::vector<float>& cumLen,
157  bool fwdFit,
158  int pid,
159  float pmin,
160  float pmax,
161  float pstep,
162  float detAngResol) const
163 {
164  int best_idx = -1;
165  float best_logL = std::numeric_limits<float>::max();
166  float best_p = -1.0;
167  std::vector<float> vlogL;
168  for (float p_test = pmin; p_test <= pmax; p_test += pstep) {
169  float logL = mcsLikelihood(p_test, detAngResol, dtheta, seg_nradlengths, cumLen, fwdFit, pid);
170  if (logL < best_logL) {
171  best_p = p_test;
172  best_logL = logL;
173  best_idx = vlogL.size();
174  }
175  vlogL.push_back(logL);
176  }
177  //
178  //uncertainty from left side scan
179  float lunc = -1.;
180  if (best_idx > 0) {
181  for (int j = best_idx - 1; j >= 0; j--) {
182  float dLL = vlogL[j] - vlogL[best_idx];
183  if (dLL >= 0.5) {
184  lunc = (best_idx - j) * pstep;
185  break;
186  }
187  }
188  }
189  //uncertainty from right side scan
190  float runc = -1.;
191  if (best_idx < int(vlogL.size() - 1)) {
192  for (unsigned int j = best_idx + 1; j < vlogL.size(); j++) {
193  float dLL = vlogL[j] - vlogL[best_idx];
194  if (dLL >= 0.5) {
195  runc = (j - best_idx) * pstep;
196  break;
197  }
198  }
199  }
200  return ScanResult(best_p, std::max(lunc, runc), best_logL);
201 }
202 
203 const TrajectoryMCSFitter::ScanResult TrajectoryMCSFitter::doLikelihoodScan(
204  std::vector<float>& dtheta,
205  std::vector<float>& seg_nradlengths,
206  std::vector<float>& cumLen,
207  bool fwdFit,
208  int pid,
209  float detAngResol) const
210 {
211 
212  //do a first, coarse scan
213  const ScanResult& coarseRes = doLikelihoodScan(
214  dtheta, seg_nradlengths, cumLen, fwdFit, pid, pMin_, pMax_, pStepCoarse_, detAngResol);
215 
216  float pmax = std::min(coarseRes.p + fineScanWindow_, pMax_);
217  float pmin = std::max(coarseRes.p - fineScanWindow_, pMin_);
218  if (coarseRes.pUnc < (std::numeric_limits<float>::max() - 1.)) {
219  pmax = std::min(coarseRes.p + 2 * coarseRes.pUnc, pMax_);
220  pmin = std::max(coarseRes.p - 2 * coarseRes.pUnc, pMin_);
221  }
222 
223  //do the fine grained scan in a smaller region
224  const ScanResult& refineRes =
225  doLikelihoodScan(dtheta, seg_nradlengths, cumLen, fwdFit, pid, pmin, pmax, pStep_, detAngResol);
226 
227  return refineRes;
228 }
229 
230 void TrajectoryMCSFitter::linearRegression(const recob::TrackTrajectory& traj,
231  const size_t firstPoint,
232  const size_t lastPoint,
233  Vector_t& pcdir) const
234 {
235  //
237  auto const* _SCE = (applySCEcorr_ ? lar::providerFrom<spacecharge::SpaceChargeService>() : NULL);
238  //
239  int npoints = 0;
240  geo::vect::MiddlePointAccumulator middlePointCalc;
241  size_t nextValid = firstPoint;
242  //fixme explore a max number of points to use for linear regression
243  //while (nextValid<std::min(firstPoint+10,lastPoint)) {
244  while (nextValid < lastPoint) {
245  auto tempP = traj.LocationAtPoint(nextValid);
246  if (applySCEcorr_) {
247  geo::TPCID tpcid = geom->FindTPCAtPosition(tempP);
248  geo::Vector_t tempP_offset(0., 0., 0.);
249  if (tpcid.isValid) { tempP_offset = _SCE->GetCalPosOffsets(tempP, tpcid.TPC); }
250  tempP.SetX(tempP.X() - tempP_offset.X());
251  tempP.SetY(tempP.Y() + tempP_offset.Y());
252  tempP.SetZ(tempP.Z() + tempP_offset.Z());
253  }
254  middlePointCalc.add(tempP);
255  //middlePointCalc.add(traj.LocationAtPoint(nextValid));
256  nextValid = traj.NextValidPoint(nextValid + 1);
257  npoints++;
258  }
259  const auto avgpos = middlePointCalc.middlePoint();
260  const double norm = 1. / double(npoints);
261  //
262  //assert(npoints>0);
263  //
264  TMatrixDSym m(3);
265  nextValid = firstPoint;
266  while (nextValid < lastPoint) {
267  auto p = traj.LocationAtPoint(nextValid);
268  if (applySCEcorr_) {
269  geo::TPCID tpcid = geom->FindTPCAtPosition(p);
270  geo::Vector_t p_offset(0., 0., 0.);
271  if (tpcid.isValid) { p_offset = _SCE->GetCalPosOffsets(p, tpcid.TPC); }
272  p.SetX(p.X() - p_offset.X());
273  p.SetY(p.Y() + p_offset.Y());
274  p.SetZ(p.Z() + p_offset.Z());
275  }
276  const double xxw0 = p.X() - avgpos.X();
277  const double yyw0 = p.Y() - avgpos.Y();
278  const double zzw0 = p.Z() - avgpos.Z();
279  m(0, 0) += xxw0 * xxw0 * norm;
280  m(0, 1) += xxw0 * yyw0 * norm;
281  m(0, 2) += xxw0 * zzw0 * norm;
282  m(1, 0) += yyw0 * xxw0 * norm;
283  m(1, 1) += yyw0 * yyw0 * norm;
284  m(1, 2) += yyw0 * zzw0 * norm;
285  m(2, 0) += zzw0 * xxw0 * norm;
286  m(2, 1) += zzw0 * yyw0 * norm;
287  m(2, 2) += zzw0 * zzw0 * norm;
288  nextValid = traj.NextValidPoint(nextValid + 1);
289  }
290  //
291  const TMatrixDSymEigen me(m);
292  const auto& eigenval = me.GetEigenValues();
293  const auto& eigenvec = me.GetEigenVectors();
294  //
295  int maxevalidx = 0;
296  double maxeval = eigenval(0);
297  for (int i = 1; i < 3; ++i) {
298  if (eigenval(i) > maxeval) {
299  maxevalidx = i;
300  maxeval = eigenval(i);
301  }
302  }
303  //
304  pcdir = Vector_t(eigenvec(0, maxevalidx), eigenvec(1, maxevalidx), eigenvec(2, maxevalidx));
305  if (traj.DirectionAtPoint(firstPoint).Dot(pcdir) < 0.) pcdir *= -1.;
306  //
307 }
308 
309 double TrajectoryMCSFitter::mcsLikelihood(double p,
310  double theta0x,
311  std::vector<float>& dthetaij,
312  std::vector<float>& seg_nradl,
313  std::vector<float>& cumLen,
314  bool fwd,
315  int pid) const
316 {
317  //
318  const int beg = (fwd ? 0 : (dthetaij.size() - 1));
319  const int end = (fwd ? dthetaij.size() : -1);
320  const int incr = (fwd ? +1 : -1);
321  //
322  // bool print = false;
323  //
324  const double m = mass(pid);
325  const double m2 = m * m;
326  const double Etot = sqrt(p * p + m2); //Initial energy
327  double Eij2 = 0.;
328  //
329  double const fixedterm = 0.5 * std::log(2.0 * M_PI);
330  double result = 0;
331  for (int i = beg; i != end; i += incr) {
332  if (dthetaij[i] < 0) {
333  //cout << "skip segment with too few points" << endl;
334  continue;
335  }
336  //
337  const double Eij = GetE(Etot, cumLen[i], m);
338  Eij2 = Eij * Eij;
339  //
340  if (Eij2 <= m2) {
341  result = std::numeric_limits<double>::max();
342  break;
343  }
344  const double pij = sqrt(Eij2 - m2); //momentum at this segment
345  const double beta = sqrt(1. - ((m2) / (pij * pij + m2)));
346  constexpr double HighlandSecondTerm = 0.038;
347  const double tH0 = (HighlandFirstTerm(pij) / (pij * beta)) *
348  (1.0 + HighlandSecondTerm * std::log(seg_nradl[i])) * sqrt(seg_nradl[i]);
349  const double rms = sqrt(2.0 * (tH0 * tH0 + theta0x * theta0x));
350  if (rms == 0.0) {
351  std::cout << " Error : RMS cannot be zero ! " << std::endl;
352  return std::numeric_limits<double>::max();
353  }
354  const double arg = dthetaij[i] / rms;
355  result += (std::log(rms) + 0.5 * arg * arg + fixedterm);
356  }
357  return result;
358 }
359 
360 double TrajectoryMCSFitter::energyLossLandau(const double mass2,
361  const double e2,
362  const double x) const
363 {
364  //
365  // eq. (33.11) in http://pdg.lbl.gov/2016/reviews/rpp2016-rev-passage-particles-matter.pdf (except density correction is ignored)
366  //
367  if (x <= 0.) return 0.;
368  constexpr double Iinv2 = 1. / (188.E-6 * 188.E-6);
369  constexpr double matConst = 1.4 * 18. / 40.; //density*Z/A
370  constexpr double me = 0.511;
371  constexpr double kappa = 0.307075;
372  constexpr double j = 0.200;
373  //
374  const double beta2 = (e2 - mass2) / e2;
375  const double gamma2 = 1. / (1.0 - beta2);
376  const double epsilon = 0.5 * kappa * x * matConst / beta2;
377  //
378  return 0.001 * epsilon * (log(2. * me * beta2 * gamma2 * epsilon * Iinv2) + j - beta2);
379 }
380 //
381 double TrajectoryMCSFitter::energyLossBetheBloch(const double mass, const double e2) const
382 {
383  // stolen, mostly, from GFMaterialEffects.
384  constexpr double Iinv = 1. / 188.E-6;
385  constexpr double matConst = 1.4 * 18. / 40.; //density*Z/A
386  constexpr double me = 0.511;
387  constexpr double kappa = 0.307075;
388  //
389  const double beta2 = (e2 - mass * mass) / e2;
390  const double gamma2 = 1. / (1.0 - beta2);
391  const double massRatio = me / mass;
392  const double argument = (2. * me * gamma2 * beta2 * Iinv) *
393  std::sqrt(1 + 2 * std::sqrt(gamma2) * massRatio + massRatio * massRatio);
394  //
395  double dedx = kappa * matConst / beta2;
396  //
397  if (mass == 0.0) return (0.0);
398  if (argument <= exp(beta2)) { dedx = 0.; }
399  else {
400  dedx *= (log(argument) - beta2) * 1.E-3; // Bethe-Bloch, converted to GeV/cm
401  if (dedx < 0.) dedx = 0.;
402  }
403  return dedx;
404 }
405 //
406 double TrajectoryMCSFitter::GetE(const double initial_E,
407  const double length_travelled,
408  const double m) const
409 {
410  //
411  if (eLossMode_ == 1) {
412  // ELoss mode: MIP (constant)
413  constexpr double kcal = 0.002105;
414  return (initial_E - kcal * length_travelled); //energy at this segment
415  }
416  //
417  // Non constant energy loss distribution
418  const double step_size = length_travelled / nElossSteps_;
419  //
420  double current_E = initial_E;
421  const double m2 = m * m;
422  //
423  for (auto i = 0; i < nElossSteps_; ++i) {
424  if (eLossMode_ == 2) {
425  double dedx = energyLossBetheBloch(m, current_E);
426  current_E -= (dedx * step_size);
427  }
428  else {
429  // MPV of Landau energy loss distribution
430  current_E -= energyLossLandau(m2, current_E * current_E, step_size);
431  }
432  if (current_E <= m) {
433  // std::cout<<"WARNING: current_E less than mu mass. it is "<<current_E<<std::endl;
434  return 0.;
435  }
436  }
437  return current_E;
438 }
Float_t x
Definition: compare.C:6
Utilities related to art service access.
ROOT::Math::DisplacementVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Vector_t
Type for representation of momenta in 3D space.
Definition: geo_vectors.h:160
T DirectionAtPoint(unsigned int p) const
Direction at point p. Use e.g. as:
recob::tracking::Vector_t Vector_t
Definition: TrackState.h:19
Helper class to compute the middle point in a point set.
size_t LastValidPoint() const
Returns the index of the last valid point in the trajectory.
bool isValid
Whether this ID points to a valid element.
Definition: geo_types.h:210
constexpr auto abs(T v)
Returns the absolute value of the argument.
STL namespace.
void add(Point const &p)
Accumulates a point.
decltype(auto) constexpr end(T &&obj)
ADL-aware version of std::end.
Definition: StdUtils.h:77
TPCID FindTPCAtPosition(Point_t const &point) const
Returns the ID of the TPC at specified location.
double Length(size_t startAt=0) const
Returns the approximate length of the trajectory.
Float_t E
Definition: plot.C:20
A trajectory in space reconstructed from hits.
T LocationAtPoint(unsigned int p) const
Position at point p. Use e.g. as:
Utilities to extend the interface of geometry vectors.
The data type to uniquely identify a TPC.
Definition: geo_types.h:381
Class storing the result of the Maximum Likelihood fit of Multiple Coulomb Scattering angles between ...
Definition: MCSFitResult.h:19
size_t FirstValidPoint() const
Returns the index of the first valid point in the trajectory.
static constexpr size_t InvalidIndex
Value returned on failed index queries.
Float_t norm
size_t NextValidPoint(size_t index) const
Returns the index of the next valid point in the trajectory.
TPCID_t TPC
Index of the TPC within its cryostat.
Definition: geo_types.h:399
Vector_t StartDirection() const
Returns the direction of the trajectory at the first point.
recob::tracking::Vector_t Vector_t
art framework interface to geometry description