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